Rails - column not found for defined 'has_many' relationship - ruby-on-rails

I define a Post class which can link or be linked to multiple posts. To do this I added a PostLink class which specifies post_to and post_from.
I generated the PostLink class by rails g model post_link from_post:integer to_post:integer and of course rake db:migrate, and added
belongs_to :from_post, :class_name => 'Post'
belongs_to :to_post, :class_name => 'Post'
to the class.
And I also have has_many :post_links in my Post class.
I ran rails console and Post.new.post_links and got nil printed out, which is expected. However after I save a Post using
p = Post.new
p.save
and then run p.post_links, it prints out the following error message:
SQLite3::SQLException: no such column: post_links.post_id: SELECT "post_links".*
FROM "post_links" WHERE "post_links"."post_id" = 1
So anybody know why after saving it to the database post_link can not be accessed?

The has_many :post_links association in Post throws an error because it assumes the foreign key in post_links is post_id by default. Since you are using from_post_id and to_post_id, you will have to find a way to group the post_links for "from" posts and "to" posts to get the total set of post_links for a post.
One approach could be to define two associations on Post and an additional method to add the sets together:
class Post < ActiveRecord::Base
has_many :from_post_links, :class_name => 'PostLink', :foreign_key => :from_post_id
has_many :to_post_links, :class_name => 'PostLink', :foreign_key => :to_post_id'
def post_links
from_post_links + to_post_links
end
end
As another option, you could provide special sql to the association to group the sets in a single query:
class Post < ActiveRecord::Base
has_many :post_links, :finder_sql => Proc.new {
%Q{
SELECT *
FROM post_links pl
WHERE pl.to_post_id = #{id}
OR pl.from_post_id = #{id}
}
}

Related

Rails 5 how to form association between tables on multiple shared attributes

In Rails 5, given a relationship between two tables that involves joining them on multiple shared attributes, how can I form an association between the models corresponding to these tables?
SQL:
SELECT *
FROM trips
JOIN stop_times ON trips.guid = stop_times.trip_guid AND trips.schedule_id = stop_times.schedule_id
I tried the following configuration, which works in general...
class Trip < ApplicationRecord
has_many :stop_times, ->(trip){ where("stop_times.schedule_id = ?", trip.schedule_id) }, :inverse_of => :trip, :primary_key => :guid, :foreign_key => :trip_guid, :dependent => :destroy
end
class StopTime < ApplicationRecord
belongs_to :trip, :inverse_of => :stop_times, :primary_key => :guid, :foreign_key => :trip_guid
end
Trip.first.stop_times.first #> StopTime object, as expected
Trip.first.stop_times.first.trip #> Trip object, as expected
... but when I try to use it in more advanced queries, it triggers ArgumentError: The association scope 'stop_times' is instance dependent (the scope block takes an argument). Preloading instance dependent scopes is not supported....
Trip.joins(:stop_times).first #=> the unexpected ArgumentError
StopTime.joins(:trip).first #> StopTime object, as expected
I understand what the error is referencing, but I'm unsure of how to fix it.
EDIT:
I was hoping a single association would be sufficient, but it has been noted two different associations can do the job:
class Trip < ApplicationRecord
has_many :stop_times,
->(trip){ where("stop_times.schedule_id = ?", trip.schedule_id) },
:primary_key => :guid,
:foreign_key => :trip_guid # use trip.stop_times instead of trip.joined_stop_times to avoid error about missing attribute due to missing join clause
has_many :joined_stop_times,
->{ where("stop_times.schedule_id = trips.schedule_id") },
:class_name => "StopTime",
:primary_key => :guid,
:foreign_key => :trip_guid # use joins(:joined_stop_times) instead of joins(:stop_times) to avoid error about instance-specific association
end
Trip.first.stop_times
Trip.eager_load(:joined_stop_times).to_a.first.joined_stop_times # executes a single query
If anyone reading this knows how to use a single association, please at-mention me.
I don't think it is the right solution, but it can help. You can add another similar instance independent association that will be used for preloading only. It will work with :joins and :eager_load but not with :preload.
Note that :includes might internally use either :eager_load or :preload. So, :includes will not always work with that association. You should explicitly use :eager_load instead.
class Trip < ApplicationRecord
has_many :preloaded_stop_times,
-> { where("stop_times.schedule_id = trips.schedule_id") },
class_name: "StopTime",
primary_key: :guid,
foreign_key: :trip_guid
end
# Usage
trips = Trip.joins(:preloaded_stop_times).where(...)
# ...
# with :eager_load
trips = Trip.eager_load(:preloaded_stop_times)
trips.each do |trip|
stop_times = trip.preloaded_stop_times
# ...
end

No Such Column - When Column Exists

Not sure how this is happening but it's saying the column doesn't exist:
SQLite3::SQLException: no such column: element.kind: SELECT COUNT(*) FROM "answers" INNER JOIN "elements" ON "elements"."id" = "answers"."element_id" WHERE "answers"."form_id" = 55 AND "element"."kind" = 6
# element.rb
class Element < ActiveRecord::Base
has_many :answers
end
# answer.rb
class Answer < ActiveRecord::Base
belongs_to :element
belongs_to :form
end
class Form < ActiveRecord::Base
has_many :answers
end
But when I run:
#form.answers.joins(:element).where(:element => {:kind => 6})
I get the sql error above. Not sure what's going on. Any thoughts on what I'm missing?
Thanks!
FYI I'm running rails 3.2.3 with ruby 1.9.3.
The table is elements rather than element as generated by the query ("element"."kind" = 6).
#form.answers.joins(:elements).where(:elements => {:kind => 6})
I would have expected the rest of the query to be generated using the nonexistent element table as well, since you used .joins(:element) instead of .joins(:elements) but perhaps Rails is pluralizing inside .joins() for a belongs_to association.
#form.answers.joins(:element).where(:elements => {:kind => 6})

