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.
Related
I am trying to create an association between two models (Submissions & Agent Activity) whereby each agent can log an activity against a submission without changing the actual submission record itself.
These are my rb files and I believe that these are setup correctly:
class Submission < ApplicationRecord
belongs_to :user, :optional => true
belongs_to :location, :optional => true
has_many :agent_activities
end
class AgentActivity < ApplicationRecord
belongs_to :submission, :optional => true #has submission_id foreign key in table
belongs_to :agent, :optional => true #has agant_id foreign key in
table
end
However, I get lost when it comes to showing this association in the controller. Currently my code looks like this:
#submissions = Submission.where(:Desired_Location => current_agent.Company_Business_Location).pluck(:AgentActivity)
However, I get this error when i do that
PG::UndefinedColumn: ERROR: column "AgentActivity" does not exist
LINE 1: SELECT "AgentActivity" FROM "submissions" WHERE
"submissions... ^ : SELECT "AgentActivity" FROM "submissions" WHERE
"submissions"."Desired_Location" = $1
Additionally, when I run
Submission.where(:Desired_Location =>
current_agent.Company_Business_Location).map(&:AgentActivity)
I get this error:
undefined method `AgentActivity' for #<Submission:0x007fc78601e168>
Did you mean? agent_activities
And when I run:
AgentActivity.joins(:submission).where('submission.Desired_Location'
=> current_agent.Company_Business_Location)
I get this error:
PG::UndefinedTable: ERROR: missing FROM-clause entry for table
"submission"
LINE 1: ..."."id" = "agent_activities"."submission_id" WHERE
"submissio...
^
: SELECT "agent_activities".* FROM "agent_activities" INNER JOIN
"submissions" ON "submissions"."id" =
"agent_activities"."submission_id" WHERE
"submission"."Desired_Location" = $1
You have a typo in class Submission
it should be has_many :agent_activities
.pluck returns an array of values from a database column. You don't have AgentActivity column in submissions table. You can use map instead:
#submissions = Submission.where(:Desired_Location => current_agent.Company_Business_Location).map(&:AgentActivity)
or revert the way you do the query using join:
AgentActivity.joins(:submision).where('submission.Desired_Location' => current_agent.Company_Business_Location)
You may also want to change association naming to snake case to follow Ruby/Rails conventions:
class Submission < ApplicationRecord
belongs_to :user, :optional => true
belongs_to :location, :optional => true
has_many :agent_activities # instead of :Agent_activities
end
EDIT:
map correction:
#submissions = Submission.where(:Desired_Location => current_agent.Company_Business_Location).map(&:agent_activity)
join correction:
AgentActivity.joins(:submision).where('submissions.Desired_Location' => current_agent.Company_Business_Location)
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
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}
}
}
Just started playing with Ruby (no IT background) and restarted the project based on previous question / answer (link). I have got the following situation now:
created a currencymaster table with the following columns id:integer , description:string and isocode:string whereby the ID column is created by Ruby.
created a currencyrates table with the following columns id:integer , dominant:integer and converted:integer and rate:decimal whereby the ID column is created by Ruby.
Based on help on this site I created the following models.
The models/currencymaster.rb looks like this:
class Currencymaster < ActiveRecord::Base
has_many :currency_rates_dominant, :validate => true, :class_name => 'Currencyrate'
has_many :currency_rates_converted, :validate => true, :class_name => 'Currencyrate'
end
The models/currencyrate.rb looks like this:
class Currencyrate < ActiveRecord::Base
belongs_to :currency_master_doms, :class_name => 'Currencymaster'
belongs_to :currency_master_convs, :class_name => 'Currencymaster'
end
I haven't changed anything yet in the both controllers.
The views\currencyrates\index.html.erb is generated automatically via Ruby and is showing the values of the records as integer. The goal is to show the currencymaster.iso value out of the Currencymaster table for both currencyrate.dominant and currencyrate.converted
Thanks a lot!!
I think you should change your class like this :
class Currencyrate < ActiveRecord::Base
belongs_to :currency_master_dom, :class_name => 'Currencymaster', :foreign_key => 'dominant'
belongs_to :currency_master_conv, :class_name => 'Currencymaster' , :foreign_key => 'converted'
end
After that you should do this :
rate = Currencyrate.first
rate.currency_master_dom.iso
rate.currency_master_conv.iso
All the conventions aren't respected. You should use dominant_id and converted_id as forign keys, CurrencyRate and CurrencyMaster for class names and you shouldn't have a 's' whan you use belongs_to.
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})