How can you create a has_many association but also be able to refer to the various instances by a term that isn't the name? Sort of like an attribute of the class? (dog1 in this case.)
class User < ActiveRecord::Base
has_many :dogs
dogs: dog1, dog2, dog3, dog4 # I know this won't work but just to give the idea
end
So you can do:
barry = User.create(name: 'Barry')
barry.dog1.create(name: 'rover', weight: 12)
barry.dog1.name #=> rover
barry.dog1.weight #=> 12
There is no clean way to do this in Rails, a workaround is to use has_one:
class User
has_many :dogs
has_one :favorite_dog, class_name: 'Dog', -> { where(favorite: true).order(updated_at: :desc) }
But, the thing is, Rails' has_one is just a has_many with a SQL LIMIT 1. So in fact, the relation some_user.favorite_dog will return only one record even though there could be several "favorited dogs". The has_one :favorite_dog I used as an example should be latest_favorited_dog (because of the order on updated_at).
has_one documentation: https://apidock.com/rails/ActiveRecord/Associations/ClassMethods/has_one
Related
I'm using Rails 5.1. How do I write a finder method when there is a chain of "belongs_to" associations? I have the following models ...
class Plan < ApplicationRecord
...
has_many :plan_items, :dependent => :destroy
class PlanItem < ApplicationRecord
...
belongs_to :offer, :optional => false
class Offer < ApplicationRecord
belongs_to :package, :optional => false
class Package < ApplicationRecord
has_and_belongs_to_many :items
I want to write a finder that gets all Plans with an Item with id = "blah". But the below is failing ...
[19] pry(main)> Plan.joins(plan_items: :offer).joins(packages: :item).where(:item => {:id => "abac"}).count
ActiveRecord::ConfigurationError: Can't join 'Plan' to association named 'packages'; perhaps you misspelled it?
from /Users/davea/.rvm/gems/ruby-2.5.1/gems/activerecord-5.2.2.1/lib/active_record/associations/join_dependency.rb:188:in `find_reflection'
How do I write a finder when there is a chain of belongs_to associations?
First, maybe your table name is wrong. Second, to pass method between belong_to association, you can use delegate
I'm assuming PlanItem is a join table between Plan and Item (that would be inline with the Rails naming convention). This could be done neatly with through associations and scopes. I would do it like this...
class Plan < ApplicationRecord
has_many :plan_items, dependent: :destroy
has_many :items, through: :plan_items
scope :blah_items { items.id_of_blah }
class PlanItem < ApplicationRecord
belongs_to :offer, optional: false
belongs_to :item
class Item < ApplicationRecord
scope :id_of_blah { where(id: 'blah') }
Then you can call it like so... Plan.with_blah_items or if you had an active record collection of plans you could use the scope to narrow it down plans.with_blah_items.
Since ActiveRecord associations will return ActiveRecord relations, you can chain them with any other active record methods (e.g. Plan.first.items.where(item: { id: 'blah' }) Scopes just make it nice and neat. : )
If PlanItem is not a join table between Plan and Item, first thing you should do is rename it. This is not just a best practice, rails spends a lot of time assuming what things are named, and it could cause a bug. After you rename it you should create a join table between Plan and Item called PlanItem. If a join between these tables doesn't make sense with your application architecture, you could always string through associations together, but that would be a code smell.
If you didn't want to mess with scopes, you could always just do a query like this plan.items.where(items: { id: 'blah' }).
Hope that helps!
I've got two models with a has_many / has_many relationship. I have a variable exp_ids which is an array of integers representing the id's of some ExperienceLevel records. I need to write a query that will select all JobDescriptions that have an ExperienceLevel with one of those ids.
The query must work on an existing ActiveRelation object called job_descriptions, which is being passed through some flow controls in my controller to filter the results based on my params.
I've tried these queries below and some other variations, but with little success. As far as I can tell, ActiveRecord thinks that experience_levels is an attribute, which is causing it to fail.
job_descriptions.where(experience_levels: exp_ids)
job_descriptions.joins(:experience_levels).where(experience_levels.id: exp_ids)
job_descriptions.joins(:experience_levels).where(experience_levels: exp_ids)
job_descriptions.joins(:experience_levels).where("experience_levels.id IN exp_ids")
job_descriptions.includes(:experience_levels).where("experience_levels.id = ?", exp_ids).references(:experience_levels)
Here are my models:
class JobDescription < ActiveRecord::Base
has_many :job_description_experience_levels
has_many :experience_levels, through: :job_description_experience_levels
end
class JobDescriptionExperienceLevel < ActiveRecord::Base
belongs_to :job_description
belongs_to :experience_level
end
class ExperienceLevel < ActiveRecord::Base
has_many :job_description_experience_levels
has_many :job_descriptions, through: :job_description_experience_levels
end
I'm not sure if what I want to do is even possible. I've used a similar approach for another job_description filter where I selected the company_id, but in the case, company_id was an attribute of JobDescription.
Try this:
job_descriptions.joins(:job_description_experience_levels).where(job_description_experience_levels: { experience_level_id: exp_ids })
job_descriptions.joins(:experience_levels).where(experience_levels: {id: exp_ids})
Try this one. Note the lack of plural on the experience level.id
job_descriptions.includes(:experience_levels).where("experience_level.id = ?", exp_ids).references(:experience_levels)
I want to create a random pack of 15 cards which should be invoked in the cardpacks_controller on create. I have the following models:
Card:
class Card < ActiveRecord::Base
# relations
has_many :cardpacks, through: :cardpackcards
belongs_to :cardset
end
Cardpack:
class Cardpack < ActiveRecord::Base
#relations
has_many :cards, through: :cardpackcards
belongs_to :cardset
# accept attributes
accepts_nested_attributes_for :cards
end
Cardpackcards:
class Cardpackcard < ActiveRecord::Base
#relations
belongs_to :card
belongs_to :cardpack
end
Cardsets:
class Cardset < ActiveRecord::Base
#relations
has_many :cards
has_many :cardsets
end
How can I create 15 Cardpackcards records with random card_id values and with the same cardpack_id (so they belong to the same pack)
I have watched the complex form series tutorial but it gives me no comprehension as how to tackle this problem.
I hope anyone can help me solve this problem and give me more insight in the rails language.
Thanks,
Erik
Depending on the database system you might be able to use an order random clause to find 15 random records. For example, in Postgres:
Model.order("RANDOM()").limit(15)
Given the random models, you can add a before_create method that will setup the associations.
If the Cardpackcard model doesn't do anything but provide a matching between cards and cardpacks, you could use a has_and_belongs_to_many association instead, which would simplify things a bit.
Without it, the controller code might look something like this:
cardset = Cardset.find(params[:cardset_id])
cardpack = Cardpack.create(:cardset => cardset)
15.times do
cardpack.cardpackcards.create(:card => Card.create(:cardset => cardset))
end
I have the following setup:
class Publication < ActiveRecord::Base
has_and_belongs_to_many :authors, :class_name=>'Person', :join_table => 'authors_publications'
has_and_belongs_to_many :editors, :class_name=>'Person', :join_table => 'editors_publications'
end
class Person < ActiveRecord::Base
has_and_belongs_to_many :publications
end
With this setup I can do stuff like Publication.first.authors. But if I want to list all publications in which a person is involved Person.first.publications, an error about a missing join table people_publications it thrown. How could I fix that?
Should I maybe switch to separate models for authors and editors? It would however introduce some redundancy to the database, since a person can be an author of one publication and an editor of another.
The other end of your associations should probably be called something like authored_publications and edited_publications with an extra read-only publications accessor that returns the union of the two.
Otherwise, you'll run in to sticky situations if you try to do stuff like
person.publications << Publication.new
because you'll never know whether the person was an author or an editor. Not that this couldn't be solved differently by slightly changing your object model.
There's also hacks you can do in ActiveRecord to change the SQL queries or change the behavior of the association, but maybe just keep it simple?
I believe you should have another association on person model
class Person < ActiveRecord::Base
# I'm assuming you're using this names for your foreign keys
has_and_belongs_to_many :author_publications, :foreign_key => :author_id
has_and_belongs_to_many :editor_publications, :foreign_key => :editor_id
end
I have a many-to-many relationship set up through a join model. Essentially, I allow people to express interests in activities.
class Activity < ActiveRecord::Base
has_many :personal_interests
has_many :people, :through => :personal_interests
end
class Person < ActiveRecord::Base
has_many :personal_interests
has_many :activities, :through => :personal_interests
end
class PersonalInterest < ActiveRecord::Base
belongs_to :person
belongs_to :activity
end
I now want to find out: in which activities has a particular user not expressed interest? This must include activities that have other people interested as well as activities with exactly zero people interested.
A successful (but inefficent) method were two separate queries:
(Activity.all - this_person.interests).first
How can I neatly express this query in ActiveRecord? Is there a (reliable, well-kept) plugin that abstracts the queries?
I think the easiest way will be to just use an SQL where clause fragment via the :conditions parameter.
For example:
Activity.all(:conditions => ['not exists (select 1 from personal_interests where person_id = ? and activity_id = activities.id)', this_person.id])
Totally untested, and probably doesn't work exactly right, but you get the idea.