Multi level joins in rails 4 - ruby-on-rails

I want to make this query in rails 4
select r.region_id, r.region_name from countries c, zones z, regions r where c.country_id = $country_id (pass as parameter) and c.country_id = z.zone_id and z.subzone_id = r.region_id
Models :
#Country.rb
class Country < ActiveRecord::Base
has_one :place, foreign_key: :place_id
has_many :zones , foreign_key: :place_id
has_many :subzones, :through => :zones
end
#Zone.rb
class Zone < ActiveRecord::Base
belongs_to :place
belongs_to :subzone, :class_name => 'Place' , :foreign_key => :subzone_id
end
#Region.rb
class Region < ActiveRecord::Base
has_one :place , foreign_key: :place_id
end
#Place.rb
class Place < ActiveRecord::Base
belongs_to :region, :foreign_key => :place_id
has_many :zones, :foreign_key => :place_id
has_many :subzones, :through => :zones
end
I have tried this:
Country.joins(:zones).where("zone.subzone_id = regions.region_id AND country_id = ?",$country_id )
but got error as :
Java::JavaSql::SQLSyntaxErrorException: ORA-00904: "REGIONS"."REGION_ID": invalid identifier.....
Not sure how to load region in above query...
Thanks in advance :-)

Region.joins(zones: :country).where(country: {country_id: $country_id})
This will only work if you have your models like this:
#Country.rb
class Country < ActiveRecord::Base
has_many :zones, as: :zone
end
#Zone.rb
class Zone < ActiveRecord::Base
has_many :regions, as: :region
belongs_to :country, foreign_key: :zone_id
end
#Region.rb
class Region < ActiveRecord::Base
belongs_to :zone, foreign_key: :region_id
end

when using string for the where statement after a join or include, you need to use the table name, not the relation name to refer to, so where you have written
zone.subzone_id
you would need to write
zones.subzone_id
Your models and relations are a bit confusing and its hard to see what your trying to achieve relation wise with them, place_id seems to be being used as a reference for many things that have nothing to do with the Place class, and it could get a bit messy in the future.
However from what I can grasp, in terms of multi level joins, something like:
Country.joins(zones: {place: :region}).where.........
is something along the lines of what you want to set up your table joins for your query. Play around with that or things similar, hope it helps.

Related

HABTM relationships with polymorphism

I've got a Person who can be linked to many Structures (structure is polymorphic)
I've got a Venue, who can have many People, as a structure.
I've got a Journal, who can have many People, as a structure.
Here is my modelization :
class Venue < ActiveRecord::Base
has_many :structure_people, :as => :structure
has_many :people, :through => :structure_people
end
class Journal < ActiveRecord::Base
has_many :structure_people, :as => :structure
has_many :people, :through => :structure_people
end
class Person < ActiveRecord::Base
has_many :structure_people
has_many :structures, :through => :structure_people
end
class StructurePerson < ActiveRecord::Base
belongs_to :structure, polymorphic: true
belongs_to :person
end
My problem :
when i try to get people on a Venue or on a Journal, it works. Cool :)
BUT
when i try to get structures on a person, i've got an error :
ActiveRecord::HasManyThroughAssociationPolymorphicSourceError: Cannot have a has_many :through association 'Person#structures' on the polymorphic object 'Structure#structure'.
Anyone could help me to solve this ?
Thanks a lot.
Christophe
I think it's a restriction of Rails, because has_many association will guess a class_name automatically. But polymorphic association may returns multiple class_name. Do you mind use this:
class Person < ActiveRecord::Base
has_many :structure_people
has_many :venues, :through => :structure_people
#Journal is the same.
end
class StructurePerson < ActiveRecord::Base
belongs_to :structure, polymorphic: true
belongs_to :venue, :foreign_key => 'structure_id', :conditions => {:structure_type => 'Venue'}
belongs_to :person
end
Although it is an ugly solution...
I think you can choose an alternative way.
class Person < ActiveRecord::Base
has_many :structure_people
def structures
structure_people.map(&:structure)
end
end
You can't get chaining function of has_many, but you can get polymorphic structures :)

Rails 3 / Active Record - Association problems with additional model in assignment table

