rails: how to add custom json to json output - ruby-on-rails

Whats the easiest way of adding custom json output when you render json? I've seen people override as_json or to_json but they usually exclude attributes or include associations. Is there a way of including plain text?
in my view:
def show_json
show_id = URI.decode(params[:id])
show_id = show_id.gsub(/\s*$/,'')
logger.debug "\nshow json: #{show_id}\n"
#qtls = Qtl.find_by_sql("select * from qtls where qtl_name like '%#{show_id}%' or qtl_symbol in (select qtl_symbol from obs_traits where qtl_symbol like '%#{show_id}%' or trait_name like '%#{show_id}%');")
render :json => #qtls
end
this returns something like:
[{qtl: {...qtl attributes..}},{... more qtls }]
and I would like to add a a specific field for each qtl. Whats best way to do that?

override as_json or use some gem to provide many json templates for example https://github.com/fabrik42/acts_as_api

Related

Rails : includes doesn't work in model scope

I created a scope in my book model and want to include the author relation. Unfortunately, the relation isn't loaded with the following code:
scope :search, ->(title) {
quoted_title = ActiveRecord::Base.connection.quote_string(title)
includes(:author).where("title % :title", title: title).
order(Arel.sql("similarity(title, '#{quoted_title}') DESC"))
}
I tried several tweaks such as using joins(:author).merge() but the relation is still not loaded. Any idea how to load the relation within a scope? Thanks.
Here is the controller with the method I called through Ajax to render search results:
def search
results_books = Book.search(search_params[:q]).first(5)
results_authors = Author.search(search_params[:q]).first(5)
results = results_books + results_authors
render json: { results: results }, status: :ok
end
For the search function in your scope, if I understand correctly, you are trying to pick out books that matches the searched params according to title field. If so, may I suggest a shorter version like this:
scope :search, lambda { |title|
where('title like ?', "%#{title}%")
}
As for including the associated authors into the json output. We usually use JBuilder when returning JSON objects to the front-end. If you insist in doing it using basic RoR then check out this answer by Substanstial https://stackoverflow.com/a/26800097/9972821
This isn't tested so let me know how well it goes. The rest of the post I shared also touches on JBuilder as the preferred alternative.

accessing multiple values from hash ruby on rails

This code snippet
room = Room.find(roomId)
returns a single column from room table, the returned value contains multiple attributes, like id, name, description, duration.
I want when coding
render json: room
to return only duration and name.
Do i have to write something like that
render json: room[:duration, :name]
query that will only give you the attributes that you want :
room = Room.select(:id, :duration, :name).find(room_id)
You can use the only option of as_json to include only certain attributes.
render json: room.as_json(:only => [:duration, :name])
You're slightly incorrect when you say Room.find(roomId) returns a "single column from the room table". It actually returns a single row.
Anyway, there are a few ways you can do this.
A good starting point is to use the .attributes method on a model to convert it to a Hash.
So for example say you have a user with a name, email, and id columns in the database. You can call user.attributes to get
{ "name" => "max", "email" => "max#max.com", "id" => 5 }.
you can select just the name and email with
user.select { |k,v| k.in?("name", "email") } and then call to_json on the result.
To avoid having to write custom to_json code for each controller response, you can overwrite .attributes in the model. Say my User table has a password column that I never want to expose in my API:
class User
def attributes
super.reject { |k,v| v.in?("password") }
end
end
Then, if you need to return a list of users as JSON:
render json: { users: #users.map(&:attributes) }.to_json, status: 200
The answer by Pavan will work as well, by the way.
Another approach is to use jbuilder and create json "views" for your records.

How to call an Active Model Serializer inline as an option to a hash assignment

I am sure this might be a stretch but I'm very impressed with the flexibility that Active Model Serializers provide. I'd like to something like:
def by_location_and_bin_number
#items=MenuItem.where('bin_number=? and location_id=?', params[:bin_number], params[:location_id]).is_valid
r={}
r[:status]="success"
r[:count] = #tems.count
r[:menu_items] = #items, serializer: ItemMicroSerializer # <- not working
render json: r.to_json
end
but this doesn't work. How can I get this to work (or similar syntax)?
As you want to serialize the array #items, you can use the independent method as
r[:menu_items] = ActiveModel::ArraySerializer.new(#items, each_serializer: ItemMicroSerializer)
It will serialize the provided array of objects by serializing each object using ItemMicroSerializer.

How to return 2 json objects at once?

I have a controller returning a json structure like so:
def show
# .......
o_json = deep_object_1_to_json(o)
render :json => o_json
end
private
def deep_object_1_to_json(o)
o.to_json(
:include => {....})
end
Now I need to extend it to return 2 objects. However the obvious solution is giving me problems:
def show
# .......
o1_json = deep_object_1_to_json(o)
o2_json = deep_object_2_to_json(o)
render :json =>
{
:object_1 => o1_json,
:object_2 => o2_json
}
end
This returns a json object with 2 strings of escaped json data!
The deep_object_2_to_json functions already have several layers of nested includes so I would rather not have to refactor these into a single function. Is there a way to make this easily extendable to add more objects in the future without the double escaping problem above?
Thanks for any pointers.
Sounds like you should be constructing something upon which to_json can easily be called.
The obvious candidate for active record objects is as_json. This does everything that to_json does (include the :include option and so on) except actually turning the object into json. Instead you get back a ruby hash which you can manipulate as you want and then call to_json. For example you could do
render :json => {
:o1 => object1.as_json(:include => :blah),
:o2 => object2.as_json(:include => :blah)
}
Your controller shouldn't be serializing the object as JSON until right before it hands it off to be rendered.
In other words deep_object_1_to_json should just be deep_object_1. Then you can package both return values into an array or hash and render the as JSON.
def show
# .......
o1 = deep_object_1(o)
o2 = deep_object_2(o)
render :json =>
{
:object_1 => o1,
:object_2 => o2
}
end
It might be a pain to change it now, but for the future of your system, you really ought to be doing it this way. JSON is just a format for sending objects over the wire or to disk; none of your code should have any references whatsoever to JSON unless it is passing it off to be rendered.
If you can refactor your class to return it's JSON via the to_json method, you can simply stick two or more objects into an array and call to_json on the array:
1.9.3-p125 :001 > require 'json'
=> true
1.9.3-p125 :002 > [{foo: "bar"}, {bar: "foo"}].to_json
=> "[{\"foo\":\"bar\"},{\"bar\":\"foo\"}]"
Example:
def to_json
super(include: [:some_association])
end

Deserialize ActiveRecord from JSON

I would like to save query result into redis using JSON serialization and query it back.
Getting query results to json is pretty easy:
JSON.generate(Model.all.collect {|item| item.attributes})
However I did not find a proper way to deserialize it back to ActiveRecord. The most straight-forward way:
JSON.parse(#json_string).collect {|item| Model.new.from_json(item)}
Gives me an error:
WARNING: Can't mass-assign protected attributes: id
So id gets empty. I thought of just using OpenStruct for the views instead of ActiveRecord but I am sure there is a better way.
You could instantiate the new object from JSON and then assign the id afterwards. Probably best to create your own method for this:
class Model
def self.from_json_with_id(params = {})
params = JSON.parse(params)
model = new(params.reject {|k,v| k == "id"})
model.id = params["id"]
model
end
end
Or maybe just override the from_json() method.
Why not like this:
JSON.parse(#json_string).each do |item|
item.delete(:id) # I tested it in my case it also works without this line
object=Model.create(item)
end
If the host that created the JSON adds a JSON root you might have to use item[1] instead of item.

Resources