Controller Chokes on Nested Model's Attributes - ruby-on-rails

I'm using Rails 4 + Ruby 2
Nested models in Rails are a huge pain. There, I said it.
Okay, so I have an entry model with approaches nested inside.
# --- entry.rb ---
has_many :approaches, :dependent => :destroy
accepts_nested_attributes_for :approaches, :reject_if => lambda { |a| a[:approach_type].blank? }, :allow_destroy => true
The approach parameters come over to the controller's create method like this (from the log):
{"utf8"=>"✓",
"entry"=>{
"aircraft_registration"=>"N384HA",
"flight_date"=>"2013-12-10",
"departure"=>"KSAD",
...
"approaches"=>{
"1386633324306"=>{
"approach_type"=>"GLS",
"holding"=>"false",
"quantity"=>"2",
"airport"=>"FFS",
"runway"=>"12L",
"updated_flag"=>"true"
},
"1386633813852"=>{
"approach_type"=>"TACAN",
"holding"=>"false",
"quantity"=>"1",
"airport"=>"DFD",
"runway"=>"12L",
"updated_flag"=>"true"
}
},
}
For testing purposes, I do blanket param allowance:
params.require(:entry).permit!
...and I get this error from Satan himself:
ActiveRecord::AssociationTypeMismatch - Approach(#70114475640640) expected,
got Array(#70114477494560)
It seems that the problem is that the Entry model doesn't like the approaches being an array (which actually looks like a hash with id numbers, but what do I know).
My Question
How should the params look on a nested model when they come over from the view to the controller?
I'm trying to narrow down whether I have mis-formatted data coming from my form, or a problem in my controller.
I'm new to Rails, so please be gentle. :)

To answer your question, a nested model should come through something like this:
params = {
:entry => {
:approaches_attributes => [
{:approach_one_attr => ...},
{:approach_two_attr => ...}
]
}
}
Looks to me like the problem here is with the form in your view not the controller.

Related

Issue including a second and a third level association

This query is not working, pease help. I'm trying to include a second and a third deep-level of association.
Pedido > has_one(products_pedido) > has_one(product_size)
#pedidos = Pedido.includes(:pedidos_payments, :products_pedidos => { :product_size } , :estado, :brand, :customer ).where(:is_quote => false)
Ps: I know products_pedido is mispelled according to ActiveRecord good practices :).
Without a stacktrace here's what I suggest:
Assuming your has_one method name is products_pedidos, your issue looks like a problem with your hash syntax.
Your syntax creates a hash with key products_pedidos that returns a hash without a value. This is probably where the error is occurring.
#pedidos = Pedido.includes(:products_pedidos => { :product_size })
What you likely want is this which returns a hash with key products_pedidos with value product_size
#pedidos = Pedido.includes({products_pedidos: :product_size })
The Entire query might look like:
#pedidos = Pedido.includes(
:pedidos_payments,
{products_pedidos :product_size},
:estado,
:brand,
:customer
).where(is_quote: false)
Here's a great post explaining a bit more about ActiveRecord nested relationship loading: Rails - Nested includes on Active Records?. I'd also suggest fixing the naming on products_pedido to follow good naming practices.

How do I use except on a nested hash in a reject

I'm trying to reject an empty form entry, but I'm having difficulties.
Users can either choose for an existing location, or create a new one. I want the form to actually show empty fields, but reject them when they are all empty. As '_destroy' will never be empty, I need to make an exception. Also if only the quantity is filled out, the entry can be rejected.
The form submits the following information:
Parameters:
{"product"=>
{...,
"product_locations_attributes"=>
{
"0"=>{"location_attributes"=>{"_destroy"=>"false", "street"=>"", "number"=>"", "zipcode"=>"", "city"=>"", "country"=>""}, "quantity"=>""},
"1"=>{"_destroy"=>"false", "location_id"=>"", "quantity"=>""}}
}
, "commit"=>"Create Product"
}
AI'm trying to get the empty location removed in the Product model as follows:
accepts_nested_attributes_for :product_locations, :allow_destroy => true,
:reject_if => proc {|a| a.except('_destroy', 'quantity').values.all?( &:blank? )}
As it is nested, it doesn't work like this.
So how can I check if everything is blank except the quantity and _destroy?
It should be possible to do it in one go right?
Thanks for any help.
* updated to make it more clear *
I would explicitly check all the fields that may or may not be blank, rather than try and do some kind of "are they all blank". Much more explicit and readable.
:reject_if => proc {|a|
location_attributes = a[:location_attributes]
a[:street].blank? && a[:number].blank? && a[:location_id].blank?
}
It will be verbose but better in the long run!
Thanks to the anser of #RobHeaton I finally was able to make this work. Myabe his answer could work, but it did not work for me. If it should and I did something wrong, let me know, and I will accept his answer. I finally got it to work with the following code:
accepts_nested_attributes_for :product_locations, :allow_destroy => true, :reject_if => :empty_fields
def empty_fields(a)
if la = a[:location_attributes]
la[:street].blank? && la[:number].blank? && la[:city].blank? && la[:zipcode].blank? && la[:country].blank?
else
a[:location_id].blank?
end
end
It is now very clear on what needs to be blank in order for it to reject. With other things I tried I ended up with either too many things accepted or rejected.
Just writing this down in case other people run into the same problem.

Nested attributes in hash representation of a record

I've given up hours of my day trying to accomplish this simple thing in Rails 3.1 with no luck. I've got some models nested 2 levels deep and associated many-to-one with belongs_to/foreign key, like:
TopLevelModel:
MiddleLevelModel:
BottomLevelModel
I am eagerly loading the whole hierarchy in my queries like so:
#model = TopLevelModel.find(1, :include => {:middle_level_children => :bottom_level_children})
The JSON serializer works fine for serializing the nested hierarchy (using the :include option), but this isn't enough for my purposes and I need a (ruby) hash representation of the record's attributes. #model.attributes() would be perfect but it neglects my relations. Is there a way to get a nested hash representation using this method (I read the documentation thoroughly and suspect not, but maybe there's some exotic option I don't know about). To be clear, the representation I am looking for would be:
{
:attribute_1 => 'some attribute', #an attribute of top level model
#...
:middle_level_children: => [{ # type 'MiddleLevelModel'
:attr_1 => 'some attribute of middle level model',
# ...
:bottom_level_children => [{ #type 'BottomLevelModel'
:attr => 'some attribute of bottom level model'
}]
}]
}
This seems like an incredibly simple (and, I would think, common) need, but I've had no luck.
Why can't you iterate through all your child relationships and print all the attributes for each instance of them?
Might be a little hokey but give Hash.from_xml a whirl.
Use the object's to_xml method to serialize with associations and then deserialize with the Hash.from_xml class method.
xml = #model_instance.to_xml(:include=>:middle_level_children)
nested_hash = Hash.from_xml(xml)

ActiveRecord Include, how to use in nested records?

I currently have the following:
#threads = current_user.threads.includes(:user, :thread_members)
I then take threads and do the following:
#threads.each do |thread|
thread_members = thread.thread_members_active(current_user)
#threadList << {
:id => thread.id,
:uuid => thread.uuid,
:user_id => thread.user.id,
:last_activity_at => thread.last_activity_at,
:user_count => thread_members.length,
:user_photos => thread_members.collect { |thread_member|
{
:id => thread_member.user.id,
:photo => thread_member.user.photo(:thumb),
:name => thread_member.user.full_name
}
},
:caption => thread.caption
}
end
The issue here is that every EACH loop, rails is hitting the DB for the same basic records. Rails sees to be caching as I see CACHE in the log but it's mighty messy. Leaves me wishing I could do some type of includes so there wasn't so many db requests.
Any ideas on how this can be optimized? Something around including all the users in one db hit?
Thanks
If you don't want any DB queries in the loop, you have to define everything that's used there in the named associations that are included, so instead of a thread_members_active method you'd define a thread_members_active association which has the same behavior. Note that the association also needs to use includes on user. Can't give you more right now, but maybe that helps a bit.
Edit: Check out the "Eager loading of associations" part of this doc:
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html