I currently have a setup that links the models User, Dealer and Role together. User and Dealer is many to many, and is working as expected with a Dealer_user assignment table.
The problem is that I want to have roles assigned to the user that are specific to the dealer also (i.e. a user could be a Sales Manager and a Parts Manager in one dealership, while being a Sales Manager and a Director in another).
In order to do this, I have a Role model (which belongs to a Role_type). Role should belong to Dealer_user, and Dealer_user has many Roles.
The intention is that I will be able to do dealer.users.where(:id => user.id).first.roles and it will return only the roles specific to that dealership.
The problem I have is that when I run the following test code: dealer.users.where(:id => user.id).first.roles.create(:role_type_id => 1 + Random.rand(4))
I get an error: Cannot modify association 'User#roles' because the source reflection class 'Role' is associated to 'DealerUser' via :has_many.
Can anyone suggest what I am doing wrong with my models (which are below)?
NOTE: The belongs_to relationship that Role has with Dealer_user is polymorphic because it could also belong to Sale_user or other association tables, which require the same functionality as Dealer.
class Dealer < ActiveRecord::Base
attr_accessible :name, :address_id
has_many :dealer_users
has_many :users, :through => :dealer_users
has_many :roles, :through => :dealer_users
end
class User < ActiveRecord::Base
attr_accessible :first_name, :last_name
has_many :dealer_users
has_many :dealers, :through => :dealer_users
has_many :roles, :through => :dealer_users
end
class DealerUser < ActiveRecord::Base
attr_accessible :dealer_id, :user_id
belongs_to :dealer
belongs_to :user
has_many :roles, :as => :role_originator
end
class Role < ActiveRecord::Base
attr_accessible :role_type_id
belongs_to :role_type
belongs_to :role_originator, :polymorphic => true
end
Edit: No luck so far - can anyone help?
I would use has_many through association with the roles table. Get rid of the dealer_user table, and add columns to the roles table dealer_id and user_id
Then your models would look something like this:
class Dealer < ActiveRecord::Base
has_many :users, :through => :roles
has_many :roles
end
class User < ActiveRecord::Base
has_many :dealers, :through => :roles
has_many :roles
end
class Role < ActiveRecord::Base
belongs_to :dealer
belongs_to :user
end
That should make it easier to do the types of queries you're trying. The rails guide has a really good overview here

activerecord / db theory - where do I put these fields?

I have the models shown below. I need to store some details that are specific a person and a house (first_viewed:date, opening offer:decimal, etc). I feel like these should belong to the PersonHouse model but I'm not certain enough. Any suggestions?
class Person < ActiveRecord::Base
has_many :houses, through: :person_houses
has_one :favorite_house, through: :person_houses
end
class PersonHouse < ActiveRecord::Base
belongs_to :house
belongs_to :person
end
class House < ActiveRecord::Base
has_many :house_people
has_many :people, through: :person_houses
end
I could do something like this to get all the details but perhaps there is a more effient way.
#house = House.find(1)
#house.house_people.each do |hp|
puts hp.person.name
puts hp.first_viewed
puts #house.address
end
I think your assumption is correct. If the data is relevant to the relationship between a person and a house, then yes it belongs on this model. The only recommendation I would make is to rename this model to a name that better describes what the relationship is. It doesn't have to be the concatenation of the two models it joins. I don't know exactly what the model is going to be ultimately used for, but SelectedHouse, HouseProspect or something along those lines might work.
You can also delegate properties to the house or person models:
class PersonHouse < AR::Base
belongs_to :person
belongs_to :house
delegate :address, :to => :house, :prefix => true
delegate :name, :to => :person, :prefix => true
end
person_house.address
person_house.person_name

How to save data with has_many :through

I have many-to-many relationship between Game and Account models like below:
class Account < ActiveRecord::Base
has_many :account_games, :dependent => :destroy
has_many :games, :through => :account_games
end
class Game < ActiveRecord::Base
has_many :account_games, :dependent => :destroy
has_many :accounts, :through => :account_games
end
class AccountGame < ActiveRecord::Base
belongs_to :account
belongs_to :game
end
Now I know let's say I want to create a record something like:
#account = Account.new(params[:user])
#account.games << Game.first
#account.save
But how am I supposed to update some of the attributes in AccountGame while I'm doing that? Lets say that AccountGame has some field called score, how would I update this attribute? Can you tell me about the best way to do that? To add any field in the through table while I'm saving the object.
#account = Account.new(params[:user])
#accountgame = #account.account_games.build(:game => Game.first, :score => 100)
#accountgame.save
Though I'd strongly recommend that if you start adding columns to your join-model that you call it something different eg "subscription" or "membership" or something similar. Once you add columns it stops being a join model and starts just being a regular model.
This should work:
class AccountGame < ActiveRecord::Base
belongs_to :account
belongs_to :game
attr_accessible :account_id, :game_id #<======= Notice this line
end

