Reputation: 422
I have fairly simple problem but I can not think of the simple solution. I have 3 models.
class User < ActiveRecord::Base
has_many :memberships
end
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :school
end
class School < ActiveRecord::Base
has_many :memberships
end
A User can create a membership for school(s). "membernumber" is given by school. So far so good.
Except one use case, where a membership record gets created by school without user_id value in memberships table.
In this case a record gets created with "membernumber", "school_id" and empty "user_id" field.
What I want to do is, when a user tries to create his membership (using his "membernumber") with a school, First I want to see if "user_id", "membernumber". school_id combination is unique if found (I used validates_uniqueness_of....).
If record not found than check a record/association with "membernumber" and school exists and it's user_id field is empty. If that is the case then don't create a new membership but just update it's user_id field (with current_user.id) and return with a msg that a creation of new association is successful (as if its a new membership record).
How do I do this? Do I need to use any callbacks? How do I use callbacks in this case? Any help is appreciated.
Thanks,
Atarangp
Upvotes: 1
Views: 2340
Reputation: 422
This is how I solved it.
In model membership, added a method membership_exists and called it from membership's create
action.
class Membership < ActiveRecord::Base
belongs_to :school
belongs_to :user
#validations, etc.
def Membership.membership_exists(membernumber, school_id)
membership = Membership.where(:membernumber => membernumber, :school_id => school_id, :user_id => nil).first
if membership.nil?
return false
else
membership.update_attribute :user_id, user_id
return true
end
end
end
And in Membership controller (in create action)
def create
@membership = Membership.new(params[:membership])
@membership.user_id = current_user.id
@flag = Membership.membership_exists(@membership.membernumber,@membership.school_id)
if @flag == true
flash[:notice] = "Membership creation is successful"
redirect_to @membership
return
end
respond_to do |format|
if @membership.save
format.html { redirect_to @membership, notice: 'Membership was successfully created.' }
format.json { render json: @membership, status: :created, location: @membership }
else
format.html { render action: "new" }
format.json { render json: @membership.errors, status: :unprocessable_entity }
end
end
end
This works. But is it a good way to do it?
Thanks, Atarangp.
Upvotes: 0
Reputation: 1868
Only going by the models, one can make out:
has_and_belongs_to_many
or a has_many :through
relationship between User and SchoolIf the above works for you, then your models shall look something like:
class User < ActiveRecord::Base
has_many :memberships
has_many :schools, :through => :memberships
end
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :school
end
class School < ActiveRecord::Base
has_many :memberships
has_many :users, :through => :memberships
end
Now, you should be able to add users to a school and vice-versa.
user = User.first
user.schools << School.first
This would automatically create a record in Membership with appropriate references. And the user would start showing up in School.first.users
. Further, you could add validations for uniqueness within the scope of a school_id i.e. validates_uniqueness_of :user_id, :scope => :school_id
.
before_ after_ callbacks are good but in this case, you'll need some custom code for keeping things in sync. The above would eliminate any dependency on before_ after_ callbacks. Also, you wouldn't need to bother about any other validation checks on Membership model since the record would be created automatically only when a User record is added to a School record and vice-versa.
Upvotes: 1
Reputation: 3818
You should enforce uniqueness constraints in your migrations as well, however much DHH like's to preach the slathering of 'dumb persistance' across the board and only handle this at the application level.
The simplest approach is to enforce validates_presence_of :user_id
in your Membership
model. This will not allow join-records to be created if a user ID isn't present; your AR associative queries should inherently pass this ID on anyways.
For example, consider - User.last.memberships.build(@school)
and in the context of your application, the User will be the current_user
itself.
before_create
and other before_
AR callbacks are definitely handy, but I'd always leave such an approach as a last resort. The reason is you'd intuitively expect your app to function in a certain manner whilst these before_
callbacks, can essentially mutate the information hitting the DB. Of course, unit tests will take care of such issues.
Upvotes: 3