I have models Group, Membership and User. Connected with a has_many :through association.
Route wise, membership is nested inside group.
What I want is that whenever someone joins or leaves the group (ie. on create or destroy membership), to initiate a check on the group to check what the dominating language is (this is an attribute in the User model) and update the language attribute in the Group model.
I have a method called define_language in the Group model which seems to work independently.
Now I need to call this method from the Membership model, I was thinking to do this with an after_save callback, but I'm having trouble referencing it to the method in the (different) Group model.
I put this method in the Group model and not the Membership model as I feel semantically it has little to do with the membership. Is this assumption wrong? How would I go about this problem in an efficient way?
One way is :
class Membership < ActiveRecord::Base
belongs_to :group
before_save :update_group_language
...
private
def update_group_language
self.group.define_language
end
end
I can't see how this could work though :
class Membership < ActiveRecord::Base
belongs_to :group
before_save group.define_language
end
The problem with that is that belongs_to is only evaluated by Ruby when rails is first loaded.
I figured it out, you just run in Membership.rb
before_save group.define_language
And tadaa! It will call define_language in Group.rb model.
Optional you can add such to define the relation:
before_save group.define_language "id = #{group_id}"
Related
Assuming
class Kid < ActiveRecord::Base
has_one :friend
end
class Friend< ActiveRecord::Base
belongs_to :kid
end
How can I change this to
class Kid < ActiveRecord::Base
has_many :friends
end
class Friend< ActiveRecord::Base
belongs_to :kid
end
Will appreciate your insight...
Collection
The bottom line is that if you change your association to a has_many :x relationship, it creates a collection of the associative data; rather than a single object as with the single association
The difference here has no bearing on its implementation, but a lot of implications for how you use the association throughout your application. I'll explain both
Fix
Firstly, you are correct in that you can just change your has_one :friend to has_many :friends. You need to be careful to understand why this works:
ActiveRecord associations work by associating something called foreign_keys within your datatables. These are column references to the "primary key" (ID) of your parent class, allowing Rails / ActiveRecord to associate them
As long as you maintain the foreign_keys for all your Friend objects, you'll get the system working no problem.
--
Data
To expand on this idea, you must remember that as you create a has_many association, Rails / ActiveRecord is going to be pulling many records each time you reference the association.
This means that if you call #kind.friends, you will no longer receive a single object back. You'll receive all the objects from the datatable - which means you'll have to call a .each loop to manipulate / display them:
#kid = Kid.find 1
#kid.friends.each do |friend|
friend.name
end
If after doing this changes you have problem calling the save method on the order.save telling you that it already exists, and it not allowing you to actually have many order records for one customer you might need to call orders.save(:validate=> false)
You have answered the question. Just change it in model as you've shown.
I have a Client Model as below:
class Client < ActiveRecord::Base
has_many :custodians,:dependent => :destroy
I have a Custodian Model as below:
class Custodian < ActiveRecord::Base
belongs_to :client
In my custodians table I have record with id = 0 , name = 'N/A' that I want to include in all my collection_selects irrespective of the client_id.
e.g for client_id = 10 I want the following in collection_select
Custodian.where('client_id = 10 or client_id = 0')
I know I can do it in my views but I have too many views so it is not practical. Plus I want a more DRY method on either Custodian model or associations. I tried default_scope on Custodian model but could not get it to work.
Basically I am looking for way to always include custodian with id=0 in each association and collection_select.
You can't do what you want using a has_many and belongs_to approach. To implement a belongs_to relationship, the Custodian record has to have a single client_id field. Your logic requires that the custodian_id=0 record belong to many Client records, so it would have to have many client_id fields, but it can only have one. See the Rails Guides-Active Record Associations-The belongs_to Association (http://guides.rubyonrails.org/association_basics.html)
You can accomplish what you want by using a has_and_belongs_to_many relationship. By making both the Custodian and Client models has_and_belongs_to_many to each other, you will be able to have the custodian_id=0 record belong to many Client records and all the other Custodian records will only belong to one client (even though they could belong to many, your program logic must only allow them to belong to one.) See the has_and_belongs_to_many section of the above Rails Guide. To be clear, here is how your models would look:
class Client < ActiveRecord::Base
has_many_and_belongs_to_many :custodians
end
class Custodian < ActiveRecord::Base
has_many_and_belongs_to_many :client
end
Also, because of your special case on custodian_id=0, you will need to establish the look-up table record for the custodian_id=0 record relationship using an active_record callback (probably before_validation or before_create) when you create a new Client record.
Similarly, you will need to implement your own :dependent => :destroy functionality using the before_destroy callback to preserve the custodian_id=0 record and delete all the other associated Custodian records. You'll also have to destroy the corresponding look-up table entries.
This sounds like a lot of work, but if you absolutely must have the custodian_id=0 record associated with every Client, this is the only way I can see it being done. You may want to evaluate it this is really necessary. There may be other program logic that could allow you to get to similar results without going through this process.
You could use an instance or class method:
#app/models/client.rb
Class Client < ActiveRecord::Base
has_many :custodians,:dependent => :destroy
def inc_zero(id)
where("client_id = ? OR client_id = 0", id)
end
def self.inc_zero_custodians(id)
joins(:custodians).where("client_id = ? OR client_id = 0", id)
end
end
#-> Client.custodians.inc_zero(10)
#-> Client.inc_zero_custodians(10)
I have a model with a var reference to another one.
User -> Profile
When I have generated the Profile model I used the references
feature so it has generated the corresponding migration
....
t.references :user
....
My question is do I have to add a relationship on the User model too?
has_one :Profile
Yes, you need both code in two models and the migration you mentioned.
class User < AR
has_one :profile
end
class Profile < AR
belongs_to :user
end
has_one and belongs_to are just methods which adds some more methods to your model. This means, you can have belongs_to defined on one model and no has_one on the other. The only problem is that you would be able to call profile.user, but no user.profile.
It is absolutely up to you which methods you want to be defined and which you don't need. If you never ever want anyone to call profile.user, but want user.profile just call has_one :profile. In general those method shares nothing except that their using same foreign key column.
It is however worth mentioning, that this is usually advised to declare reverse association - it is not needed for things to work though.
I have the following models
class Business < ActiveRecord::Base
has_and_belongs_to_many :categories
validates_presence_of :category_ids
end
class Category < ActiveRecord::Base
has_and_belongs_to_many :businesses
end
I am setting the relationship through the business creation form, using the category_ids attribute.
I tried using validates_presence_of, however, this is not validating the existence of a category.
I can manipulate the form through the browser, give a non-existing ID for a category. After submitting the form, I get an error:
Couldn't find Category with id=181723
Edit:
Added the following custom validation method, but I am still getting the same error, as if the validation was not being run.
class Business < ActiveRecord::Base
has_and_belongs_to_many :categories
validate :categories_exist
def categories_exist
category_ids.each do |c|
errors.add(:category_ids, :category_doesnt_exist) unless Category.exists? c
end
end
end
There's probably a variety of ways you could achieve this but I'd recommend looking at Custom Validations and ActiveRecord Callbacks.
You can check out the validates_existence gem. This gem has been very useful to me for validating if foreign keys correspond to legitimate parent records. As described in the readme:
This plugin library adds ActiveRecord models a way to check if a
:belongs_to association actually exists upon saving.
I have some problem trying to understand when building a rails app with several models and relation ships between them...
If I take a basic example like a model Group, a model User and a model Car
class Group < ActiveRecord::Base
has_many :users
end
class User < ActiveRecord::Base
belongs_to :group
has_many :cars
end
class Car < ActiveRecord::Base
belongs_to :user
end
Will those relation ship statements automatically create the following functions:
group.users
user.group
user.cars
car.user
It seems that we sometimes need to have to create "references" in migration (like adding a reference toward User in Car table) but is this always required ?
In this case, what is the difference of creating the migration and of adding the relationship statement in the models ? I sometimes have the feeling this is used for the same purpose.
Thanks a lot for your help,
Regards,
Luc
The association declarations are there for Rails only. You have to define the foreign keys (references) in the database, so that Rails can properly save the data.
Remember, despite all the magic, it's still backed by a relational database, so good practices there will pay off in the long run.