Assuming
class Kid < ActiveRecord::Base
has_one :friend
end
class Friend< ActiveRecord::Base
belongs_to :kid
end
How can I change this to
class Kid < ActiveRecord::Base
has_many :friends
end
class Friend< ActiveRecord::Base
belongs_to :kid
end
Will appreciate your insight...
Collection
The bottom line is that if you change your association to a has_many :x relationship, it creates a collection of the associative data; rather than a single object as with the single association
The difference here has no bearing on its implementation, but a lot of implications for how you use the association throughout your application. I'll explain both
Fix
Firstly, you are correct in that you can just change your has_one :friend to has_many :friends. You need to be careful to understand why this works:
ActiveRecord associations work by associating something called foreign_keys within your datatables. These are column references to the "primary key" (ID) of your parent class, allowing Rails / ActiveRecord to associate them
As long as you maintain the foreign_keys for all your Friend objects, you'll get the system working no problem.
--
Data
To expand on this idea, you must remember that as you create a has_many association, Rails / ActiveRecord is going to be pulling many records each time you reference the association.
This means that if you call #kind.friends, you will no longer receive a single object back. You'll receive all the objects from the datatable - which means you'll have to call a .each loop to manipulate / display them:
#kid = Kid.find 1
#kid.friends.each do |friend|
friend.name
end
If after doing this changes you have problem calling the save method on the order.save telling you that it already exists, and it not allowing you to actually have many order records for one customer you might need to call orders.save(:validate=> false)
You have answered the question. Just change it in model as you've shown.
Related
I have the following models
class Widget
has_one :widget_sprocket
has_one :sprocket, through: :widget_sprockets
end
class Sprocket
has_many :widget_sprockets
has_many :widgets, though: :widget_sprockets
end
class WidgetSprocket
belongs_to :widget
belongs_to :sprocket
end
This works fine in the console, but I'm struggling with view updates for Widget. has_many :through gives Sprocket widget_ids, which I believe can be treated like a local attribute for most purposes, but the Rails docs evidently expect a different table configuration for has_one :through and therefore doesn't define a sprocket_id on Widget. As a result code like this throws an unknown attribute error
<%= f.collection_select(:sprocket_id, Sprocket.all, :id, :sprocket_type) %>
Of course I could use has_many :through for both models, but I consider it a last resort.
I think you're falling for a classic trap and overcomplicating this. If you want a one to many assocation between Sprocket and Widget you should just be using belongs_to and adding a sprocket_id foreign key column to the widgets table:
class AddSprocketToWidgets < ActiveRecord::Migration[6.1]
def change
add_reference :widgets, :sprocket, null: false, foreign_key: true
end
end
class Widget
belongs_to :sprocket
end
This guarentees on the database level that a Widget can only have one Sprocket because the column can only hold one single value. Your join table gives no such guarentee. You're really just selecting the first matching row off the join table and its actually a many to many relation. Unless thats acceptable or you prevent it with unique indexes thats an invitation for some nasty bugs.
While there are scenarios where you actually need an intermediadary table that describes a one to many relation - YAGNI.
has_many :through gives Sprocket widget_ids, which I believe can be treated like a local attribute for most purposes
Its not an attribute in any way or form. Its a method which will actually do a SELECT id FROM other_tablequery unless the assocation is preloaded.
but the Rails docs evidently expect a different table configuration for has_one :through and therefore doesn't define a sprocket_id on Widget.
Classic noob trap caused by the confusing semantics of the method names. has_one means there is a foreign key column on the other models table. Its like has_many but with a LIMIT 1 tacked onto the end of the query. To get the id you would actually call other.id.
In the case of belongs_to its not the relations macro that creates the attribute. Its having an actual sprocket_id column on the widgets table.
If you actually wanted to go though creating an intermediary table you can't just assign an id. You would have to use nested attributes and fields_for to create or update a WidgetSprocket instance. Again YAGNI.
I'm practicing ruby on rails and I'm trying to add achievements to users. I was wondering if anyone could explain the benefits/detriments to using a hash over a new class.
For example, should my object have a "has_many" relationship with an "achievement" object, or should there be a "achievement" hash and why? I'm mostly concerned with database speed implications.
should my object have a has_many relationship with an "achievement" object, or should there be a "achievement" hash and why?
I'd most certainly recommend a has_many relationship based on database backends - it gives you the ability to build the associative data as you require (instead of messing around with custom methods).
You must also realize that ActiveRecord will build a hash for you, right?
The only difference is that ActiveRecord will populate the hash with data from your db, whilst I believe you'll be talking about a hash of static data (which I've not massive experience with).
--
Since this question doesn't have many answers, this is how I'd do it:
#app/models/achievement.rb
class Achievement < ActiveRecord::Base
## you could attach this to MongoDB or some other file-based storage system
has_many :awards
has_many :users, through : :awards
end
#app/models/award.rb
class Award < ActiveRecord::Base
belongs_to :user
belongs_to :achievement
end
#app/models/user.rb
class User < ActiveRecord::Base
has_many :awards
has_many :achievements, through: :award
end
This is a standard has_many :through relationship. I imagine you already know about it, so I'll not bore you with details; however, you have to remember that this type of setup is a standard in Rails -- it will give you the functionality you want without any of the customization your ideas will probably require.
It will give you the ability to call the following:
#awards = Award.joins(:achievements).where(user_id: #current_user.id)
This will take one DB call to bring back all the achievements for a single user, all related.
The data will be encapsulated in classes -- and will basically be a series of hashes, populated from the db.
The Achievement model could easily be converted to use static data.
TL;DR: How do I use the ID of the respective parent object in a has_many SQL clause to find child objects?
Long version:
I have the following example code:
class Person < AR::Base
has_many :purchases, -> {
"SELECT * from purchases
WHERE purchase.seller_id = #{id}
OR purchase.buyer_id = #{id}"
}
This was migrated from Rails 3 which worked and looked like
has_many :purchases, :finder_sql => proc { #same SQL as above# }
I want to find all purchases associated with a Person object in one association, no matter whether the person was the one selling the object or buying it.
Update: I corrected the SQL, it was inside out. Sorry! Also: The association only needs to be read-only: I am never going to create records using this association, so using id twice should be OK. But I do want to be able to chain other scopes on it, e.g. #person.purchases.paid.last_5, so creating the sum of two associations in a method does not work (at least it didn't in Rails 3) since it doesn't return an AM::Relation but a simple Array.
When using this above definition in Rails 4.2, I get
> Person.first.purchases
undefined method `id' for #<Person::ActiveRecord_Relation:0x...>
The error is clear, but then how do I solve the problem?
Since this is only an example for much more complicated SQL code being used to express has_many relationships (e.g. multiple JOINS with subselects for performance), the question is:
How do I use the ID of the parent object in a has_many SQL clause?
I don't think your code will work at all. You are defining an association with two foreign keys ... that'd mean that in case you want to create a new Person from a present Purchase, what foreign key is to be used, seller_id or buyer_id? That just don't make sense.
In any case, the error you are getting is clear: you are calling a variable id which is not initialized in the block context of the SQL code.
A better approach to the problem I understand from your question would be to use associations in the following way, and then define a method that gives you all the persons, both buyers and sellers that a product has. Something like this:
class Purchase < ActiveRecord::Base
belongs_to :buyer, class_name: 'Person'
belongs_to :seller, class_name: 'Person'
def persons
ids = (buyer_ids + seller_ids).uniq
Person.where(ids: id)
end
end
class Person < ActiveRecord::Base
has_many :sold_purchases, class_name: 'Purchase', foreign_key: 'buyer_id'
has_many :buyed_purchases, class_name: 'Purchase', foreign_key: 'seller_id'
end
Im my approach, buyer_id and seller_id are purchase's attributes, not person's.
I may have not understood correctly, in that case please clarify.
I was interested in creating a model that could stand alone but could also belong to another model. So for example: I have an Artist that has many Albums. In addition to having many tracks (which is irrelevant for this case) it also can have many Singles. Here's the catch. There are some instances where a single doesn't belong to an album and is just used for promo (aka a promo single). So I thought I'd approach it using a polymorphic association:
class Artist < ActiveRecord::Base
has_many :albums
has_many :singles, as: :singleable
end
class Album < ActiveRecord::Base
belongs_to :artist
has_many :singles, as: :singleable
end
class Single < ActiveRecord::Base
belongs_to :singleable, polymorphic: true
end
Not being entirely familiar with polymorphic associations I didn't know if this would be the correct way to setup what I had in mind of doing. Alternatively should I have created an entirely separate model called PromoSingle or create a dropdown that would define the single type?
I don't think this case actually needs a polymorphic association. It should work, yes, but you'll soon find yourself in situations when you need to perform complicated searches to get seemingly simple results.
Polymorphic association should be used when association is semantically the same, but may involve different objects. It's not the case here, an artist is the one who created the track. But the album isn't.
This can cause trouble at least if you ever decide to fetch specific artist's tracks. Here's what ActiveRecord would have to do with your structure (including internal operations):
Get list L1
Get an array A of album_ids, whose artist_id is X (a parameter)
Get all singles, whose singleable_type is "Album" and singleable_id is in the array of albums A, fetched before.
Get list L2
Get all singles, whose singleable_type is "Artist" and singleable_id is X.
Concatenate L1 and L2
Here is what I suggest you do.
class Artist < ActiveRecord::Base
has_many :albums
has_many :singles
end
class Album < ActiveRecord::Base
belongs_to :artist
has_many :singles
end
class Single < ActiveRecord::Base
belongs_to :artist
belongs_to :album
end
PromoSingles fit well here too. Just because association is defined doesn't mean it should be present: it only means "it might be present, there is a place where we can put it".
Should you absolutely need it to be present (not here, somewhere else), you'd use a validation to ensure.
Otherwise, you may have items that don't belong to anyone, or, technically, belong to nil (Ruby level) or NULL (DB level). It's not bad if it makes sense.
In my Rails app I have a has_many_through relation. I want to use the join model/table to store some data about the relation (how many times that particular relation is used, to be specific).
I'm writing an add-method on one of my classes which should check for any existing relation with the subject, update a counter on the relation if it exists or create it if it doesn't exist.
Example:
CoffeeDrinker is related to Coffee through Cup. Every time CoffeeDrinker takes a sip a counter on that particular Cup should be incremented. The first time CoffeeDrinker takes a sip, the Cup should be created and the counter initialized.
What is the easiest and/or most correct way to get a hold on the relation object?
Either I'm not understanding your question or you're going to be surprised how obvious this is.
You will have defined the relationship as so:
#coffee_drinker.rb
has_many :cups
has_many :coffees, :through => :cup
#cup.rb
belongs_to :coffee
belongs_to :coffee_drinker
#coffee.rb
has_many :cups
has_many :coffee_drinkers, :through => :cup
coffee_drinker.cups
coffee_drinker.coffees
coffee.cups
coffee.coffee_drinkers
#coffee_drinker.rb
after_save :update_cups_drunk
def update_cups_drunk
cups.find_by_coffee_id(coffee_id).increment!(:count)
end
#maybe you don't have access to the coffee_id
def update_cups_drunk
cups.find_by_coffee_id(coffees.last.id).increment!(:count)
end