How to join the same 2 models twice in Rails 4.0? - ruby-on-rails

I'm on rails 4 and I couldn't figure out how to join two models twice in rails. I found an answer to my problem here but it's an old one, here's what it says:
class User < ActiveRecord::Base
has_many :user_countries
has_many :event_countries,
:through => :user_countries,
:source => :country,
:conditions => { :event => true }
has_many :research_countries,
:through => :user_countries,
:source => :country,
:conditions => { :research => true }
end
class UserCountry < ActiveRecord::Base
belongs_to :country
belongs_to :user
# * column :event, :boolean
# * column :research, :boolean
end
class Country < ActiveRecord::Base
# ...
end
I found this solution interesting as I only need one join table for UserCountries, however it doesn't seem to work in rails 4 (the conditions method has been deprecated in rails 4.0), so my question is simply : how would you do this in rails 4.0 ?

The solution you mention is still valid, you just need to change the conditions part to adopt the new Rails 4 convention (see similar question here):
class User < ActiveRecord::Base
has_many :user_countries
has_many :event_countries,
-> { where(user_countries: {:event => true}) },
:through => :user_countries,
:source => :country
has_many :research_countries,
-> { where(user_countries: {:research => true}) },
:through => :user_countries
:source => :country
end
class UserCountry < ActiveRecord::Base
belongs_to :country
belongs_to :user
end
class Country < ActiveRecord::Base
# ...
end

Related

Two associations between same tables with Ruby On Rails

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.

AR joined class in parent class

i have Author entity which belongs_to User. User has_many posts. Please advice how can i show recent_posts on Author entity from User.
class User < ActiveRecord::Base
has_many :posts, :foreign_key => "author_id"
end
class Post < ActiveRecord::Base
attr_accessible :title, :content
belongs_to :author, :class_name => "User"
end
class Author < ActiveRecord::Base
belongs_to :user
has_many :recent_posts, :through => :user,
:class_name => "Post",
:limit => 3,
:order => "updated_at desc"
end
How recent_post should be done? Raw sql?
You want the :source option to has_many, which you use to specify the association on the other model, like so:
has_many :recent_posts, :through => :user, :source => :posts, :limit => 3, :order => 'updated_at desc'

Ruby on Rails has_many_through with validation and logic to ensure only one record in the join table which is updated as required

I have the Course model which has a number of has many through associations with the User model with join table CourseUser. The join table has an attribute type_str which specifies which role the user takes on. I have added validation to ensure that only one record is present in the join table for each course, user pair. The problem is ensuring that this record is updated if it is already present, rather than adding a new one which of course makes validation fail.
User class:
class User < ActiveRecord::Base
...
has_many :courses_enrolled_on, :through => :course_enrollees, :source => :course, :conditions => { :course_users => { :type_str => "enrollee" } }
has_many :course_users
has_many :courses, :through => :course_users, :source => :course, :readonly => true
end
Course class
class Course < ActiveRecord::Base
has_many :course_enrollees, :conditions => { :type_str => "enrollee" }, :class_name => CourseUser
has_many :enrollees, :through => :course_enrollees, :source => :user
has_many :course_users
has_many :users, :through => :course_users, :source => :user, :readonly => true
end
Course class:
class CourseUser < ActiveRecord::Base
belongs_to :course
belongs_to :user
validates_uniqueness_of :course_id, :scope => :user_id
end

Polymorphic associations in Rails 3

