I implemented an example of Reverse Polymorphism in Rails with the selected answer from this question:
Reverse Polymorphic Associations
With this we are able to do the following:
t = Article.new
t.article_elements # []
p = Picture.new
t.article_elements.create(:element => p)
t.article_elements # [<ArticleElement id: 1, article_id: 1, element_id: 1, element_type: "Picture", created_at: "2011-09-26 18:26:45", updated_at: "2011-09-26 18:26:45">]
t.pictures # [#<Picture id: 1, created_at: "2011-09-26 18:26:45", updated_at: "2011-09-26 18:26:45">]
I'm wondering if it's possible to modify this such that if I do
t.article_elements that I can also see the attributes for the picture to. So for example, if I had an picture_name attribute for the variable p, how can I access that from t.article_elements ? So basically, I am trying to access the parent's attributes from the child object.
Note that t.article_elements is a collection. I will use article_element to refer to one member of the collection.
Per your example,
article_element.element.picture_name
will work.
However, you run into a problem with undefined methods by mismatched attributes. For example, a video would not have a picture_name attribute. If all element types shared a common attribute, such as name, it would be fine.
One way to avoid this problem is to check whether the element responds to a given attribute method. For example:
# models/article_element.rb
def element_try(method)
self.element.respond_to?(method) ? self.element.send(method) : ""
end
If our element is a video and we call:
article_element.element_try(:picture_name) # => ""
we will get a blank string instead of NoMethodError.
This solution is a bit hacky, so use at your own risk. Personally, I'd use common attributes instead.
I had similar situation.
class User < ActiveRecord::Base
has_one :image, as: :imageable
end
class Person < ActiveRecord::Base
has_one :image, as: :imageable
end
class Image < ActiveRecord::Base
belongs_to :imageable, :polymorphic => true
end
When you wanna access parent of image, you can do Image.last.imageable and that will give you either User or Person object. It works same with has_many relations.
Related
I want to build up a model without storing it in the database, but with accepting nested attributes. It appears that passing parameters with nested attributes to .new does not accept them, and just creates a new model, without any of the passed associations.
Is there a way to have .new accept nested attributes, or another method I can use to accomplish this?
Thanks!
You're basing the entire question on a faulty premise.
class Company < ApplicationRecord
has_many :products
accepts_nested_attributes_for :products
end
class Product < ApplicationRecord
belongs_to :company
end
irb(main):002:0> c = Company.new(name: 'Acme', products_attributes: [ { name: 'Anvil' }])
=> #<Company:0x000056417471c2b0 id: nil, name: "Acme", created_at: nil, updated_at: nil>
irb(main):003:0> c.products
=> [#<Product:0x0000564174972258 id: nil, name: "Anvil", company_id: nil, created_at: nil, updated_at: nil>]
As you can see here the attributes are very much passed to .new - in fact thats exactly what the entire feature is supposed to do so it would be strange otherwise.
So whats actually going wrong? The most likely explaination is that you aren't whitelisting the parameters for the nested records. Make sure that you're using the correct pluralization - the parameter key is singular_attributes for belongs_to / has_one and plural_attributes for has_many/has_and_belongs_to_many.
I have a model connecting to a Postgres db.
class Person < ApplicationRecord
def say_id
"#{name} has id: #{id}"
end
end
I have some attributes id,name,email as well as the method above: say_id that can be accessed via:
person = Person.new
person.id => 1
person.say_id => "John has id: 1"
I would like to have the method 'say_id' listed as an attribute as well, now when running person.attributes, I'm only seeing: id, name, email
How can I have my method included as a listable information in full, as with person.attributes but which will include my method? A usecase would be for lazily just laying out all these fields in a table of the Person-object.
In Rails 5+ you can use the attributes api to create attributes that are not backed by a database column:
class Person < ApplicationRecord
attribute :foo
end
irb(main):002:0> Person.new.attributes
=> {"id"=>nil, "email"=>nil, "name"=>nil, "created_at"=>nil, "updated_at"=>nil, "foo"=>nil}
Unlike if you used attr_accessor these actually behave very much like database backed attributes.
You can then override the getter method if you wanted to:
class Person < ApplicationRecord
attribute :foo
def foo
"foo is #{super}"
end
end
irb(main):005:0> Person.new(foo: 'bar').foo
=> "foo is bar"
But for whatever you're doing its still not the right answer. You can get a list of the methods of an class by calling .instance_methods on a class:
irb(main):007:0> Person.instance_methods(false)
=> [:foo]
Passing false filters out inherited methods.
From inside edit action in the contacts controller have ...
#programs << #contact.program
Which produces the following error:
NoMethodError - undefined method `<<' for Program::ActiveRecord_Relation
Contacts Model:
belongs_to :program
Program Model:
has_many :contacts
validates :name, presence: true, uniqueness: true
#programs.class
Program::ActiveRecord_Relation
#contact.program.class
Program(id: integer, name: string, active: boolean, created_at: datetime, updated_at: datetime)
Question: Why does this operation fail? Why can't the record get added to the record collection. What is preventing the collection(ActiveRecord_Relation) from adding the record?
You're contradicting yourself here:
Program has_many contacts vs Programs << Contact.program
If you're trying to add a Contact to a particular program, you would be looking at adding the contact:
program.contacts << contact
And if you're trying to set the program for the contact:
contact.program = program
What does not make sense, however, is to try to add something to “programs”, which isn't a relationship. Nothing in this system as you've defined it has_many :programs, so #programs.<< cannot possibly act on a relationship.
You're receiving this error because the ActiveRecord::Relation class is only a collection of results returned by an ActiveRecord query. You probably got it by running Program.where or a similar query. It is not an ActiveRecord::Association and therefore you cannot add more records to it.
You must instead use the association returned by the parent object.
Here's an example of what you're doing, vs what you should be doing:
class User < ApplicationRecord
has_many :programs
end
class Program < ApplicationRecord
belongs_to :user
end
new_program = Program.new
# What you're attempting.
programs_where = Program.where(user_id: User.first) # Class is Program::ActiveRecord_Relation
programs_where << new_program # Throws Error b/c << is not available on ActiveRecord::Relation objects.
# What you should be attempting.
user = User.first
programs_assoc = user.programs # Returns Programs::ActiveRecord_Associations_CollectionProxy
programs_assoc << new_program # Returns Correctly
Note: It's not clear how #programs is defined. Is this answer does not work for you then please provide the complete controller code, as well as the other model you're using.
I have this association setup:
class Connection < ActiveRecord::Base
belongs_to :membership
end
class Membership < ActiveRecord::Base
has_many :connections, dependent: :destroy
end
But, when I try to do the following:
#membership ||= Membership.find_or_create_by(member: #member)
#connection ||= #membership.create_connection!(sent_at: Time.now, times_sent: 1, request_status: 0)
I get this error:
undefined method `create_connection!' for #<Membership:0x007fab2dac83b8>
Despite the fact that the Rails API docs say I should be able to do this:
belongs_to(name, scope = nil, options = {}) Link
Methods will be added for retrieval and query for a single associated object, for which this object holds an id:
association is a placeholder for the symbol passed as the name argument, so belongs_to :author would add among others author.nil?.
create_association(attributes = {})
Returns a new object of the associated type that has been instantiated with attributes, linked to this object through a foreign key, and that has already been saved (if it passed the validation).
create_association!(attributes = {})
Does the same as create_association, but raises ActiveRecord::RecordInvalid if the record is invalid.
What could be causing this?
I think you have your relations mixed up a bit. You've linked the docs for belongs_to. Since connection belongs to membership, you'd be able to
#membership ||= #connection.create_membership(attr1: val1, attr2: val2)
However, a membership does not belong to a connection. A membership has many connections. Scrolling down to has_many, you can see that to build an association, you need to use:
collection.create(attributes = {}, …)
Applying that to your use case, you'd get
#connection || = #member.connections.create(sent_at: Time.now, times_sent: 1, request_status: 0)
I figured it out, it seems the Rails 4 docs are out of date.
What I should be doing is:
#membership.connections.create!(sent_at: Time.now, times_sent: 1, request_status: 0)
This worked for me.
This is best explained by example. The following is simple to do:
class Foo < ActiveRecord::Base
has_many :bars
end
1a>> foo = Foo.new
=> #<Foo id: nil>
2a>> foo.bars << Bar.new
=> [#<Bar id: nil, foo_id: nil>]
3a>> foo.bars
=> [#<Bar id: nil, foo_id: nil>]
However, I want all Foo objects initialized with a Bar without having to explicitly run line 2:
class Foo < ActiveRecord::Base
has_many :bars
# [...] Some code here
end
1b>> foo = Foo.new
=> #<Foo id: nil>
2b>> foo.bars
=> [#<Bar id: nil, foo_id: nil>]
Is this possible? Ideally the 'default' object would still be associated in the same way as if I'd explicitly run line 2a, so that it gets saved when the parent Foo object is saved.
Let me preface this by saying that from a design perspective, this probably isn't the greatest idea. Assuming your code is much more complicated than Foo and Bar, you might be headed toward something more along the lines of a builder.
However, if you insist on doing what you ask, this will do it.
class Foo < ActiveRecord::Base
has_many :bars
def after_initialize
self.bars << Bar.new if self.new_record?
end
end
by concept, this isn't the cleanest way to do something like this, because you're mixing a model logic inside another one.
there's more: you haven't yet any instance during initialization, so I think this is not possible.
keep in mind that if you need to save associated objects, you can do it in controllers or using Nested Models Forms
what's the task you need to achieve?