Rails assignment on a one-to-one relationship - ruby-on-rails

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

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.

Why does create_association not work on a belongs_to association?

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.

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

Reassigning an ActiveRecord instance and corresponding foreign keys

In Rails/ActiveReocrd is there a way to replace one instance with another such that all the relations/foreign keys get resolved.
I could imagine something like this:
//setup
customer1 = Customer.find(1)
customer2 = Customer.find(2)
//this would be cool
customer1.replace_with(customer2)
supposing customer1 was badly configured and someone had gone and created customer2, not knowing about customer1 it would be nice to be able to quickly set everything to customer 2
So, also this would need to update any foreign keys as well
User belongs_to :customer
Website belongs_to :customer
then any Users/Websites with a foreign key customer_id = 1 would automatically get set to 2 by this 'replace_with' method
Does such a thing exist?
[I can imagine a hack involving Customer.reflect_on_all_associations(:has_many) etc]
Cheers,
J
Something like this could work, although there may be a more proper way:
Updated: Corrected a few errors in the associations example.
class MyModel < ActiveRecord::Base
...
# if needed, force logout / expire session in controller beforehand.
def replace_with (another_record)
# handles attributes and belongs_to associations
attribute_hash = another_record.attributes
attribute_hash.delete('id')
self.update_attributes!(attribute_hash)
### Begin association example, not complete.
# generic way of finding model constants
find_model_proc = Proc.new{ |x| x.to_s.singularize.camelize.constantize }
model_constant = find_model_proc.call(self.class.name)
# handle :has_one, :has_many associations
have_ones = model_constant.reflect_on_all_associations(:has_one).find_all{|i| !i.options.include?(:through)}
have_manys = model_constant.reflect_on_all_associations(:has_many).find_all{|i| !i.options.include?(:through)}
update_assoc_proc = Proc.new do |assoc, associated_record, id|
primary_key = assoc.primary_key_name.to_sym
attribs = associated_record.attributes
attribs[primary_key] = self.id
associated_record.update_attributes!(attribs)
end
have_ones.each do |assoc|
associated_record = self.send(assoc.name)
unless associated_record.nil?
update_assoc_proc.call(assoc, associated_record, self.id)
end
end
have_manys.each do |assoc|
associated_records = self.send(assoc.name)
associated_records.each do |associated_record|
update_assoc_proc.call(assoc, associated_record, self.id)
end
end
### End association example, not complete.
# and if desired..
# do not call :destroy if you have any associations set with :dependents => :destroy
another_record.destroy
end
...
end
I've included an example for how you could handle some associations, but overall this can become tricky.

Proxy Objects with ActiveRecord models - method_missing not working sometimes

I've been using a model of my application as a proxy to other objects that define behavior.
class Box < ActiveRecord::Base
belongs_to :box_behavior, :polymorphic => true, :validate => true, :foreign_key => 'box_behavior_id', :dependent => :destroy
[...]
def initialize(opts = {})
super(opts)
self.box_behavior = BoxBehaviorDefault.new if self.box_behavior.blank?
end
private
def method_missing(method, *args, &block)
super
rescue NoMethodError
return self.box_behavior.send(method,*args,&block)
end
end
So I implement all the methods on my BoxBehavior objects, and when I call a method on a box instance then it redirects the call to the associated boxbehavior object. It all works fine except when i tried to create a hook on my purchase model where it gets the total from its box object and saves it:
class Purchase < ActiveRecord::Base
belongs_to :box
before_validation_on_create { |r| r.total = r.box.total }
end
When I try to save any purchase object that has a box associated, I get this error:
undefined method `total' for #<ActiveRecord::Associations::BelongsToAssociation:0x7fe944320390>
And I don't have a clue on what to do next... When I implement the total method directly in the box class then it works fine... what can I do to solve this? Isn't the proxy working properly?
I found out that Rails doesn't always use initialize to create a new instance of a model. So i used the hook after_initialize and solved the problem!

Resources