I have been trying to figure out a way to customize JSON with special fields, custom formats, etc etc. I have created an as_json and to_xml method in my model to formulate the object how I need. This works well but it is sloppy because some of my helper methods had to move into the model, because I need the formats in the helpers and model. I also think it is sloppy code and makes the model out of control.
I have been able to get a format with json.erb working but don't think it is working 100% correct either and the callback doesn't append either. Anyone get this working
Here is what I got so far.
api calls format.json
template called is items.json.erb
<% #items.each do |item| %>
<%= { :item => { :id => item.id, :name => item.name }.to_json.html_safe %>
<% end %>
This works but seems odd. Anyone have suggestions or have a way to do this?
btw did this for the callback to work
<%= params[:callback]+"(" if params[:callback] %>
<% #items.each do |item| %>
<%= { :item => { :id => item.id, :name => item.name }.to_json.html_safe %>
<% end %>
<%= ")" if params[:callback] %>
I think the best way to do this would be to skip the erb template if you don't absolutely need if for some reason. Then you could do something like this:
items = Item.all
render :json => items.to_json(:only => [:id, :name]), :callback => params[:callback]
You can override the to_json method in your model to add fields or call methods.
Based on your answer to polarblau, you should override the as_json method and use the :methods parameter to include method results in your json
class Item
def date
return "1 year and 8 months" #obviously use logic here
end
def as_json(args={})
super(:methods=>[:date], :only=>[:id=>:name])
end
end
Most likely, you'll want to either:
use custom finder sql to alter column names/perform calculations (it's much faster than Ruby):
MyModel.select('col_name_that_needs_renamed AS new_name').order('some_col DESC')
or a more complicated example:
MyModel.find_by_sql('SELECT col_name_that_needs_renamed AS new_name, foo_col*50 AS math WHERE foo=bar ORDER some_col LIMIT 8')
if there's something you can't do (or can't figure out) in SQL, you may have to revert to Ruby (although not recommended because it's significantly slower)
API Dock for to_json
Related
This may sound strange, but none the less I want to learn how to do it and I need some help getting there. I'm not sure how to approach this. I'm hoping to get some dev love on this.... Let me explain by giving an example. (Btw thank you---you are awesome!)
Instead of this in my view:
<table>
#users.map do |user|
...
</table>
I want to extract it away into a helper that I can reuse for other collections.
So I want to say instead:
#users.to_table({
template: "simple_template",
header: ["Full Name","Email"],
column: ["name", "email"]
})
So in my application_helper I have something like this: (pseudo-ish code)
class ActiveRecord::Relation
def to_table *args
load args.template
self.map do |j|
args.header do |header|
j.header
end
args.column do |column|
j.column
end
end
end
end
I have no idea how to wire this up. (helper or table template) Definitely an order of magnitude above my current skill level. Need some serious direction.. I'm asking this because I feel like I hit a learning plateau and need help busting through to something more challenging (hence this question)... Hope it's clear, if not ask for clarification. Thanks for reading... Thanks for helping! =)
Not guaranteeing this will work it is just to show the syntax issues:
class ActiveRecord::Relation
def to_table(options={})
load options[:template]
self.map do |j|
Hash[
args[:headers].zip(args[:columns].map{ |column| j.send(column) }
]
end
end
end
Not Sure about the load part I think this should be handled outside of the relation as it is a view issue and has nothing to do with the ActiveRecord::Relation but this method will return an Array of Hashes like
[{"Full Name" => "USER 1 Name", "Email" => "USER1#email.com},{"Full Name" => "USER 2 Name", "Email" => "USER2#email.com"}]
In your current method args which is an array now based on the * will not respond to things like template or column. Like I said I have never really tried to implement anything in this way but the syntax change might get you headed in the right direction. Also handling should be put in place for when template is not passed or headers.count != columns.count.
Best bet is probably something like this
<%= render "template", obj: #user.to_table(headers: ["Full Name","Email"],columns: ["name", "email"]) %>
in _template.rb
<table>
<thead>
<tr>
<% obj.first.keys.each do |header|
<th><%= header %></th>
<% end %>
</tr>
</thead>
<tbody>
<% obj.each do |row|
<tr>
<% row.values.each do |cell| %>
<td><%= cell %></td>
<% end %>
</tr>
<% end %>
</tbody>
Although if I had more time to think there are probably far simpler implementations of this maybe something like
<%= render 'template', locals:{collection: #users, headers: ["Full Name","Email"], columns: ["name", "Email"]} %>
UPDATE
I think making a view helper might be better in this instance like this
def make_table(collection,options={})
content_tag(:table,options[:table_options]) do
content_tag(:thead) do
content_tag(:tr) do
options[:headers].map do |header,header_options|
content_tag(:th,header,header_options,false)
end.join.html_safe
end
end
content_tag(:tbody,options[:body_options]) do
collection.map do |obj|
content_tag(:tr,options[:row_options]) do
options[:columns].map do |column,cell_options|
content_tag(:td,obj.public_send(column),cell_options,false)
end.join.html_safe
end
end.join.html_safe
end
end
end
call as
<%= make_table(#users,columns:{name:{class: "name"},email:{}},headers:{"Full Name"=>{class:"name_header"},"Email"=>{}}) %>
or without formatting
<%= make_table(#users,columns:[:name,:email],headers:["Full Name","Email"]) %>
This method requires an object collection and will accept the following through the options Hash
:table_options as a Hash to pass to the content tag for formatting the table
:headers as an Array or Hash (for formatting header rows)
:body_options as a Hash to pass to the content tag for formatting the table body
:row_options as a Hash to pass to the content tag for formatting the rows
:columns as an Array or Hash (for formatting the individual cells)
You can place this method in helpers/application_helper.rb and you will have access to it throughout the application. Although I have not fully vetted this method and it is currently more conceptual than anything else.
I am trying to convert this .json.rabl view to .json.erb and since im very new to ruby, I am looking for direction in achieving this. What will be the best way to go about this?
object #perform
attributes :id => :team_id
attributes :perform_id
node(:fb_user_id) {|x| x.user.fb_user_id.to_s}
node(:first_name) {|x| x.user.first_name}
node(:last_name) {|x| x.user.last_name}
attributes :level, :jersey, :height, :weight, :positions
node(:stat_source) {|x| Stat::SOURCES.key(x.stat_source)}
if !#nomvps
node({}, :if => lambda{ |m| #event.present? }) do |x|
{
mvp_votes: Vote.player_vote_count(#event.id, x.id)
}
end
end
if !#nostats && (#_options[:event] || #event)
node :stats do |perform|
Hash[*perform.source_stats.where(event_id: (#_options[:event].try(:id) || #event.id)).map{|x|[x.name, x.value]}.flatten]
end
end
Erb is the reverse of a templating language like rabl - you create the json you want, and then insert the values with erb, rather than defining the structure with code as in rabl.
In erb surrounding text with <% %> indicates some ruby code (for assigning, loops etc):
<% x = "output" %>
and <%= %> (NB = sign) indicates ruby code (any code) which will be output to the template by calling to_s:
<%= x.to_json %>
Anything not surrounded by those symbols is output exactly as it is.
So you should be able to just create the json you want with fake values, and then put in the calls to the relevant ruby code to get the real values at runtime. e.g. something like this fragment (not familiar with rabl so the format of json may need adjustment):
[{ "perform" :
{
"id" : <%= #perform.team_id %>,
"user" : <%= #perform.perform_id %>,
...
}
}]
<% if !#nomvps %>
<% node({}, :if => lambda{ |m| #event.present? }) do |x| %>
{
mvp_votes: <%= Vote.player_vote_count(#event.id, x.id) %>
}
<% end %>
<% end %>
For objects which map cleanly to json as a hash for example you could also consider creating to_json methods instead of laying them out in a template like this, but for a complex structure like this without a clean mapping a template might be best.
I'd start with the json you want in a file named xx.json.erb (pure json), then start inserting values using erb.
Maybe it's not an answer to the exact question, but the endorsed way to do JSON views in rails is using jbuilder.
The API of JBuilder is very similar to the RABL one, but it's done by the very same rails core team, so it integrates with all the internals, like cache helpers (in the future also cache dependence), partials etc.
https://github.com/rails/jbuilder
I have created a custom helper in my application.rb file, which looks like:
module ApplicationHelper
def add_feature_field(feature_type, object_form_builder, actions_visible)
object_form_builder.object.features.build
fields = object_form_builder.fields_for :features do |features_builder|
render :partial => "features/fixed_feature", :locals => {:feature => features_builder, :fixed_feature_type => feature_type, :form_actions_visible => actions_visible}
end
end
end
I am calling this helper from my view like so:
<%= add_feature_field("First Name", customer, false) %>
<%= add_feature_field("Last Name", customer, false) %>
<%= add_feature_field("Date of Birth", customer, false) %>
This is working pretty much as anticipated, except for one major hurdle: the second time the helper is called, it renders 2 fields instead of a single field, and the third time it renders 3 fields.
I assume that what is happening is that the fields_for loop in my helper is picking up the previously built objects, and also rendering those - can anyone suggest a way of preventing this?
EDIT: For clarity, as per the comments, this helper method is being used within the Customer form; the Features being created are nested attributes.
I'm implementing search and am having some difficulties with my sql/finding.
Basically, I have a favorites page, that gets a collection of favorites with:
#favorites = current_user.votes
In the view, I then loop through my favorites, and can call .voteable on them to get the actual object that was voted on. This is making search very difficult for me to write.
I was wondering if it was possible to change my original collection, so that I'm actually getting the .voteable objects each time to dry up my view/help me write my search. I cannot called current_user.votes.voteables but individually can do something like current_user.votes.first.voteable
What I've attempted is a loop like so:
#favorites = current_user.votes.each {|vote| vote.voteable }
Which is wrong, and I'm just getting my votes again, and not the actual voteable object. I was just wondering if there was a way to get these voteables from looping through my votes like this.
Any pointers would help, thanks.
EDIT:
Expansion what I mean by search:
I'm building a method in the model that searches self, here is an example:
def self.search(search)
if search
where('title LIKE ?', "%#{search}%")
else
scoped
end
end
I pass in search from the view, with a form like:
<div id="search_form">
<%= form_tag topic_links_path, :method => 'get', :id => "links_search" do %>
<%= text_field_tag :search, params[:search], :size => "35" %>
<%= submit_tag "Search", :name => nil %>
<% end %>
</div>
That way, when I make my collection in the controller, I can call .search(params[:search]) to get only the items that are like whatever the user entered in the form. I'm using the vote_fu gem for handling the votes/voteables.
You need to use map instead of each:
#favorites = current_user.votes.map {|vote| vote.voteable }
each simply loops through the elements and performs the operation on them, but it doesn't return the result in an array format. That's what map does.
On a side note, you can use a scope for search instead of a function. It might be a little cleaner:
scope :search, lambda{ |title| where('title LIKE ?', "%#{title}%") unless title.blank? }
i am trying to use form data outside of just form elements. i want to show form data as normal text.
controller:
#addresses = ['Billing', 'Shipping']
#addresses.each do |a|
addresses.build(:address_type => a)
end
then within my form...for example...(haml)
- fields_for :addresses do |a|
a.address_type #to just render 'Billing', etc.
or...
- fields_for :addresses do |a|
%div{:class => a.address_type
would i need to make a custom formbuilder method? or is there an existing way
can't understand what you're looking for. that code doesn't seem correct at all. if #addresses is an array of elements, how do you expect it has any ActiveRecord methods?
EDIT:
if you want to render just the data, even if the object is not saved, it's not a problem:
in the controller you'll build the object:
...
addresses.build(:address_type => a)
...
then, in the view, use that data:
<some tag>
<%= #object.address_type %>
</some tag>
With a better example, I can explain better, but I hope you understand ;)
discovered the object method!
- form_for #addresses do |a|
%h1= a.object.address_type