rails to_json unexpected behavior for include_root_in_json

I have a model like this
class Parent < ActiveRecord::Base
:has_many :kids
end
class Kid < ActiveRecord::Base
:has_many :grandkids
:belongs_to :parent
end
I can generate json like this:
the_parent.to_json( :methods => [:kids] )
=> { "parent" : { "kids" : [ { "kid" : { "name" => "kid0" .... and so on. Just what I want. Each object looks like a hash with a single key - which is the model name - and the value is an attribute hash. Great.
But I get into trouble when I try to serialize the whole tree, like this:
the_parent.to_json( :include => { :kids => { :include => :grandkids } } )
=> { "parent" : { "kids" : [ { "name" => "kid0" ...
which is missing the model names in the "kids" array. The same thing happens at the next level with the grandkids. I'm going to parse this somewhere else, and it would help to have certainty about the object name (as opposed to relying on convention using the relationship name). The docs advertise this:
ActiveRecord::Base.include_root_in_json = true
Which I found it to have no effect. My guess is the different behavior has something to do with the difference between the :method and :include options, but I can't wrangle the :method syntax to get the nesting I need, and I'm not sure if that will work even if it compiles.
Any ideas? Thanks, Dan
As a workaround, I'm overriding to_json in my model like this:
def to_json(args)
super( :methods => [:kids] )
end

Resources