belongs_to not using primary key option - ruby-on-rails

I've been struggling with this for a while, and decided to throw it out there:
I have 3 models, User, Connection, Suspect
A User has many Connections,
A Connection has one Suspect, linked via case_id
A User has many Suspects through its Connections.
The code is as follows:
class User < ActiveRecord::Base
has_many :followers
has_many :suspects, :through => :followers
end
class Connection < ActiveRecord::Base
belongs_to :user
belongs_to :suspect, :primary_key => :case_id , :foreign_key => :case_id
end
class Suspect < ActiveRecord::Base
belongs_to :connection, :primary_key => :case_id , :foreign_key => :case_id
end
The problem is that the belongs_to seems to ignore the :primary key.
If I do
u = User.find(:first)
u.suspects
The SQL generated is:
SELECT `suspects`.* FROM `suspects` INNER JOIN `connections` ON `suspects`.id = `connections`.case_id WHERE ((`followers`.user_id = 1))
However it should be:
SELECT `suspects`.* FROM `suspects` INNER JOIN `connections` ON `suspects`.case_id = `connections`.case_id WHERE ((`followers`.user_id = 1))
Can someone point me in the right direction?
James

The docs for belongs_to say that you can use the :primary_key option, but we have never been able to get it to work either. That said the latest version of rails, 2.3.3, specifically has a fix for this

After more testing, #pallan is correct, this was fixed in 2.3.3 but only for the first level of association, not for has_many :through.
For example:
Connection.suspects
Uses the correct SQL, but
User.connections.suspects
Does not.
I'm going to open a ticket for it now.
JP

Related

How to Query Multiple Associated Tables in Rails

I have two associated table in my application and I cannot figure it how to join them together in rails, below is my Model:
class Lead < ApplicationRecord
has_many :employee_leads
has_many :employees, :through => :employee_leads
end
class EmployeeLead < ApplicationRecord
belongs_to :employee
belongs_to :lead
end
class Employee < ApplicationRecord
has_many :employee_leads
has_many :leads, :through => employee_leads
has_many :emp_stores
has_many :stores, :through => emp_stores
end
class EmpStore < ApplicationRecord
belongs_to :store
belongs_to :employee
end
class Store < ApplicationRecord
has_many :emp_stores
has_many :employees, :through => :emp_stores
end
My application required me to find out which store each lead belongs to. I know how to join the lead to the employee, which is:
Lead.joins(employee_leads: :employee)
and I also know how to join the employee to the store
Employee.joins(emp_stores: :store)
Those are working for me without issue. When I try to get the lead join to store, I used:
Lead.joins(employee_leads: :employee { emp_stores: :store })
This gave me a syntax error, I refer to the link of Active Record regarding Joining Nested Associations (Multiple Level) and I still can't figure it out. I'm very new to this, please someone take some time to explain and help me out. Thank you.
Try this:
Lead.joins(employee_leeds: [employee: [amp_stores: :store] ])
I believe these variants should also work:
Lead.joins(employee_leeds: [{employee: [{amp_stores: :store}] }])
Lead.joins(employee_leeds: {employee: {amp_stores: :store} })
Although, depending on how you're using this you might want to consider using includes rather than joins. Joins are lazy-loaded whereas includes are eager-loaded.
As an explanation to why your syntax was wrong ... :something is a symbol. And in the old (pre ruby 1.9) world, hash key/value pairs were written like so: :key => :value. However, from Ruby 1.9 the newer syntax of key: :value was provided.
In your attempt to get this working you had a key/value pair followed (without a separator) by another hash. In order to fix it, you need to provide just one value to the first key:
# Old syntax
:employee_leads => [ :employee => [ :emp_stores => :store ] ]
Which when you consider the new syntax for hashes, hopefully makes this more logical to understand:
# New syntax
employee_leads: [ employee: [ emp_stores: :store ] ]

Related object not saved

I have the models:
class Idea < ActiveRecord::Base
has_many :connections, :class_name => 'IdeaConnection', :foreign_key => 'idea_a_id', :dependent => :destroy
has_many :ideas, :through => :connections, :source => :idea_b, :dependent => :destroy
end
class IdeaConnection < ActiveRecord::Base
belongs_to :idea
belongs_to :idea_a, :class_name => 'Idea'
belongs_to :idea_b, :class_name => 'Idea'
belongs_to :relationship
end
class Relationship < ActiveRecord::Base
has_many :idea_connections
end
Idea, as you can see, own itself through Connections (join table). Each Connection entry belongs to Relationship. What I'm trying to do is to, after adding an Idea to another with:
Idea.find(1).ideas << Idea.find(2)
which is working and saving properly, get its connection on join table and update its relationship:
Ex:
Idea.find(1).connections.find_by_idea_b_id(Idea.f
ind(2).id).relationship = Relationship.find(1)
It processes correctly but it won't save.
Please, help, what am I missing?
ps: I don't want to do it by manually editting the relationship_id since it's ugly.
ps2: Before you answer, remember the fact that autosave:true do not work for belongs_to/has_many relationships.
You're working with connection object. Remember it.
Your problem is that you call find_by... method in has_many association. It returns one record BUT Idea model has no ruby attribute link to that. See here why. That's why Idea#save cannot call IdeaConnection#save (remember that for cascade saving connections realtion must have autosave: true if connection already exists).
So I suggest you two options:
Set :inverse_of on Idea#connections and IdeaConnection#idea_a relations and preload all records before mangling with Idea.find(1).connections.find_by_idea_b_id(Idea.f
ind(2).id).relationship = Relationship.find(1). But I don't recommend you to do so because:
As I said you're working with connection object. Just do so:
Idea.find(1).connections.create do |connection|
connection.idea_b = Idea.find(2)
connection.relationship = Relationship.first
end
This line of code won't save it
Idea.find(1).connections.first.relationship = Relationship.first
What you need to do is:
Method #1: ( add the connection to the has_many relation )
Relationship.first.idea_connections << Idea.find(1).connections.first
OR
Method #2: ( add relationship_id to the connection then manually save it)
connection = Idea.find(1).connections.first
connection.relationship_id = Relationship.first.id
connection.save
Assignments on the relational object level don't/won't automatically save; you have to tell them to. In ActiveRecord the push method (<<) has save built into it, which is why that was working for you. Setting a value (=) however does not have save built in, so you have to do it manually.
If you're interested, here's a link to another SO question where an answer talks about why push saves: Rails push into array saves object

