How do you render a partial to a hash value in JBuilder? - ruby-on-rails

I have a tree-like object graph that resembles the following:
{
:name => "Grandparent",
:children => {
:child_a => {
:name => "Parent A",
:children => {
:grandchild_a_a => {
:name => "Child A-A",
:children => {}
}
:grandchild_a_b => {
:name => "Child A-B"
:children => {}
}
}
}
:child_b => {
:name => "Parent B",
:children => {}
}
}
}
I want to generate JSON that mirrors this structure. I don't know how deep the child nesting goes, and the attributes are the same for each level. The keys in the children hash are significant and must be preserved.
I want to use a JBuilder partial to represent a level, and then call it recursively. Here's my template thus far:
# _level_partial.json.jbuilder
# passing the above object graph as :level
json.name level[:name]
json.children do
level[:children].each do |key, child|
# How do I map the following to the given key?
json.partial! "level_partial", :level => child
end
end
I can generate the JSON for each child through the partial call easily enough, but that inserts it directly into the JSON output. How do I map the results of the partial to a particular hash/object key?

I've found an answer. Although it appears to be largely undocumented, JBuilder.set! can accept a block instead of an explicit value. That block can call the partial, which is then assigned to the hash.
json.name level[:name]
json.children do
level[:children].each do |key, child|
json.set! key do
json.partial! "level_partial", :level => child
end
end
end

Related

Rails. Has_many :through and form_for params for a checkbox field

So I have this kind of association:
class FirstModel
has_many :merged_models
has_many :second_models, :through => :merged_models
end
class SecondModel
has_many :merged_models
has_many :first_models, :through => :merged_models
end
class MergedModel
belongs_to :first_model
belongs_to :second_model
end
Now my problem is to understand this trick that helps check_box_tag helper to recognise elements in HTML from a passed collection in my form:
form_for(first_model) do |f|
<% SecondModel.all.each do |s| -%>
<div>
<%= check_box_tag 'second_model_ids[]', s.id, first_model.second_models.include?(s), :name => 'first_model[second_model_ids][]'-%>
<%= label_tag :second_model_ids, s.first_name -%>
</div>
<% end -%>
What I do not understand is this:
first_model.second_models.include?(s), :name => 'first_model[second_model_ids][]'
I believe that this:
first_model.second_models.include?(s)
checks if SecondModel's object id is already in FirstModel's second_model_ids array. In this case I would expect something like an if statement - if this id is there then do that, etc.
And this part makes me even more confused:
:name => 'first_model[second_model_ids][]'
Where that :name came from? Why first_model[second_model_ids][] have two square brackets - how they work in Rails syntax? To merge this newly checked id to the second_model_ids array?
I will appreciate all info. Thanks!
So check_box_tag has this signature:
check_box_tag(name, value = "1", checked = false, options = {})
In your case:
check_box_tag 'second_model_ids[]', s.id, first_model.second_models.include?(s), :name => 'first_model[second_model_ids][]'
The first parameter (name) is 'second_model_ids[]', this will be used as the id= part of the tag.
The second parameter (value) of the checkbox is the id of s (current instance of SecondModel).
The third parameter (checked) is:
first_model.second_models.include?(s)
You are right about the meaning, and you don't need an 'if'. The include?() returns a boolean (like most Ruby methods that end in a question mark). You can try this in irb or rails console:
[1,2,3].include?(2)
# => true
The final option:
:name => 'first_model[second_model_ids][]'
passes in a hash of options which will be used as html. In this case a single hash value with the key :name (not to be confused with the first parameter above, which was used as the id='...' in the html tag), this will be used directly in the tag as
name='first_model[second_model_ids][]'
You were right about the syntax here also. The brackets help Rails parse this into the correct nesting of the params hash with
first_model: {foo: 1, bar: 2, second_model: {some: stuff, other: stuff}}

RABL fails for empty query

