I have an app with Two Models Stadium & Team, they have a many-to-many relationship and are joined in the middle by a join table.
My current structure looks like this:
class Team < ActiveRecord::Base
has_many :stadiumteams, :class_name => 'StadiumTeam'
has_many :stadiums, :through => :stadiumteams
end
class Stadium < ActiveRecord::Base
has_many :stadiumteams, :class_name => 'StadiumTeam'
has_many :teams, :through => :stadiumteams
end
class StadiumTeam < ActiveRecord::Base
belongs_to :stadium
belongs_to :team
end
This relationship show “current residents for a stadium”. But now I also want to have a relationship that can display old residents.
For example Tottenham (Team) used to play at White Hart Lane (Stadium) But now play at (Wembley) Stadium.
I am planning to do the following. But I am unsure if this will work or if there is a better way to do it?
Team
class Team < ActiveRecord::Base
has_many :stadiumteams, :class_name => 'StadiumTeam'
has_many :stadiums, :through => :stadiumteams
#has_many :oldstadiums, :class_name => 'OldStadium'
#has_many :stadiums, :through => :oldstadiums
end
Stadium
class Stadium < ActiveRecord::Base
has_many :stadiumteams, :class_name => 'StadiumTeam'
has_many :teams, :through => :stadiumteams
#has_many :oldstadiums, :class_name => 'OldStadium'
#has_many :teams, :through => :oldstadiums
end
New Join Table
class OldStadium < ActiveRecord::Base
belongs_to :stadium
belongs_to :team
end
Any help is very appreciated!
EDIT: (Input fields)
Here's how the code for my inputs currently looks:
<%= f.association :teams, label: 'Current Residents', class:'select2-field', placeholder: "Select teams", collection: #teams, input_html: { multiple: true }, hint: 'Select one or multiple teams.'%>
<%= f.association :teams, label: 'Old Residents', class:'select2-field', placeholder: "Select teams", collection: #teams, input_html: { multiple: true }, hint: 'Select one or multiple teams.' %>
And my params
def stadium_params
params.require(:stadium).permit(:name, :capacity, :city, :country, :location_name, :address, :longitude, :latitude, :image, :surface, :official_opening_date, :cost, :web_url, :also_known_as, :record_attendance, :team_ids => [])
end
You can define relations with a lambda to append an additional where clause in the relation:
class Team < ActiveRecord::Base
has_many :stadiumteams, :class_name => 'StadiumTeam'
has_many stadiums, :through => :stadiumteams
has_many :current_stadiums, ->{ where(stadiumteams: { current_home: true }) }, :through => :stadiumteams, :class_name => 'Stadium', :source => :stadium
has_many :previous_stadiums, ->{ where(stadiumteams: { current_home: false }) }, :through => :stadiumteams, :class_name =>'Stadium', :source => :stadium
end
class Stadium < ActiveRecord::Base
has_many :stadiumteams, :class_name => 'StadiumTeam'
has_many :teams, :through => :stadiumteams
has_many :current_teams, ->{ where(stadiumteams: { current_home: true }) }, :through => :stadiumteams, :class_name => 'Team', :source => :team
has_many :previous_teams, ->{ where(stadiumteams: { current_home: false }) }, :through => :stadiumteams, :class_name =>'Team'. :source => :team
end
class StadiumTeam < ActiveRecord::Base
belongs_to :stadium
belongs_to :team
end
And then you should be able to do (in rails console):
team = Team.first
stadium = Stadium.first
team.previous_stadiums <<
team.save
team.reload.previous_stadiums.include?(stadium)
# => should return true
So this way you can simply use in your UI a input for the association :previous_teams and :current_teams:
f.association :current_teams, #...
There are several ways to fulfill your requirement. Your approach also enough, but for efficiency, since the number of Stadium is small ( thousands ), you can use STI for Stadium entity.
class Stadium < ActiveRecord::
has_many :stadiums_teams
has_many :teams, through: :stadiums_teams
end
class OldStadium < Stadium; end
class RecentStadium < Stadium; end
class StadiumTeam < ActiveRecord::Base
belongs_to :stadium
belongs_to :team
end
class Team < ActiveRecord::Base
has_many :stadiums_teams
has_many :stadiums, through: :stadiums_teams, source: :stadium
has_many :old_stadiums, through: :stadiums_teams,
source: :stadium, class_name: OldStadium.name
has_many :recent_stadiums, through: :stadiums_teams,
source: :stadium, class_name: RecentStadium.name
end
So, in most case, you will work with OldStadium / RecentStadium model, but when you want to check if a team ever played at a specific stadium or not, just use Stadium model.
Related
I am trying to transform my HABTM to has_many through relations. Sometimes I have to connect the same models in different ways. For example to specify different roles for authors.
With a HABTM I would do this through the declaration of a class_name option. Just like:-
class Project < ActiveRecord::Base
has_and_belongs_to_many :curators, :class_name => :author, :through => :projects_curators
end
class ProjectsCurator < ActiveRecord::Base
attr_accessible :project_id, :author_id
belongs_to :project
belongs_to :author
end
class Author < ActiveRecord::Base
has_and_belongs_to_many :projects, :through => :projects_curators
end
But when I transform everything into a has_many through:
class Project < ActiveRecord::Base
has_many :project_curators
has_many :curators, :class_name => :author, :through => :project_curators
end
class ProjectCurator < ActiveRecord::Base
attr_accessible :project_id, :author_id
belongs_to :project
belongs_to :author
end
class Author < ActiveRecord::Base
has_many :project_curators
has_many :projects, :through => :project_curators
end
I get: Could not find the source association(s) :curator or :curators in model ProjectCurator. Try 'has_many :curators, :through => :project_curators, :source => <name>'. Is it one of :author or :project?
When I add :source
has_many :curators, :class_name => :author, :through => :project_curators, :source => :author
I get:
uninitialized constant Project::author
How can I get this working? Thank you very much in advance!
Understanding :source option of has_one/has_many through of Rails should help you out
When you declare the source you're not declaring the class of the has_many relationship, but the name of the relationship you're using as the middle man. In your case, try:
has_many :curators, :through => :project_curators, :source => :author
As someone in the above post states:
Most simple answer - the name of the relationship in the middle
The following are my three models: many users can each have many products (and vice versa) through an associations model.
class Product < ActiveRecord::Base
has_many :associations
has_many :users, :through => :associations
end
class User < ActiveRecord::Base
has_many :associations
has_many :products, :through => :associations
has_many :medium_associated_products, :class_name => "Product", :through => :associations, :source => :product, :conditions => ["associations.strength = ?", "medium"]
has_many :strong_associated_products, :class_name => "Product", :through => :associations, :source => :product, :conditions => ["associations.strength = ?", "strong"]
end
class Association < ActiveRecord::Base
belongs_to :user
belongs_to :product
end
To add a "medium" association.strength product to the user, I usually do:
user.products << product #associations.strength is by default "medium"
My question is how would I do the same thing and add a product to the user but with "strong" association.strength initalized?
You can do the same with strong by
user.strong_associated_products << product
You may need to set to your relations this way:
class User < ActiveRecord::Base
has_many :medium_associations, class_name: 'Association', condition: ["associations.strength = ?", "medium"]
has_many :strong_associations, class_name: 'Association', condition: ["associations.strength = ?", "strong"]
has_many :medium_associated_products, class_name: 'Product', through: :medium_associations, source: :product
has_many :strong_associated_products, class_name: 'Product', through: :strong_associations, source: :product
end
Adding onto #sonnyhe2002's answer. I ended up using a callback such as
has_many :strong_associations, class_name: 'Association', condition: ["associations.strength = ?", "strong"], add_before: :set_the_strength
and then
def set_the_strength(obj)
obj[:strength] = "strong"
end
I generated namespaced models and how can i set many-to-many
relationships, category has many posts, post has many categories
rails g model Blog::Post body:text, title:string
rails g model Blog::Category title:string
rails g model Blog::CategoryPost post_id:integer, category_id:integer
and my models looks like
class Blog::Category < ActiveRecord::Base
attr_accessible :title
has_many :posts, :class_name => 'Blog::Post', :through => :blog_categories_posts
end
class Blog::CategoryPost < ActiveRecord::Base
belongs_to :category, :class_name => 'Blog::Category'
belongs_to :post, :class_name => 'Blog::Post'
end
class Blog::Post < ActiveRecord::Base
attr_accessible :body, :title
has_many :categories, :class_name => 'Blog::Category', :through => :blog_categories_posts
end
This should work. You need to specify relation to intermediate table.
class Blog::Category < ActiveRecord::Base
attr_accessible :title
has_many :categories_posts, :class_name => 'Blog::CategoryPost'
has_many :posts, :class_name => 'Blog::Post', :through => :categories_posts
end
class Blog::CategoryPost < ActiveRecord::Base
belongs_to :category, :class_name => 'Blog::Category'
belongs_to :post, :class_name => 'Blog::Post'
end
class Blog::Post < ActiveRecord::Base
attr_accessible :body, :title
has_many :categories_posts, :class_name => 'Blog::CategoryPost'
has_many :categories, :class_name => 'Blog::Category', :through => :categories_posts
end
Try adding the associations to the CategoryPosts to the Category and Post models. eg:
class Blog::Category < ActiveRecord::Base
...
has_many :blog_category_posts, :class_name => "Blog::CategoryPost"
...
end
I believe you need to do this for both the Category and the Post models.
In my app I have 3 models: Item, Category and Categorization defined as below:
class Item < ActiveRecord::Base
attr_accessible :name, :description
has_many :categorizations
has_many :categories, :through => :categorizations
end
class Category < ActiveRecord::Base
attr_accessible :name, :description, :parent, :children, :items, :parent_id
has_many :children, :class_name => "Category", :foreign_key => "parent_id", :dependent => :nullify
belongs_to :parent, :class_name => "Category"
has_many :categorizations
has_many :items, :through => :categorizations
end
class Categorization < ActiveRecord::Base
attr_accessible :category, :item
belongs_to :category
belongs_to :item
end
However, doing this:
Category.where(:parent_id => self.id).includes(:items)
won't return me the items associated to the category. What am I missing here?
Try this:
Category.where(parent_id: id).joins(:items)
Try this:
Category.where(:parent_id => id).collect(&:items)
It will return the items of all categories matching the where clause.
The situation is this way..
class Organization < ActiveRecord::Base
has_many :role_memberships
has_many :roles
has_many :users, :through => :role_memberships, :uniq => true
end
class User
has_many :role_memberships
has_many :organizations, :through => :role_memberships, :uniq => true
has_many :roles, :through => :role_memberships, :uniq => true
end
class RoleMembership < ActiveRecord::Base
belongs_to :organization
belongs_to :role
belongs_to :user
end
class Role < ActiveRecord::Base
belongs_to :organization
has_many :role_memberships
has_many :users, :through => :role_memberships, :uniq => true
end
The QUESTION is how do I populate all the three foreign-keys in rolemembership table..when I do org.users.push(u) this create a record but role_id is left out...
In this case I will probably create RoleMembership object itself, like this:
RoleMembership.create(:organization_id => org.id, :role_id => role.id, :user_id => user.id)