Rails 3: include a field from has_many through association

My model association is as follows:
#book model
class Book < ActiveRecord::Base
has_many :recommendations, :dependent => :destroy
has_many :similars, :through => :recommendations, :conditions => ['recommendation_type IS NULL'], :order => 'recommendations.created_at DESC'
#recommendation model
class Recommendation < ActiveRecord::Base
belongs_to :book
belongs_to :similar, :class_name => 'Book', :foreign_key => 'similar_id'
#Books_controller - injecting the recommendation_id
#book = Book.find(params[:id])
if params[:content_type]
#content_type = params[:content_type];
else
#content_type = "similars"
end
case #content_type
when "similars"
# get the similars
#book_content = #book.similars
#book_content.each do |similar|
#rec_id = Recommendation.where(:book_id=>similar.id, :recommendation_type=>'S').select('id').first.id
similar << {:rec_id => #rec_id}
# ^-- Above line gives NoMethodError (undefined method `<<' for #<Book:0x10de1f40>):
end
when "references"
# get the references
#book_content = #book.references
#book_content.each do |reference|
#rec_id = Recommendation.where(:book_id=>reference.id, :recommendation_type=>'R').select('id').first.id
reference << {:rec_id => #rec_id}
# ^-- Above line gives NoMethodError (undefined method `<<' for #<Book:0x10de1f40>):
end
end
So as noted above, A book has many similars through recommendations. My requirement is that while retrieving similars, I would also like to include the id of the corresponding record in the join table recommendations.
My questions are:
How can I include the field *recommendation_id* alongwith
similars?
If it cannot be included directly, then what is the correct way to
determine this field separately (as shown above) and then
inject it into the similars instance variable so that I can use
it directly in my views?
I recommend you read the Rails guide on associations, specifically about the has_many :through associations.
A lot of your code doesn't make sense - for example:
#book_similars = Book.similars
This means you have a class method on the Book model for similars, but you don't mention it being defined, or what it returns. Rails just doesn't work like this.

activerecord find through association

I am trying to retrieve an activerecord object from my db. My models are
class User < ActiveRecord::Base
belongs_to :account
has_many :domains, :through => :account
end
And
class Account < ActiveRecord::Base
has_many :domains
has_many :users
end
And
class Domain < ActiveRecord::Base
belongs_to :account
end
Now I would like to retrieve a user based on the username and a domain name (lets assume that these are attributes of the User and the Domain classes respectively). i.e. something along the lines of
User.find(:first, :conditions =>{:username => "Paul", :domains => { :name => "pauls-domain"}})
I know that the above piece of code will not work since I do have to mention something about the domains table. Also, the association between users and domains is a one-to-many (which probably further complicates things).
Any ideas on how should this query be formed?
If you're using Rails 3.x, the following code would get the query result:
User.where(:username => "Paul").includes(:domains).where("domains.name" => "paul-domain").limit(1)
To inspect what happen, you can append .to_sql to above code.
If you're using Rails 2.x, you'd better write the raw sql query.
The following piece of code did the trick:
User.joins(:account).joins('INNER JOIN "domains" ON "accounts"."id" = \
"domains"."account_id"').where(:users => {"username" => "Paul"}).
where(:domains => {"name" => "paul-domain"})
Sorry about the formatting of this long line of code

Rails HBTM join_table overwirting table_name

I'm running Rails 2.3.2 and doing:
class StandardWidget < ActiveRecord::Base
has_and_belongs_to_many :parts, :join_table => "widgets_parts", :association_foreign_key => "widget_custom_id"
end
class Part < ActiveRecord::Base
has_and_belongs_to_many :widgets, :join_table => "widgets_parts", :association_foreign_key => "part_custom_id"
end
p = StandardWidget.find(5)
p.widgets
and get the error
ActiveRecord::StatementInvalid: Mysql::Error: Unknown column 'widgets_parts.standard_widget_id' in 'where clause': SELECT * FROM `widgets` INNER JOIN `widgets_parts` ON `parts`.part_custom_id = `widgets_parts`.part_custom_id WHERE (`widgets_parts`.standard_widget_id = 5 )
How can I get this working?
The Rails documentation on HBTM says:
WARNING: If you‘re overwriting the
table name of either class, the
table_name method MUST be declared
underneath any has_and_belongs_to_many
declaration in order to work.
What does this mean?
You'll need to use :foreign_key also in the has_and_belongs_to_many call. So in the StandardWidget model you need this:
has_and_belongs_to_many :parts, :join_table => "widgets_parts", :association_foreign_key => "widget_custom_id", :foreign_key => "part_custom_id"
The warning in the docs means that if you are using table names other than 'parts' for the Part model and 'standard_widgets' for the StandardWidget model, then you need to call 'set_table_name' beneath the habtm call.

Resources