How can I extend ActiveRecord model with new propery? - ruby-on-rails

I'm developing a REST API and before send results (fetched from DB with ACtiveRecord) to client I need to extend it with some additional data. Here is how I suppose it to be:
def index
(categories = Category.all()).each() do |category|
category.image = "some image"
end
render json: categories
end
It seems like Ruby does not add object proporties on the fly, like JS would do, so this code ends up with error message
undefined method `image=' for #<Category:0x007f89f0513d40>
I read a lot and found solution to add attr_accessor :image in my Category model. Now it does not throw error but also image property does not appeared in JSON output.

Instead of playing with the object it self, you might want to take a few mins and learn how jbuilder works, it's very simple and it would make your life a lot easier, especially when the json format includes a little extra/different data from your original database object,
Here's a simple format of what you might do
# app/views/categories/index.json.jbuilder
json.array! #categories do |category|
json.prop1 category.prop1
json.prop2 category.prop2
json.image whatever_image_function_here
end
Other solution would be overriding the as_json method of the class

Use as_json method with attr_accessor :image in category model.
def index
categories = Category.all.each do |category|
category.image = 'some image'
end
render json: categories.as_json(methods: :image)
end
In rails < 4.2 it has different name - to_json

Related

In Rails, How do you apply different serializers for each model/object in the same render?

In my render, I have
reder json: {code_names: #code_names, rows: #tables}, each_serializer: FreeTableSerializer
Where #code_names is just an arbitrary list, which doesn't matter much in our problem, and #tables can be a collection of obejcts of one model.
If I use each_serializer in this case, I'm assuming the each goes trough json's "children", which are both lists and it ends up not applying the serializer into #tables.
I want a way to return both #code_names and #tables with #tables using a Serializer that is no its default.
I tought about making a single variable that is a single list, but I need a way to identify both code and tables.
you can define a composite serializer
class ACompositeSerializer < ActiveModel::Serializer
def serializable_hash
tables_serializer_hash.merge code_names_serializer_hash
end
private
def tables_serializer_hash
FreeTableSerializer.new(object, options).serializable_hash
end
def code_names_serializer_hash
CodeNameSerializer.new(object, options).serializable_hash
end
end
update
in your case, i think just simple like this:
render json: {
code_names: #code_names.as_json,
rows: ActiveModel::Serializer::CollectionSerializer.new(
#tables,
serializer: FreeTableSerializer
)
}

Rails 4 and Jbuilder, how to call to_builder method for a collection of models

I've defined a to_builder method on my Location model. Here's the method:
class Location < ActiveRecord::Base
def to_builder
Jbuilder.new do |json|
json.(self, :id, :latitude, :longitude, :name, :description)
end
end
end
If I select a Location from the database, I can convert it to JSON like this:
Location.first.to_builder.target!
This works great. How can I do the same thing for a collection of Location models? For example, if I get multiple from the database via a relation (an Area has_many :locations).
locations = Area.first.locations
Is there an easy way to convert locations to json with Jbuilder? My current solution is to define a helper method that will convert models or collections to json for me. For example:
def json_for(object)
if object.is_a? ActiveRecord::Relation
a = object.map do |o|
o.to_builder.target!
end
"[#{a.join(',')}]".html_safe
else
object.to_builder.target!.html_safe
end
end
Then I would call json_for locations. But this does not feel like the right way to handle it.
UPDATE
I haven't yet found a great solution to this. Currently I am using a helper that I wrote to render the content of a JSON view. Here's what the helper looks like:
def json_for(view, locals_hash = {})
render(template: view, formats: [:json], locals: locals_hash).html_safe
end
Then, I use the helper like this in a view, passing in whatever variables are used in the view:
<%= json_for 'locations/index', { locations: #locations } %>
I also have a helper for partials:
def json_for_partial(partial, locals_hash = {})
render(partial: partial, formats: [:json], locals: locals_hash).html_safe
end
So in summary, I am not using the to_builder method at all. Instead, I'm creating the actual jbuilder view files, and then using a helper to render the JSON they generate wherever I need it within my app.
You can get the collection to render as an array that leverages your to_builder override like this:
Jbuilder.new do |json|
json.array! locations.map do |l|
l.to_builder.attributes!
end
end.target!
https://github.com/rails/jbuilder/issues/139
https://github.com/rails/jbuilder/issues/75

Is there a way to manually serialize a collection?

I'd like to manually serialize a collection for testing purposes. I tried this:
JSONAPI::ResourceSerializer.new(PostResource).
serialize_to_hash(PostResource.new(Post.all))
This doesn't work. It appears you can only serialize a single resource here. How would one return a serialized collection of all posts?
I was trying the same thing - but it is not possible. JSONAPI::Resource does not have any helper to easily convert Relation object or array of records into JSONAPI::Resource instances, so you can't pass the collection like this.
serialize_to_hash expects array of JSONAPI::Resource, so you would have to do something horrible like this:
result = []
Post.all.each do |post|
result << PostResource.new(post)
end
JSONAPI::ResourceSerializer.new(PostResource).serialize_to_hash(result)
JSONAPI::Resources expects that the JSONAPI itself should be sufficient, so no implementation of methods like index should be needed - gem will handle it itself without the need for manual serialization. But it is true, that I can imagine some scenarios, where I would want to be able to mannualy serialize collection of records, so there should be some easy way to do this...unfortunately, It looks like there is no easy way for now.
UPDATE:
I had some further questions myself so I have asked the creators of the gem here: https://github.com/cerebris/jsonapi-resources/issues/460
Another way to manually serialize data, based on JSONAPI Resources features, is by using the jsonapi-utils gem:
With the jsonapi_serialize method:
class API::V1::UsersController < API::V1::BaseController
def index
users = User.all
render json: jsonapi_serialize(users)
end
end
Or the high-level jsonapi_render method:
class API::V1::UsersController < API::V1::BaseController
def index
jsonapi_render json: User.all
end
end
Hope it's useful for you :-)
If you are using ActiveModelSerializers (https://github.com/rails-api/active_model_serializers)
Try to do helper like this:
def serialize_resource(resource)
JSON.parse(ActiveModelSerializers::SerializableResource.new(resource).to_json)
end
You can use this pattern both for one resource or many resources.
In your case it will be:
JSON.parse(ActiveModelSerializers::SerializableResource.new(Post.all).to_json)

Rails: do non-ActiveRecord models need to include ActiveModel::Serializers, or just respond to #as_json?

Using Rails 3.2, I'm working on an API backed model (not ActiveRecord). I want to be able to call to_json on this model in Rails controllers. After reading through a bunch of the ActiveModel docs I'm still not clear on one thing:
Given a model like this:
class MyModel
attr_accessor :id, :name
def initialize(data)
#id = data[:id]
#name = data[:name]
end
def as_json
{id: #id, name: #name}
end
end
Should this work as expected, or do I also need to include ActiveModel::Serializers::JSON? I'm having a hard time figuring out where the as_json / to_json methods are normally defined and when Rails calls which ones automatically in different circumstances...
Thanks for any insight!
Yes this does work, however not quote as you've written in.
When you render json in a controller using
def action
render :json => #my_model
end
Then Rails will automatically call to_json on your object and as long as you've defined to_json this will work as expected.
If your controller uses the Rails 3 content negotiation shenanigans, ie.
class Controller < ApplicationController
respond_to :json, :html
def action
respond_with(#my_model)
end
Then you will need to override as_json on your class, but the method signature requires an optional hash of options to be compatible with ActiveSupport so in this case you want
def as_json(options={})
...
end
Alternatively If you include ActiveModel::Serializers::JSON into your Class and your class supports the attributes method that returns a hash of attrs and their values - then you'll get as_json for free - but without the control of the resultant structure that you have if you just override the method by hand.
Hope this helps.

ActiveModel based class does not create the same results as an ActiveRecord equivilent

I am developing a Rails 3 app in a largely tabless capacity. I am using savon_model and ActiveModel to generate similar behaviour to ActiveRecord equivalents. Below is my code:
class TestClass
include Savon::Model
include ActiveModel::Validations
# Configuration
endpoint "http://localhost:8080/app/TestService"
namespace "http://wsns.test.com/"
actions :getObjectById, :getAllObjects
attr_accessor :id, :name
def initialize(hash)
#id = hash[:id]
#name = hash[:name]
end
client do
http.headers["Pragma"] = "no-cache"
end
def self.all
h = getAllObjects(nil).to_array
return convert_array_hash_to_obj(h, :get_all_objects_response)
end
def self.find(id)
h = getObjectById(:arg0 => id).to_hash
return convert_hash_to_obj(h, :get_object_by_id_response)
end
private
def self.convert_array_hash_to_obj(arrayhash, returnlabel)
results = Array.new
arrayhash.each do |hash|
results << convert_hash_to_obj(hash, returnlabel)
end
return results
end
def self.convert_hash_to_obj(hash, returnlabel)
return TestClass.new(hash[returnlabel][:return])
end
end
OK, so everything works as expected; values are pulled from the web service and onto the page. Unfortunately, when I look at the html produced at the client side there are some issues. The Show links are along the following lines:
/testclasses/%23%3CTestClass:0xa814cb4%3E
instead of...
/testclasses/1
So, I did a print of the object (hash?) to the console to compare the outputs.
[#<System:0xa814cb4 #id="1", #name="CIS">]
instead of what I believe it should be...
[#<System id="1", name="CIS">]
I have three questions:
1: What is the hex suffix on my class name when it is printed out
2: How can I modify my class to match the desired output when printed to the console?
3: Why are the frontend links (Show, Edit, Delete) broken and is there an easy fix?
Thanks so much for your time and apologies for rubbish code / stupid questions. This is my first Ruby or Rails app!
Gareth
The hex suffix is the object id of your instance of System
You can manipulate the output on the console by implementing an inspect instance method
The Rails url helpers use the to_param instance method to build these links. You should implement this if you are going to use your class as an ActiveRecord substitute.
Generally speaking, if you want to use all the Rails goodies with an own implementation of a model class, you should use ActiveModel:Lint::Test to verify which parts of the ActiveModel APIs are working as expected.
More information can be found here: http://api.rubyonrails.org/classes/ActiveModel/Lint/Tests.html

Resources