I think I'm going crazy.
Let's say I have 3 models: Address, Warehouse, Category:
class Address < ActiveRecord::Base
belongs_to :category
belongs_to :addressable, :polymorphic => true
scope :billing_addresses , where(:categories => {:name => 'billing'}).joins(:category)
scope :shipping_addresses , where(:categories => {:name => 'shipping'}).joins(:category)
end
class Category < ActiveRecord::Base
has_many :addresses
has_many :subcategories, :class_name => "Category", :foreign_key => "category_id"
belongs_to :category, :class_name => "Category"
end
class Warehouse < ActiveRecord::Base
has_many :addresses, :as => :addressable
end
Address is polymorphic, because eventually I'll be using it to store addresses for clients, people, employees etc. Also each address can be of a certain type: billing, shipping, work, home, etc.
I'm trying to pull some information on a page.
#some_warehouse = Warehouse.first
Then in my view:
%b= #some_warehouse.name
%b= #some_warehouse.billing_address.address_line_1
Etc.
I end up doing a lookup for each line of information.
I tried to do things like
Warehouse.includes(:addresses).where(:name => "Ware1")
Warehouse.joins(:addresses).where(:name => "Ware1")
And various variations of that.
No matter what I don' I can't get rails to preload all the tables. What am I doing wrong?
Here are revised models, that do appropriate joins in sql and reduce number of quesries from 16 to 8, one for each piece of info, instead of multiples ones that also do lookup categories, etc.:
class Address < ActiveRecord::Base
belongs_to :category
belongs_to :addressable, :polymorphic => true
scope :billing_addresses , where(:categories => {:name => 'billing'}).includes(:category)
scope :shipping_addresses , where(:categories => {:name => 'shipping'}).includes(:category)
end
class Warehouse < ActiveRecord::Base
has_many :addresses, :as => :addressable, :include => :category, :dependent => :destroy
def billing_address
self.addresses.billing_addresses.first
end
def shipping_address
self.addresses.shipping_addresses.first
end
end
class Category < ActiveRecord::Base
has_many :addresses
has_many :subcategories, :class_name => "Category", :foreign_key => "category_id"
belongs_to :category, :class_name => "Category"
end
Sleep helps. Also not forgetting to reload console from time to time :-)
Maybe you want to use preload_associations?

How can I join the same 2 models twice in Rails?

I have an app with country preferences. A user will have 2 types of country preferences - event and research. In the future there may be more. I was leaning more towards having 2 tables to represent this over using STI. I'm having a bit of trouble configuring Rails elegantly to do this. I could hack it but I would rather do this by Rails convention. What I want is something like this:
class User < ActiveRecord::Base
has_many event_countries, :through => :event_countries, :class_name => 'Country'
has_many research_countries, :through => :research_countries, :class_name => 'Country'
end
class EventCountry < ActiveRecord::Base
belongs_to :country
belongs_to :user
end
class ResearchCountry < ActiveRecord::Base
belongs_to :country
belongs_to :user
end
class Country < ActiveRecord::Base
...
end
This doesn't work though. Given this "pseudo code" does anyone know how to actually implement this in Rails?
I think you're going about declaring them wrong, because this should work properly. That's what the :through directive is for:
class User < ActiveRecord::Base
has_many :event_countries
has_many :countries_with_events,
:through => :event_countries,
:source => :country
has_many :research_countries
has_many :countries_with_researches,
:through => :research_countries,
:source => :country
end
class EventCountry < ActiveRecord::Base
belongs_to :country
belongs_to :user
end
class ResearchCountry < ActiveRecord::Base
belongs_to :country
belongs_to :user
end
class Country < ActiveRecord::Base
# ...
end
A lot of the awkwardness comes from the labels you've chosen for the tables. Although they'd seem reasonable at first glance, the way you use them ends up making them difficult.
You might want to call research_countries something like user_research_countries so that the relationship name can be user.research_countries as the :through:
class User < ActiveRecord::Base
has_many :user_event_countries
has_many :event_countries,
:through => :user_event_countries,
:source => :country
has_many :user_research_countries
has_many :research_countries,
:through => :user_research_countries,
:source => :country
end
class UserEventCountry < ActiveRecord::Base
belongs_to :country
belongs_to :user
end
class UserResearchCountry < ActiveRecord::Base
belongs_to :country
belongs_to :user
end
class Country < ActiveRecord::Base
# ...
end
You can refactor this even further by adding a field to the user-country association table that includes one or more flags, which in this case would be research or event or whatever you require later:
class User < ActiveRecord::Base
has_many :user_countries
has_many :event_countries,
:through => :user_countries,
:source => :country,
:conditions => { :event => true }
has_many :research_countries,
:through => :user_countries,
:source => :country,
:conditions => { :research => true }
end
class UserCountry < ActiveRecord::Base
belongs_to :country
belongs_to :user
# * column :event, :boolean
# * column :research, :boolean
end
class Country < ActiveRecord::Base
# ...
end

Resources