Why create_* method doesn't exist for has_many relationships? - ruby-on-rails

I have the following models:
class Post < ApplicationRecord
has_many :metrics
end
class Metric < ApplicationRecord
belongs_to :post
end
I would like to know why there isn't a create_metrics method in a post instance. If the relationship was:
class Post < ApplicationRecord
has_one :metric
end
class Metric < ApplicationRecord
belongs_to :post
end
There would be a method create_metric in a post instance.

There is a collection.create method specified here: https://guides.rubyonrails.org/association_basics.html#methods-added-by-has-many-collection-create-attributes
In this particular case, you can call:
#metric.posts.create
And pass an array of objects with the data you want.

If you flip this question on its end you can see that for belongs_to and has_one you have the build_other and create_other methods since the association is nil by default.
So if you called thing.other.create you're calling .create on nil. Not good. While you could get around it by creating some kind of proxy object that would break code that relies on it being nil.
has_many and has_and_belongs_to_many don't have this problem since an empty association is an AssociationProxy object. You can think of this as kind of like an empty array. And even empty arrays have methods.
Its far more natural to call foo.bars.new or foo.bars.create than some metaprogramming method. And much easier to find the correct documentation.

Not sure why the convenience methods aren't there, but there is definitely a post.metrics.create(...) method. Most of these dynamic methods are on the collection itself, not the original model. See the official Rails ActiveRecord Associations Guide for more details.

Related

has_one - how to assign_id?

There is a cool feature for has_many in Rails. I can write
class Article < AR::Base
has_many :comments
has_one :another_association
and voila! method comment_ids= created, which I can use in strong parameters and mass assignment. Somethind like #article.comment_ids = [1,2,3]
I need something similar for has_one, like #article.another_association_id = 1. But I get NoMethodError exception. Is there any way to make this method works?
Has one has a different syntax.
#article.build_another_association
#article.create_another_association
See the guides for more info
Use attr_accessible
Specifies a white list of model attributes that can be set via mass-assignment
So you can add it like this assuming you created and ran the migration already:
class Article < AR::Base
has_many :comments
has_one :another_association
attr_accessible :another_association_id
But if it's Rails 4 you may need to handle it in the controller.
You have the direction of the association reversed.
With has_one, the other class should have an article_id to refer to a record in your articles table.
If you want articles.another_association_id, then you should specify belongs_to :another_association.
This method should only be used if the other class contains the foreign key. If the current class contains the foreign key, then you should use belongs_to instead.
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_one
If you want to simulate the way has_many works you could try this:
class Article < AR::Base
has_one :page
def page_id=(int_id)
self.page = Page.find(int_id)
end
end
#article.page_id = 3

Ruby on Rails - Get object association values with array of symbols

For example, have the models:
class Activity < ActiveRecord::Base
belongs_to :event
end
class Event < ActiveRecord::Base
has_many :activities
attr_accessible :foo
end
I can get the activity's event foo by using activity.event.foo (simple enough).
But I want to make a generic function that finds out first if an object has a belongs_to association and then get that object's foo through the belongs_to association (pretend that all objects have a foo through the belongs_to association)?
So far I have the following with gives me a reflection:
def get_foo(object)
object.class.reflect_on_all_associations(:belongs_to).each do |belongs_to|
return object.??????
end
end
I can either get an array of the reflection's class name via belongs_to.klass (e.g. [Event]) or an array of symbols for the belongs_to association via belongs_to.name (e.g. [:event]).
How do I get the object's belongs_to's foo given what I get from the reflection?
Is there an easier way to do this without using the reflection?
I'm hoping this is something simple and I'm just spacing out on how to solve this. I also hope I am being somewhat clear. This is my first Stack Overflow question.
You can do this but its not exactly pretty:
def get_foo(object)
object.class.reflect_on_all_associations(:belongs_to).map do |reflection|
object.send(reflection.name).try(:foo)
end
end
That will give you an array of all the foos associated with the object. You can change it to not do map and do a first or something.

Struggles with has_many and belongs_to association when relationships are 2-3 deep