Multiple Associations to Same Model

I have two classes that I would like to specify as follows:
class Club < ActiveRecord::Base
belongs_to :president, :class_name => "Person", :foreign_key => "president_id"
belongs_to :vice_president,
:class_name => "Person",
:foreign_key => "vice_president_id"
end
class Person < ActiveRecord::Base
has_one :club, :conditions =>
['president_id = ? OR vice_president_id = ?', '#{self.id}', '#{self.id}']
end
This doesn't work and gives me an error when trying to get the club association from the person object. The error is because is looking for person_id in the club table when I looked at the SQL. I can get around it by declaring multiple has_one associations, but feel like this is the improper way of doing it.
A person can only be the President or Vice President of one club.
Anyone able to offer a little bit of advice on this issue, I would be very appreciative.
Your has_one condition will never work in Rails, as far as I know.
You need one explicit has_one or belongs_to or has_many per "link", on both tables. So if you have two "links", you need two has_one and two belongs_to. That is how it works.
Secondly, I think you should reconsider your models. The way you are doing it, one person can not be the president of a club and an employee, at the same time. Or be the president of two clubs. Even if you don't have these right now, they can come in the future - it is easier to stay flexible right now.
A flexible way of doing this is using a has_many :through with an intermediate table that specifies the role. In other words:
# The memberships table has a person_id, club_id and role_id, all integers
class Membership < ActiveRecord::Base
belongs_to :club
belongs_to :person
validates_presence_of :role_id
validates_numericality_of :role_id
end
class Club < ActiveRecord::Base
has_many :memberships, :dependent => :delete_all
has_many :people, :through => :memberships
end
class Person < ActiveRecord::Base
has_many :memberships, :dependent => :delete_all
has_many :clubs, :through => :memberships
end
Now, assuming that role_id=0 means employee, role_id=1 means president, and role_id=2 means vice_president, you can use it like this:
tyler = Person.find(1) # person_id is 1
other = Person.find(2) # person_id is 2
c = Club.find(1) # club_id is 1
tyler.clubs # returns all the clubs this person is "member" of
c.people # returns all the "members" of this club, no matter their role
#make tyler the president of c
tyler.memberships.create(:club_id => 1, :role_id => 1)
#make other the vicepresident of c
#but using c.memberships instead of other.memberships (works both ways)
c.memberships.create(:person_id => 2, :role_id => 1)
#find the (first) president of c
c.memberships.find_by_role_id(1).person
#find the (first) vicepresident of c
c.memberships.find_by_role_id(2).person
#find all the employees of c
c.memberships.find_all_by_role_id(0).collect { |m| m.person }
#find all the clubs of which tyler is president
tyler.memberships.find_all_by_role_id(1).collect { |m| m.club }
Additional notes:
You could complement this with a roles table and model. Roles would have just a a name, roles would have_many relationships and memberships would belong_to role. Or, you could define methods in memberships for getting the role name (if 0, it returns "employee", if 1, "president", etc
You can add validations on memberhips so no more than 1 person can be made president of a given club, or the same employee on the same club twice. Later on, if you start getting "exceptional cases" in which a person needs to be in two places, you will just have to adapt your validations.
I think your associations are the wrong way around. Your way it is hard to assign a president or vice president.
I would do it like this:
class Club < ActiveRecord::Base
has_one :president,
:class_name => "Person",
:foreign_key => 'president_club_id'
has_one :vice_president,
:class_name => "Person",
:foreign_key => 'vice_president_club_id'
end
class Person < ActiveRecord::Base
belongs_to :club
end
Now you can assign the roles like this:
club.president = Person.create(:name => 'Tom')
club.vice_president = Person.create(:name => 'Andrew')
I suggest you introduce a new model called role. Then have the following:
class Club
has_many :roles
def president
end
def vice_president
end
end
class Person
belongs_to :role
end
class Role
has_one :person
belongs_to :club
end
This is the classic use case for polymorphic associations.
Here is the link:
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
Something like..
class Club < ActiveRecord::Base
belongs_to :person, :polymorphic => true
class Person < ActiveRecord::Base
has_one :club, :as => :position

Resources