Model transactions

I have three tables: groups, memberships, and users. When a user creates a group I need to make sure that a membership in that group is also created. I have considered using transactions but I keep seeing that these are being deprecated. Is there some other practice that is simpler or preferred?

Groups: name description owner_id (user_id) [has_many :users, through :memberships] [has_many :memberships]

Memberships: groups_id user_id confirmed_at [belongs_to :groups] [belongs_to :users]

Users: user_id login: [has_many :groups, through membership] [has_many :memberships]

Since Memberships is essentially a join table with an extra attribute, and I see that you already have your :through relationships defined, simply doing

@group = Group.new( params[:group] ) @user.groups << @group

will create the membership record (assuming that @user is the User object).

Rein

I see on my second reading that you want to both set the owner_id of the group and create a membership. This is a bit more complicated (but not much!)

You will need to specify the association between Group and User:

class Group < AR::Base

belongs_to :owner, :class_name => "User", :foreign_key => "owner_id"

end

class User < AR::Base

has_many :owned_groups, :class_name => "Group", :foreign_key => "owner_id"

end

then your code to create a group belonging to this user could be:

@group = Group.new( params[:group] ) @group.owner = @user @user.groups << @group

You could also use scoping:

@user.owned_groups.build( params[:group] ) @user.groups << @group

The key is that @user.owned_groups as set up here are the groups where the group's owner_id == @user.id and @user.groups are the groups that the @user is a member of via the memberships join table.

As far as I can see, it is better to use has_and_belongs_to_many to define the relationship of user and group.

David Andrew Thompson wrote:

I have three tables: groups, memberships, and users. When a user creates a group I need to make sure that a membership in that group is also created. I have considered using transactions but I keep seeing that these are being deprecated.

IIRC transactions aren't deprecated only the form where the rollback not only applies to the DB but to the ActiveRecord::Base objects too.

The day ActiveRecord::Base completely removes transaction support is the day someone will fork it or provide the feature as a plugin :slight_smile:

Lionel

David,

There are two relationships between user and group:

One is a many to many relationship (the concept of membership), one is a one to many (the concept of ownership).

The many to many is already expressed via a has_many :through with a Membership join model. This is roughly equivalent to a habtm but also allows attributes on the join (like the confirmed_at timestamp) because the join table is represented as a first order class via the Membership model. Habtm would not be sufficient for this relationship.

The other is the "ownership" of the group, as expressed with the owner_id foreign key. This is a completely separate relationship and needs to be specified as a has_many/belongs_to.

I hope this better explains the reasoning behind the relationships I outlined above.

You are correct. My code above for creating and assigning a group can be wrapped in a

transaction do   [code here] end

block to wrap it in a transaction to provide rollback if any of the operations fails.

It is the Model.transaction form that is deprecated. See http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html

I would encapsulate all of this on the user model.

class Group < AR::Base   belongs_to :owner, :class_name => "User", :foreign_key => "owner_id" end

class User < ActiveRecord::Base   has_many :groups, :through => :memberships   has_many :memberships

  def create_group(options)     g = Group.new(options.merge({ :owner => self }))     groups << g if g.save     g   end end

Your controller code becomes the even simpler:

@group = @user.create_group params[:group]

Now you've got a method which explains what you _really_ want to do, and handles it without the application code worrying about the details.

Pat

Pat,

That's an excellent intention-revealing abstraction and a great example of the smart model, dumb controller design paradigm.

Rein

Fantastic thread! Thanks so much everyone. This was immensely helpful. -Dave