Nested attributes in hash representation of a record - ruby-on-rails

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)

Related

Rails, strong parameters, and complex data structures

Good aftern, SO folks
I am strong parameterizing a Rails 3 application that we plan to upgrade to Rails 4. Some of the controllers use the params object to hold not just nested hashes, but hashes within arrays within hashes within arrays etc. Changing the nature of the data structure would be too intense, we want to ideally have it return the same data structure, but strong parameterized
Here's an example as JSON:
"my_example" => {
"options" =>
[{"id" => "1"
"name" => "claire"
"keywords" =>
["foo", "bar"]
},
{"id" => "2",
"name" => "marie",
"keywords =>
["baz"]
}],
"wut" => "I know, right?"
}
But for added fun, the keywords array can contain any string. Which I've read about and which is tricky and supported in other versions of rails but whatever.
Are there any general rules of thumb about making complex data structures with the strong_parameters gem? I know that Rails 4 and 5 handle this better, but I'm curious.
Nested parameters are not really that challenging.
params.require(:my_example)
.permit(:wutz, options: [:id, :name, keywords: []])
This expects that options is an array of resources where the keys :id, :name, and :keywords are to be whitelisted.
:wutz, :id, :name can be any permitted scalar type. keywords: [] permits an array of any scalar type (any string, integer, date, etc). I don't really get why you're fretting here.
The issue is mainly with nested hashes with extremely dynamic contents. In that case which is not quite covered the Rails strong parameters you can use .permit! and unleash the full tools of Ruby hash slicing and dicing which are quite formidable.
The gem pretty much backports the api of ActionController::Parameters in later versions of Rails pretty closely so I would not expect any major hickups when upgrading.
https://github.com/rails/strong_parameters#nested-parameters

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 to pluck multiple attributes in Rails 3.x?

I have an ActiveRecord model like this:
class Person < ActiveRecord::Base
attr_accessible :name
end
and need to get a hash mapping Person's ids to their names:
{1 => "Paul", 2 => "Aliyah", 3 => ... }
Now, the obvious way would be
Person.all.collect { |p| [p.id, p.name] }.to_h
However, I don't need to instantiate every Person, I just need the hash. In Rails 4, I can .pluck(:id, :name) instead of collect, however in 3.x, pluck takes only one argument. However I found this workaround to get what I want without loading the models:
Person.all.group(:id).minimum(:name)
Question: will I burn in hell? Also, is there a more elegant way to do this, and are there any drawbacks of this hacky approach that I may not be aware of? Thanks!
Here's a pretty good write up of this situation and various tactics for handling it: Plucking Multiple Columns in Rails 3
My preference of suggested solutions there is to make and include a module:
# multi_pluck.rb
require 'active_support/concern'
module MultiPluck
extend ActiveSupport::Concern
included do
def self.pluck_all(relation, *args)
connection.select_all(relation.select(args))
end
end
end
class Person < ActiveRecord::Base
attr_accessible :name
def self.pluck_id_and_name
result = connection.select_all(select(:id, :name))
if result.any?
# if you are using Ruby 2.1+
result.to_h
# Works in 1.9.3+
Hash[result]
end
end
end
Since the result should be an array of arrays we can use nifty trick to get a hash with the first element as keys and the second as values:
Hash[ [ [1, "Joe"], [2, "Jill"] ] ]
# => { 1 => "Joe", 2 => "Jill"}
See:
Convert array of 2-element arrays into a hash, where duplicate keys append additional values
To avoid loading all of the objects you could do this:
hash = Hash.new
ActiveRecord::Base.connection.execute("SELECT id, name FROM persons").each {|person| hash[person['id'].to_s] = person['name'].to_s}
My company used one of the tactics from Plucking Multiple Columns in Rails 3 before.
But we had trouble upgrading from Rails 3 to Rails 4 because it didn't work in Rails 4.
I suggest using pluck_all gem which has high test coverage in Rails 3, 4, 5, so you will not worry about future upgrades.

What does :attribute => parameter actually do?

I have a hard time understanding the form :attribute => parameter
Can anyone give me some explanations for it? Is :attribute a field (variable) belonging to the class or something else? Why we can pass this pair as one parameter to methods?
If you're referring to something like this:
some_method(:foo => "bar", :baz => "abc")
then it's just shorthand which causes ruby to convert those things into a Hash. Please note that when using this form, that the hash must be the final argument to the method in order for this to work.
Based on the explanation above, this
some_method(:foo => "bar", :baz => "abc")
is ok, but this
some_method(:foo => "bar", :baz => "abc", moo)
is not.
Though you will see this commonly in Rails, it is not a Rails specific question. It is Ruby.
The answer to your question is that it is key/value pairs in a Hash, generally passed as an argument to a method.
You will see this as well when it is being assigned to a variable directly. But let me show you a sample method, and a sample usage, so that you can put them together:
def some_method(*args, name: 'Joe', amount: 42, **other_params )
puts "#{name}, #{amount}, glob of arguments = #{args.inspect}",
"other params #{other_params}"
end
some_method(:occupation => 'programmer', :phone => '123-456-7890', name: 'Jane')
This is Ruby 2.0.0 specific in the fact that you can provide for that last argument, which provides for unnamed parameters, in practice. Using the 1.9+ syntax for a Hash in the argument list, you are allowed to provide for other unnamed "parameters" which can appear after the hash argument.
Notice that if I had used the older syntax for Hash, namely the :key => 'value' syntax, I would not be allowed (at least as of this writing) to have the **other_params argument at the end of the argument list.
You could also provide the hash using the newer syntax in the calling code, though I left it as the Hash syntax when calling some_method.
The Hash still needs to be the last provided in the calling argument list, the same as indicated in the argument list for the method definition.

Where to store model attribute value translations

I have a model Invoice with attribute payment_status. payment_status has fixed values unpayed|partial_payed|payed that I want to store the translations for in a locale-file.
I thougt it would be good to have it in the model local-file
de:
activerecord:
attributes:
payment_status: Zahlstatus
payment_status_values:
unpayed: offen
partial_payed: teilgezahlt
payed: ausgeglichen
now I can get the translated payment_status-value for the last invoice like this
I18n.t Invoice.last.payment_status , :scope => "activerecord.attributes.invoice.payment_status_values"
=> "offen"
to me it looks like typing sopes a lot, is there maybe a scoped method to get the translation or a better way to do this at all?
We used the easy_enums plugin from marcel. The closest I found was: https://github.com/mschuerig/easy_enums/
Syntax is like this. Then you store only last part of the scope identifier.
has_enum :shipping_mode, :default => :not_set, :fallback => :not_set do
value :not_set
value :address
value :self_collect
define_method(:localize) { I18n.t("models.payment.shipping_mode.#{self.id}") }
end
Does that hit your goal?

Resources