Why does create_association not work on a belongs_to association? - ruby-on-rails

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.

Related

activerecord shovel operator <<

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.

Rails assignment on a one-to-one relationship

Alright, so I'm trying to connect a Profile object to a Session object through a one to one relationship. The way I understand it is if I have the relationship set up properly, the following is equivalent (please correct me if I'm wrong)
#my_session << #my_profile
#my_session.profile = #my_profile
#my_session.profile_id = #my_profile.id
I have the following set up in my models folder
profile.rb:
class Profile < ActiveRecord::Base
has_one :session
session.rb:
class Session < ActiveRecord::Base
# I tried this without foreign_key also, it works the same
belongs_to :profile, :foreign_key => 'profile_id'
And in my database tables, session has profile_id in it
Doing the following two commands in my rails console works fine:
#my_session.profile = #my_profile
#my_session.profile_id = #my_profile.id
However, whenever I try to do the following:
#my_session << #my_profile
I get an error
NoMethodError: undefined method `<<' for #<Session:0x00000004a26198>
from /.../rubies/ruby-2.2.2/lib/ruby/gems/2.2.0/gems/activemodel-4.2.3/lib/active_model/attribute_methods.rb:433:in `method_missing'
Is this some sort of problem with how I have rails setup or something? Any help would be great. Thanks.
When declaring a has_one association on an ActiveRecord model it gains the following methods:
association(force_reload = false)
association=(associate)
build_association(attributes = {})
create_association(attributes = {})
create_association!(attributes = {})
Which does not include the shovel operator << thus your error that << is undefined in this case. It's not a configuration problem. Looks like your config is working fine. Here's the Rails guide with the specific details
http://guides.rubyonrails.org/association_basics.html#has-one-association-reference
The << shovel operator is defined along with all these other methods when you include a has_many or has_and_belongs_to_many on a model:
collection(force_reload = false)
collection<<(object, ...)
collection.delete(object, ...)
collection.destroy(object, ...)
collection=(objects)
collection_singular_ids
collection_singular_ids=(ids)
collection.clear
collection.empty?
collection.size
collection.find(...)
collection.where(...)
collection.exists?(...)
collection.build(attributes = {}, ...)
collection.create(attributes = {})
collection.create!(attributes = {})
Here's the details:
http://guides.rubyonrails.org/association_basics.html#has-many-association-reference
http://guides.rubyonrails.org/association_basics.html#has-and-belongs-to-many-association-reference

Mongoid destroy method not working with has_and_belongs_to_many relationship

I'm using Mongoid version 2.8.1. I've noticed that the destroy method does not work when the model has a has_and_belongs_to_many relationship.
For example, I have two models
class Article
include Mongoid::Document
has_and_belongs_to_many :subjects
...
end
and
class Subject
include Mongoid::Document
has_and_belongs_to_many :articles
...
end
Now I want to delete an article document. So I tried
a = Article.find('someid1234')
this returns a valid object, then I do
>> a.destroy
=> true
>> a.errors.any?
=> false
>> a.errors.count
=> 0
But when I do
a.reload
I still get the object back!
If I use
a.delete
instead, it would work, but delete doesn't run callbacks, and I want to run callbacks.
I have nailed this down to the has_and_belongs_to_many relationship. Because of this relationship, destroy invokes a callback method.
Article._destroy_callbacks
=> [#<ActiveSupport::Callbacks::Callback:0x007fc4a0e71258 #klass=Shortlist, #kind=:after, #chain=[...], #per_key={:if=>[], :unless=>[]}, #options={:if=>[], :unless=>[]}, #raw_filter=#<Proc:0x007fc4a0e714d8#/opt/boxen/rbenv/versions/1.9.3-p392/lib/ruby/gems/1.9.1/gems/mongoid-2.4.12/lib/mongoid/relations/synchronization.rb:160>, #filter="_callback_after_31(self)", #compiled_options="true", #callback_id=32>]
The method in question in /opt/boxen/rbenv/versions/1.9.3-p392/lib/ruby/gems/1.9.1/gems/mongoid-2.4.12/lib/mongoid/relations/synchronization.rb:160
is
def synced_destroy(metadata)
tap do
set_callback(
:destroy,
:after
) do |doc|
doc.remove_inverse_keys(metadata)
end
end
end
After I call a.destroy, a's id is removed from its subjects' article_ids array fields. But article a is not destroyed. So it seems the callback executes correctly, but the object is not destroyed afterwards.
I debugged this problem by looking at mongoid's source code. The destroy method looks like
def destroy(options = {})
self.flagged_for_destroy = true
run_callbacks(:destroy) do
remove(options) # THIS IS NOT EXECUTED!
end.tap do
self.flagged_for_destroy = false
end
end
def remove(options = {})
Operations.remove(self, options).persist
end
alias :delete :remove
The comment is mine. This seems to be a bug with Mongoid, that destroy only executes the callbacks, and does not destroy the object itself.
However, when there's no callback methods for destroy (for example, on a model without
the has_and_belongs_to_many relationship), the object is destroyed correctly. Strange
Has anyone has experienced the same problem and if there's any workaround.
Thanks.
It could be that :article must be referenced instead of Article. Keep in mind that doing Article.new doesn't automatically give a relation. The mongoid relation doc says you need something like the following
class Person
include Mongoid::Document
embeds_many :addresses
end
person.addresses = [ address ]
a work around is add manual deletes to the modals
after_destroy :delete_self!
def delete_self!
if persisted?
self.delete
end

Is there a "first_or_build" method on has_many associations?

In rails 3.2+, you can do this :
SomeModel.some_scope.first_or_initialize
Which means you can also do :
OtherModel.some_models.first_or_initialize
I find this pretty useful, but i'd like to have a first_or_build method on my has_many associations, which would act like first_or_initialize but also add a new record to the association as build does when needed.
update for clarification : yes, i know about first_or_initializeand first_or_create. Thing is, first_or_initializedoes not add the initialized record to the association's target as build does, and first_or_create... well... creates a record, which is not the intent.
I have a solution that works, using association extensions :
class OtherModel < ActiveRecord::Base
has_many :some_models do
def first_or_build( attributes = {}, options = {}, &block )
object = first_or_initialize( attributes, options, &block )
proxy_association.add_to_target( object ) if object.new_record?
object
end
end
end
I just wonder if :
built-in solutions to this problem already exist ?
my implementation has flaws i do not see ?
I'm not sure if there is anything built into rails that will do exactly what you want, but you could mimic the first_or_initialize code with a more concise extension than you are currently using, which I believe does what you want, and wrap it into a reusable extension such as the following. This is written with the Rails 3.2 extend format.
module FirstOrBuild
def first_or_build(attributes = nil, options = {}, &block)
first || build(attributes, options, &block)
end
end
class OtherModel < ActiveRecord::Base
has_many :some_models, :extend => FirstOrBuild
end

Reverse Polymorphic Association in Rails (Accessing the parent's attributes)

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.

Resources