Rails Active Record: modifying and saving the child of an object from the result of joins?

So, this:
p = Person
.joins('join organization o on o.id = organization_id')
.where('o.id' => 1)
.select('person.*')
.first!
p.name = 'hi!'
p.save!
works as expected, saving the person's name.
But, how would I do this:
p.organization.name = 'bye!'
p.save!
I can't figure out the right projection to get the organization fields to map (or if it's possible). I've tried '*' and 'organization.name as "person.organization.name"'.
In order for what you're doing to work, you have to set the autosave option to true in your belongs_to :organization association.
belongs_to :organization, autosave: true
or just call save on the organization
p.organization.name = 'Org Name'
p.organization.save
You have to declare association in your Person class, using
belongs_to
has_one
has_many
has_many :through
has_one :through
or has_and_belongs_to_many,
and Rails will do the join by it self and link your both class together.
Let me paste here a section of Rails guide:
With Active Record associations, we can [..] tell Rails that there is a connection between the two models. Here’s the revised code for setting up customers and orders:
class Customer < ActiveRecord::Base
has_many :orders, :dependent => :destroy
end
class Order < ActiveRecord::Base
belongs_to :customer
end
With this change, creating a new order for a particular customer is easier:
#order = #customer.orders.create(:order_date => Time.now)
I suggest you read the complete guide here: http://guides.rubyonrails.org/association_basics.html

ActiveRecord query without *_id

I have 3 simple models:
class User < ActiveRecord::Base
has_many :subscriptions
end
class Product < ActiveRecord::Base
has_many :subscriptions
end
class Subscription < ActiveRecord::Base
belongs_to :user
belongs_to :product
end
I can do a_subscription.product = a_product and AR knows I mean product_id and everything works fine.
But If i do:
Subscription.where :product => a_product
It throws an error at me Unknown column 'subscriptions.product' - It knows in the first case that I mean product_id but it doesn't in the latter. I am just wondering if this is how it is suppose to be or am I missing something? I can get it to work by saying
Subscription.where :product_id => a_product
by do I have to specify _id?
Yes, right now you can't pass association to the where method. But you'll be able to do it in Rails 4. Here is a commit with this feature.
I don't think there's an elegant way around that (as of now, see #nash 's answer). However, if you have an instance of a_product and it has has_many on subscriptions, why not just turn it around and say:
subscriptions = a_product.subscriptions

Rails find confusion

Please help me get the include right.
Poem
has_many :awards
has_one :overall_ranking
Award
belongs_to :poem
# before
# has_one :overall_ranking, :foreign_key => :poem_id
## SOLUTION
# after
has_one :overall_ranking, :foreign_key => :poem_id, :primary_key => :poem_id
OverallRanking
belongs_to :poem
update: Award.all(:include => [:overall_ranking]) # works with SOLUTION
Please note that I cannot depend on Poem#id as users may delete the poem, but if it's a winner, I make a copy within Award, so I must depend only on Award#poem_id Thank you!
Your problem is that:
has_one :overall_ranking, :foreign_key => :poem_id
means that Award has one OverallRanking and that the ID of Award in has_one relation is in column poem_id, i.e. your logic is wrong.
It would make more sense if you would just use:
#award.poem.overall_rating
Or in find:
Award.all(:include => [:poem => {:overall_ranking}])
You might want to give some more information but I hope this helps:
You might want to try joins.
For example:
Award.all(:joins => :overall_ranking, :conditions => ['some_attribute_from_overall_ranking=?', true])
So this will find awards and include the overall_ranking.
There is some confusion for me.
Award
belongs_to :poem
has_one :overall_ranking, :foreign_key => :poem_id
Here you are using the same id for both relation. This means that you try to retrieve the overall ranking with the poem id.
If I translate to sql you say something like:
overall_ranking.id = poem_id
I think this is wrong.
If you like to have the same overall_ranking for award and poem you can write something like this:
Award.rb
belongs_to :poem
has_one :overall_ranking, :through=>:poem
You can include like
Award.all(:include => [:overall_ranking])
or nested
Award.all(:include => [{:poem=>:overall_ranking}])
Update
1.Your has one association is set up incorrectly.
Please see: http://blog.hasmanythrough.com/2007/1/15/basic-rails-association-cardinality
Award
belongs_to :poem
belongs_to :overall_ranking, :foreign_key => :poem_id
OverallRanking
belongs_to :poem
has_one :award
You always should have belongs_to at the model where you store the referencing id.
2. But this not resolves your problem in your logic.
With this you will still has association between Award#poem_id = OverallRanking#id. You should have Award#poem_id = OverallRanking#poem_id.
I suggest to add overall_ranking_id to Award and things become much cleaner.

Resources