I'm pretty new to Rails and setting up associations, so I suspect I'm missing something pretty obvious. I'm trying to set up an app where one model has two models that it has_many of. The second model belongs_to the first and has_many of the third. And the third can either belong to the first or the second model.
Specifically, I have a wall model that holds pictures and collages. The wall can hold either pictures or collages or neither. Collages can hold pictures.
class Wall < ActiveRecord::Base
belongs_to :user
has_many :collages
has_many :pictures
end
class Collage < ActiveRecord::Base
belongs_to :user
belongs_to :wall
has_many :pictures
end
class Picture < ActiveRecord::Base
belongs_to :user
belongs_to :wall
belongs_to :collage
end
The error I'm getting is telling me:
undefined method `picture?' for #Wall
Is there something I'm doing wrong with the associations I'm creating?
has_many association on any model gives plural form of that method
Therefore Wall class has method #pictures available by this line:
If you want #picture method to be available you should use association as belongs_to
We can debug more into the exact problem if you tell where actually you are getting this error and what is your feature to implement.
Also name for Picture class should be with capital P
#cvibha's answer should help you with the associations
However, there's another problem you may need to consider. You're calling this method:
undefined method `picture?' for #Wall
Rails associations basically create a record as per how you define the association (has_many :pictures creates #wall.pictures). However, you're calling picture?
--
If you've got a custom method called picture?, this should work (albeit without the association working - as described in the other answer). The problem you have is I don't think you've defined picture?
I would do this:
#app/models/wall.rb
Class Wall < ActiveRecord::Base
...
def picture?
#your code
end
end
Alternatively, if you're looking to validate the existence of a picture, you may wish to use in your view:
#wall.pictures.any?
#wall.pictures.first.present?

Explanation of proxy_association to find unassociated records

I came across some magic today and I am hoping for some help in understanding it so I can write informed code.
In my app, I have three classes:
class Person < ActiveRecord::Base
has_many :selected_apps
has_many :app_profiles, through: :selected_apps do
def unselected(reload=false)
#unselected_app_profiles = nil if reload
#unselected_app_profiles ||= proxy_association.owner.app_profile_ids.empty? ?
AppProfile.all :
AppProfile.where("id NOT IN (?)", proxy_association.owner.app_profile_ids)
end
end
end
class AppProfile < ActiveRecord::Base
end
class SelectedApp < ActiveRecord::Base
belongs_to :person
belongs_to :app_profile
end
The above code lets me do person.app_profiles.unselected and get back all of the AppProfiles that are not currently associated with the Person without having to do a lot of SQL work. Brilliant!
My problem is that I don't understand the code - which always leaves me feeling unsettled. I tried trolling through the proxy_association documentation, but it was fairly opaque.
Can anyone provide a reasonably straight-forward explanation and/or a good place to learn more?
Basically, when you call self while extending an association it won't return an Association instance, but instead delegates to to_a.
Try it:
class Person < ActiveRecord::Base
has_many :app_profiles, through: :selected_apps do
def test_me
self
end
end
end
Sometimes we need to get to the actual association object while were extending the association itself. Enter the proxy_association method, which will give us the association which contains the owner, target, and reflection attributes.
For reference here is the documentation.
This question provides a simpler use case of proxy_association.owner.

Find the associations for an ActiveRecord class at run-time?

I would like to find the assocations of an ActiveRecord class at runtime...
Let's assume I have the following:
class Person < ActiveRecord::Base
has_many :chairs
has_many :pens
end
class Chair < ActiveRecord::Base
belongs_to :person
end
class Pen < ActiveRecord::Base
belongs_to :person
end
How can I find out at runtime that Person "has many" Chairs and Pens, and vice versa? I'm looking for a method that would return an array of strings (if such a method exists). i.e.
Person.has_many_assocations
would return:
["chairs", "pens"]
and
Pen.belongs_to_associations
would return:
["person"]
Am I missing a method like this that exists??
Thanks for your help.
I think the ActiveRecord::Reflection class may be what you're looking for. From the documentation:
Account.reflect_on_all_associations # returns an array of all associations
Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
Sounds like a pretty silly thing to do run-time. What exactly are you trying to achieve? I assume that there are a simple and more commonly used solution to whatever your problem is.
If I had to, I'd use TheModel.read_inheritable_attribute(:reflections).

Resources