I have Group model that has_many institutions, and schools.
class Group
has_many :institutions
has_many :schools
end
However I need my Group to have_and_belongs_to_many :users but I want all the users that are associated through the institutions and schools like this:
class Group
has_many :institutions
has_many :users, :through => :instiutions
has_many :schools
has_many :users, :through => :schools
end
class School
belongs_to :group
has_many :educations
has_many :users, :through => :educations
end
class Institution
belongs_to :group
has_many :institutional_educations
has_many :users, :through => :institutional_educations
end
Obviously this isn't possible so what should I do?
Just offhand, have you considered using single table inheritance, so that school is a type of institution?
Your School class would inherit from Institution ("class School < Institution"). Plus, I guess you'd call it something else but you could have "class GenericInstitution < Institution" as well. Then your Group class could look like this:
class Group
:has_many :school_users, :through => :schools,
:has_many :generic_institution_users, :through => :generic_institutions
# if you need all of the users at once:
:has_many :users, :through => :institutions
end
You'd probably have to specify a foreign key or two to get this to work for you.
Also, I can't quite figure see what an :institutional_education is, but if you really need it you could do the same thing there (maybe "class institutional_education < education", or maybe the other way around.
So I've got three models:
User
User Interest
Interest Tags
User Model
class User < ActiveRecord::Base
has_many :user_interests
end
InterestTag Model
class InterestTag < ActiveRecord::Base
has_many :user_interests, :dependent => :destroy
validates :name, :uniqueness => true
end
UserInterest Model
class UserInterest < ActiveRecord::Base
belongs_to :interest_tag
belongs_to :user
end
I'd like to use ActiveRecord to include the name of the user's interests when loading their profile using the following query:
#user = User.find(current_user.id, :include => [{:user_interests => :interest_tags}])
Migrations for interest_tags + user_interests
create_table :interest_tags do |t|
t.string :name, :null => false, :size => 30
t.timestamp :created_at
end
create_table :user_interests do |t|
t.integer :user_id
t.integer :interest_tag_id
end
What am I doing wrong?
You have to add an has_many :through association on User model.
class User < ActiveRecord::Base
has_many :user_interests
has_many :interest_tags, :through => :user_interests
end
class UserInterest < ActiveRecord::Base
belongs_to :interest_tag
belongs_to :user
end
class InterestTag < ActiveRecord::Base
has_many :user_interests, :dependent => :destroy
validates :name, :uniqueness => true
end
Now you can eager load the tags as follows:
User.find(current_user.id, :include => :interest_tags)
Note:
You might want to look at the acts_as_taggable_on gem for your requirement.
I assume you are building a tagging system, like in stackoverflow: every user kann have multiple tags that they are interested in. In that case the user_interests table is only a join table and does not need a model. Just use has_and_belong_to_many on the two real models.
See also this article on different ways to implement tagging with more or less normalized relational databases. You could also use a non-relational database like Mongodb, there you would need only one table to do tagging, see the cookbook.
I'm having a little trouble with querying multiple tables with different types of association. Can someone please point me in the right direction?
class Sale < ActiveRecord::Base
has_many :items, :dependent => :destroy
end
class Item < ActiveRecord::Base
belongs_to :sale, :dependent => :destroy
has_many :images, :dependent => :destroy
end
class Image < ActiveRecord::Base
belongs_to :item, :dependent => :destroy
end
What would be the query to get all items relating to a sale with the ID 1, and then loop through all the images relating to each item returned?
Thanks for your help.
You can define :through => :something in a has_many association
class Sale < ActiveRecord::Base
has_many :items, :dependent => :destroy
has_many :images, :through => :items
end
and then simply query
Sale.find(1).images
so I have these relationships:
a location:
has_many :services
has_many :products, :through => :services
a product:
has_many :services
has_many :locations, :through => :services
has_many :add_ons
and a service:
belongs_to :product
belongs_to :location
has_many :service_add_ons
has_many :add_ons, :through => :service_add_ons
a service_add_on:
belongs_to :service
belongs_to :add_on
How can I write a :through that will return a location with it's products and each products add_ons?
so far I have:
wants.json { render :json => #location.to_json(:include => {:products => {:add_ons}}) }
which is obviously not working. What can I do to change this and make it work?
Try this:
wants.json {
render :json => #location.to_json(:include => {:products => {:include => :add_ons}})
}
I believe you should use
:include => {:products => :add_ons}
Sorry for my english :S
I think you have an error here:
a service_add_on:
belongs_to :service
belongs_to :add_on
add_on must belongs_to service_add_on and not contrary
I have badges (sorta like StackOverflow).
Some of them can be attached to badgeable things (e.g. a badge for >X comments on a post is attached to the post). Almost all come in multiple levels (e.g. >20, >100, >200), and you can only have one level per badgeable x badge type (= badgeset_id).
To make it easier to enforce the one-level-per-badge constraint, I want badgings to specify their badge by a two-column foreign key - badgeset_id and level - rather than by primary key (badge_id), though badges does have a standard primary key too.
In code:
class Badge < ActiveRecord::Base
has_many :badgings, :dependent => :destroy
# integer: badgeset_id, level
validates_uniqueness_of :badgeset_id, :scope => :level
end
class Badging < ActiveRecord::Base
belongs_to :user
# integer: badgset_id, level instead of badge_id
#belongs_to :badge # <-- how to specify?
belongs_to :badgeable, :polymorphic => true
validates_uniqueness_of :badgeset_id, :scope => [:user_id, :badgeable_id]
validates_presence_of :badgeset_id, :level, :user_id
# instead of this:
def badge
Badge.first(:conditions => {:badgeset_id => self.badgeset_id, :level => self.level})
end
end
class User < ActiveRecord::Base
has_many :badgings, :dependent => :destroy do
def grant badgeset, level, badgeable = nil
b = Badging.first(:conditions => {:user_id => proxy_owner.id, :badgeset_id => badgeset,
:badgeable_id => badgeable.try(:id), :badgeable_type => badgeable.try(:class)}) ||
Badging.new(:user => proxy_owner, :badgeset_id => badgeset, :badgeable => badgeable)
b.level = level
b.save
end
end
has_many :badges, :through => :badgings
# ....
end
How I can specify a belongs_to association that does that (and doesn't try to use a badge_id), so that I can use the has_many :through?
ETA: This partially works (i.e. #badging.badge works), but feels dirty:
belongs_to :badge, :foreign_key => :badgeset_id, :primary_key => :badgeset_id, :conditions => 'badges.level = #{level}'
Note that the conditions is in single quotes, not double, which makes it interpreted at runtime rather than loadtime.
However, when trying to use this with the :through association, I get the error undefined local variable or method 'level' for #<User:0x3ab35a8>. And nothing obvious (e.g. 'badges.level = #{badgings.level}') seems to work...
ETA 2: Taking EmFi's code and cleaning it up a bit works. It requires adding badge_set_id to Badge, which is redundant, but oh well.
The code:
class Badge < ActiveRecord::Base
has_many :badgings
belongs_to :badge_set
has_friendly_id :name
validates_uniqueness_of :badge_set_id, :scope => :level
default_scope :order => 'badge_set_id, level DESC'
named_scope :with_level, lambda {|level| { :conditions => {:level => level}, :limit => 1 } }
def self.by_ids badge_set_id, level
first :conditions => {:badge_set_id => badge_set_id, :level => level}
end
def next_level
Badge.first :conditions => {:badge_set_id => badge_set_id, :level => level + 1}
end
end
class Badging < ActiveRecord::Base
belongs_to :user
belongs_to :badge
belongs_to :badge_set
belongs_to :badgeable, :polymorphic => true
validates_uniqueness_of :badge_set_id, :scope => [:user_id, :badgeable_id]
validates_presence_of :badge_set_id, :badge_id, :user_id
named_scope :with_badge_set, lambda {|badge_set|
{:conditions => {:badge_set_id => badge_set} }
}
def level_up level = nil
self.badge = level ? badge_set.badges.with_level(level).first : badge.next_level
end
def level_up! level = nil
level_up level
save
end
end
class User < ActiveRecord::Base
has_many :badgings, :dependent => :destroy do
def grant! badgeset_id, level, badgeable = nil
b = self.with_badge_set(badgeset_id).first ||
Badging.new(
:badge_set_id => badgeset_id,
:badge => Badge.by_ids(badgeset_id, level),
:badgeable => badgeable,
:user => proxy_owner
)
b.level_up(level) unless b.new_record?
b.save
end
def ungrant! badgeset_id, badgeable = nil
Badging.destroy_all({:user_id => proxy_owner.id, :badge_set_id => badgeset_id,
:badgeable_id => badgeable.try(:id), :badgeable_type => badgeable.try(:class)})
end
end
has_many :badges, :through => :badgings
end
While this works - and it's probably a better solution - I don't consider this an actual answer to the question of how to do a) multi-key foreign keys, or b) dynamic-condition associations that work with :through associations. So if anyone has a solution for that, please speak up.
Seems like it might workout best if you separate Badge into two models. Here's how I'd break it down to achieve the functionality you want. I threw in some named scopes to keep the code that actually does things clean.
class BadgeSet
has_many :badges
end
class Badge
belongs_to :badge_set
validates_uniqueness_of :badge_set_id, :scope => :level
named_scope :with_level, labmda {|level
{ :conditions => {:level => level} }
}
named_scope :next_levels, labmda {|level
{ :conditions => ["level > ?", level], :order => :level }
}
def next_level
Badge.next_levels(level).first
end
end
class Badging < ActiveRecord::Base
belongs_to :user
belongs_to :badge
belongs_to :badge_set
belongs_to :badgeable, :polymorphic => true
validates_uniqueness_of :badge_set_id, :scope => [:user_id, :badgeable_id]
validates_presence_of :badge_set_id, :badge_id, :user_id
named_scope :with_badge_set, lambda {|badge_set|
{:conditions => {:badge_set_id => badge_set} }
}
def level_up(level = nil)
self.badge = level ? badge_set.badges.with_level(level).first
: badge.next_level
save
end
end
class User < ActiveRecord::Base
has_many :badgings, :dependent => :destroy do
def grant badgeset, level, badgeable = nil
b = badgings.with_badgeset(badgeset).first() ||
badgings.build(
:badge_set => :badgeset,
:badge => badgeset.badges.level(level),
:badgeable => badgeable
)
b.level_up(level) unless b.new_record?
b.save
end
end
has_many :badges, :through => :badgings
# ....
end