I have a data tables table which is receiving data via a query that is then formatted using RABL.
It all works great until my search finds no rows.
Then I just get the ....processing message and no update.
I am really confused by RABL. Is there a way to see what it is doing?
This is my data.rabl file
object false
node(:iTotalRecords){#iTotalRecords}
node(:iTotalDisplayRecords){#iTotalDisplayRecords}
child(#aaData, :object_root => false) do
attributes :school_name => "1",
:student_name => "3",
:paid => "4",
:short_name => "5",
:emailed => "6",
:reconciled => "9"
node("6") do |p|
if p.emailed == 0
'false'
else
'true'
end
end
node("0") do |p|
p.created_at.to_date
end
node("2") do |p|
schedule = p.enrollment.schedule
link_to(p.klass_name, schedule_path(schedule))
end
node("3") do |p|
link_to(p.enrollment.student.name, student_path(p.enrollment.student_id))
end
node("7") do |p|
p.enrollment.rate
end
node("8") do |p|
p.enrollment.paid
end
node("10") do |p|
link_to("Edit", edit_payment_path(p), :class => 'btn btn-info btn-mini')
end
end
What I needed to do was to make sure that the json included a field called :payments.
If there was no data the child loop was not being executed and so there was no reason for RABL to generate a :payments field.
I found that you can force this to happen by creating an alias.
So I added the code:
child :payments => :payments
right before the line
child(#aaData, :object_root => false) do
This works great now.

RABL - attributes within child node

If I create a child node in RABL using the node() method, how can I control the attributes that are presented?
The JSON output is this:
[
{
"location": {
"latitude": 33333,
"longitude": 44444,
"address": "xxxxxxx",
"title": "yyyy",
"url": "http://www.google.com",
"rate": {
"created_at": "2012-09-02T11:13:13Z",
"id": 1,
"location_id": 1,
"pair": "zzzzzz",
"updated_at": "2012-09-02T12:55:28Z",
"value": 1.5643
}
}
}
]
I want to get rid of the created_at, updated_at and location_id attributes.
I have this in my view file:
collection #locations
attributes :latitude, :longitude, :address, :title, :url
node (:rate) do
|location| location.rates.where(:pair => #pair).first
end
I tried using a partial and the 'extend' method, but it totally screwed things up. Also, I tried adding attributes to the block but it didn't work (the output was as specified in the attributes but it didn't show the values for each attribute).
Thanks!
Your code: location.rates.where(:pair => #pair).first returns the whole Rate object. If you want specific fields (for example: all, except for create_at, updated_at, etc.) then you have two options:
Manually describe the hash in node():
node (:rate) do |location|
loc = location.rates.where(:pair => #pair).first
{ :pair => loc.pair, :value => loc.value, etc... }
end
Or you can do this:
node (:rate) do |location|
location.rates.where(:pair => #pair).select('pair, value, etc...').first
end
...and as a side note, I should say that placing logic (rates.where) in your view is not a best practice. see if your controller can do that for the view using the Rate model.
You wouldn't be able to use attributes within the node block, since "self" in there is still the root object or collection, so in your case #locations. See also RABL wiki: Tips and tricks (When to use Child and Node)
In the node block you could simply create your custom response by only listing the attributes that your interested in:
node :rate do |location|
rate = location.rates.where(:pair => #pair).first
{:id => rate.id, :location_id => rate.location_id, :value => rate.value}
end
You can also try the approach using a partial:
In app/views/rates/show.json.rabl
object #rate
attributes :id, :location_id, :value
Then in your #locations rabl view:
node :rate do |location|
rate = location.rates.where(:pair => #pair).first
partial("rates/show", :object => rate)
end

accepts_nested_attributes_for and nested_form plugin

I've the following code in a _form.html.haml partial, it's used for new and edit actions.
(fyi I use the Ryan Bates' plugin nested_form)
.fields
- f.fields_for :transportations do |builder|
= builder.collection_select :person_id, #people, :id, :name, {:multiple => true}
= builder.link_to_remove 'effacer'
= f.link_to_add "ajouter", :transportations
works fine for the new action...
for the edit action, as explain in the doc, I've to add the :id of already existing associations, so, I've to add something like
= builder.hidden_field :id, ?the value? if ?.new_record?
How can I get the value?
Here is the doc of accepts_nested_attributes_for for reference (source: http://github.com/rails/rails/blob/master/activerecord/lib/active_record/nested_attributes.rb#L332)
# Assigns the given attributes to the collection association.
#
# Hashes with an <tt>:id</tt> value matching an existing associated record
# will update that record. Hashes without an <tt>:id</tt> value will build
# a new record for the association. Hashes with a matching <tt>:id</tt>
# value and a <tt>:_destroy</tt> key set to a truthy value will mark the
# matched record for destruction.
#
# For example:
#
# assign_nested_attributes_for_collection_association(:people, {
# '1' => { :id => '1', :name => 'Peter' },
# '2' => { :name => 'John' },
# '3' => { :id => '2', :_destroy => true }
# })
#
# Will update the name of the Person with ID 1, build a new associated
# person with the name `John', and mark the associatied Person with ID 2
# for destruction.
#
# Also accepts an Array of attribute hashes:
#
# assign_nested_attributes_for_collection_association(:people, [
# { :id => '1', :name => 'Peter' },
# { :name => 'John' },
# { :id => '2', :_destroy => true }
# ])
Thanks for your help.
I found my error, here is what i learned fyi:
When you use accepts_nested_attributes_for with many to many associations, keep the :id primary key for the association table.
Cheers
Mine works when using ":_delete" instead of ":_destroy". I am on rails 2.3.4. Ruby 1.8.7
Check out this: http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#M001605
Nested forms are officially supported with Rails. What you are doing (specifically with the fields_for method) may be conflicting with RAils' built-in way to render fields_for.
Here's the documentation for the Rails way to do fields_for...it's very thorough:
http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#M001605
I highly recommend you try the built-in way instead of the plugin, as that will continue to be supported almost indefinitely.
Hope this helps!

On the fly tags generation for XML Builders

I have a hash like,
object = { :type => 'book', :name => 'RoR', :price => 33 }
OR
object = { :type => 'wig', :name => 'Elvis-Style', :price => 40, :color => 'black' }
The problem is that keys in above hash may be different all the time or even increase and decrease depending upon the object type.
What I want to do generate XML for above hashes using Xml::Builder. The XML tags are decided by the keys in the hash and text inside a tag is value corresponding that key.
I can use eval to do this like below. However, I think there must be a better way to do it.
object.each do |key, text|
eval("xml.#{key.to_s} do
#{text}
end
")
end
#object.each do |k, v|
xml.tag!(k.to_s, v)
end
Rails supports to_xml on Hash classes.
hash = { :type => 'book', :name => 'RoR', :price => 33 }
hash.to_xml
# Returns
# <?xml version=\"1.0\" encoding=\"UTF-8\"?>
# <hash>
# <type>book</type>
# <name>RoR</name>
# <price type=\"integer\">33</price>
# </hash>
If you want to skip the types then:
hash.to_xml(:skip_types => true)
If you want to give a different root then:
hash.to_xml(:root => 'options')
This one worked.
#object.each do |k, v|
xml.tag!(k.to_s, v)
end
out << "<#{key}>#{html_escape(value)}</#{key}>"

Resources