How to respond_with multiple objects in Rails 3.1 - ruby-on-rails-3.1

I have a route for example
POST /interaction.json
where the client posts a new interaction. Normally my controller would look like
class InteractionController < ApplicationController
def create
respond_with #log
end
end
and I will get back a json response
{ "log" : { "id" : 20, .... } }
and the location header set to
http://foo.com/log/20
However if I wish to return more objects in my :json response than just the #log. For example to notify the client that some thing has changed with respects to this interaction the normal. Perhaps the user has won a prize for making this interaction. It would be nice to be able to do
response_with #log, #prize
and get the response
{ "log": { "id": 20, ... },
"prize": { "id": 50, ...}
}
but that is not the way respond_with works. It treats #prize as a nested resource of #log. Can anyone suggest an idea for this?

Merging two independent objects is dangerous and will override any existing attributes in the caller.
Instead you could always wrap the objects and respond with the wrapper instead:
#response = {:log => #log, :price => #price}
respond_with #response

Assuming that #log and #prize are both hashes, you could merge both hashes and return the merge.
respond_with #log.merge(#prize)
I'm thinking it might overwrite the #log.id with #prize.id though. Can try something else if it does.

Related

Creating a FactoryBot Hash model with complicated structure, including multiple nested attributes

I'm implementing a set of Cucumber driven API tests and stumbled into one specific model that has a few nested elements, like this:
factory :human_being, class: Hash do
human {
name {
first_name { Faker::Name.first_name }
last_name { Faker::Name.last_name }
}
age { rand(1..100) }
}
initialize_with { attributes }
end
The result I wanted to achieve should look like this:
{
:human => {
:name => {
:first_name => "mike",
:last_name => "blob"
},
:age => 16
}
}
I am invoking creation using FactoryBot.build(:human_being) and I get undefined method 'name' for #<FactoryBot::SyntaxRunner:0x00007fd640a39a80>
My env.rb has the World(FactoryBot::Syntax::Methods) / FactoryBot.find_definitions lines.
I've lurked through a few answers regarding the nested attributes / associations / traits but I didn't find a proper way to get what I want. Sorry if the post is duplicated and thanks in advance for help.
Okay, seems that I've found the solution right after posting the question. Currently the following model creates the Hash I described in the question:
factory :human_being, class: Hash do
human { {
name: {
first_name: Faker::Name.first_name,
last_name: Faker::Name.last_name,
},
age: rand(1..100),
sex: %w(male female other).sample
} }
initialize_with { attributes }
end
Although I'm not sure it is a completely correct answer from guidelines perspective, but at least it works for my exact case.
source (See 'Defining factories'): Because of the block syntax in Ruby, defining attributes as Hashes (for serialized/JSON columns, for example) requires two sets of curly brackets:
factory :program do
configuration { { auto_resolve: false, auto_define: true } }
end

ActiveModel::Serializers::JSON - How to map an array of JSON from a returned string to an collection of Ruby objects

I have code that works for a single instance but the API I am consuming returns an array of data. I have a class to encapsulate this data:
class Brewery
include ActiveModel::Serializers::JSON
attr_accessor :id, :name
def attributes=(hash)
hash.each { |key, value| send("#{key}=", value) }
end
def attributes
instance_values
end
end
And what the returned data looks like is similar to this
[
{
"id": 2,
"name": "Avondale Brewing Co"
},
{
"id": 44,
"name": "Trim Tab Brewing"
}
]
I can marshal a single JSON hash to the class with code such as this:
brewery = Brewery.new
brewery.from_json(single_brewery)
However this doesn't work with the array. I'm relatively new with Ruby so I'm not quite sure what the function to use is or to at least complete the JSON hashes to an array I can map from_json over.
This works but seems clunky
breweries = JSON.parse(brewery_list).map { |b|
brewery = Brewery.new
brewery.from_json(b.to_json)
}
I am unsure why do you find mapping an array clunky, but you might turn your Brewery to be a factory.
class Brewery
...
def self.many(brewery_list)
JSON.parse(brewery_list).
map(&:to_json).
map(&Brewery.new.method(:from_json)
end
end
And use it like this
breweries = Brewery.many(brewery_list)

ActiveModel serializer ignores root key when posts or post is empty or nil

I am using active model serializer V0.10.0 with Rails 5 api only application. During implementation I noticed the AMS is completely ignoring the root key when the posts/post is empty or nil respectively. This behavior actually breaks my mobile app as it always expects root key data in all response.
So what I want to achieve here is no matter what I always want data as root element of my Rails app response for all requests.
Response for SHOW API when the post is empty
SHOW render json: #post, root: 'data'
Expected
{
"data": {}
}
Actual
null
Response for INDEX API when the posts are empty
INDEX render json: #posts, root: 'data'
Expected
{
"data": []
}
Actual
{
"posts": []
}
class ApplicationSerializer < ActiveModel::Serializer
include Rails.application.routes.url_helpers
ActiveModelSerializers.config.adapter = :json
def host
Rails.application.secrets.dig(:host_url)
end
end
class PostSerializer < ApplicationSerializer
attributes :id
has_many :comments
end

Ruby Active model serializer with jsonapi , how to characterise links

I am using active model serializer and jsonapi format
I need to get :
{
"data": {
"id": "1234",
"type": "search",
"relationships": {
"foo": {
"data": [
{
"id": "12",
"type": "foo"
}
],
"links": "/foo/12"
},
}
},
I have tried several configuration for links but it does not display as above
require 'active_model_serializers'
module test
class SearchSerializer < ActiveModel::Serializer
has_one :foo, data: true, links: {self: true, related: true}
type 'search'
end
end
I want to respect the jsonapi format
Is anybody with a good example of active model serializer and json_api showing "links" as shwon on above json?
At the moment only the following is displayed
{"data": {
"id": "1234",
"type": "search",
"relationships": {
"foo": {
"data": [
{
"id": "12",
"type": "foo"
}
]
}
},
Note also that I am trying to do that outside the rails framework.
Thanks
Sorry to answer now, but if it is still of anyone interests...
It is quite simple really. First of all it's important to notice that JSON:API specification tell us that the related link should be their URL extension and it's better to show that path through the Search(for that case specific) path, for example: http://localhost:3000/searches/:search_id/foo.
So our SearchSerializer should be something like:
class SearchSerializer < ActiveModel::Serializer
# The attributes
attributes :id, :whatever, :something, :another_one
has_one :foo do
link(:related) { contact_foo_url(object.id) }
end
end
Note also that at this point you should include the routes and the controller show method, as similiar to the bellow:
For the routes.rb:
Rails.application.routes.draw do
resources :searches do
resource :foo, only: [:show]
# Also a best practice to dispose a 'relationships' path for this kinda example
resource :foo, only: [:show], path: 'relationships/foo'
end
end
And for the FoosController.rb:
class FoosController < ApplicationController
before_action :set_search
# GET /searches/1/foo
def show
render json: #search.foo
end
private
# Use callbacks to share common setup or constraints between actions.
def set_search
#search = Search.find(params[:search_id])
end
end
Going off of FredyK's answer, JSONAPI-SERIALIZER, is a lightweight no-rails serializer for your ruby objects (even though it does have rails integration if desired) which could be a much simpler solution than using Active Record.
Also if you are not using rails, JSONAPI-SERIALIZER pairs really well with the new gem EASY-JSONAPI, which is a middleware, parser, and response validator for JSON:API requests and responses.
After looking what is available in ruby for JSONAPI without rails, I ended using the gem JSONAPI-serializers, It is much easier to set and lighter to load (less dependencies). This fit better with PORO
My serializer becomes
require_relative ./common_serializer
module JsonSerializer
class SearchSerializer < CommonSerializer
attributes :foo, include_links: true
def relationship_related_link(attribute_name)
nil
end
end
end
This gem is much easier to use as the methods which create the json can be changed in the serializer (or in a commonClass)

Api errors customization for Rails 3 like Github api v3

I am adding an API on a Rails3 app and its pretty going good.
But I saw the following Github api v3 at http://developer.github.com/v3/
HTTP/1.1 422 Unprocessable Entity
Content-Length: 149
{
"message": "Validation Failed",
"errors": [
{
"resource": "Issue",
"field": "title",
"code": "missing_field"
}
]
}
I liked the error messages structure. But couldn't get it to reproduce.
How can I make my apis to make the response like it?
You could quite easily achieve that error format by adding an ActionController::Responder for your JSON format. See http://api.rubyonrails.org/classes/ActionController/Responder.html for the (extremely vague) documentation on this class, but in a nutshell, you need to override the to_json method.
In the example below I'm calling a private method in an ActionController:Responder which will construct the json response, including the customised error response of your choice; all you have to do is fill in the gaps, really:
def to_json
json, status = response_data
render :json => json, :status => status
end
def response_data
status = options[:status] || 200
message = options[:notice] || ''
data = options[:data] || []
if data.blank? && !resource.blank?
if has_errors?
# Do whatever you need to your response to make this happen.
# You'll generally just want to munge resource.errors here into the format you want.
else
# Do something here for other types of responses.
end
end
hash_for_json = { :data => data, :message => message }
[hash_for_json, status]
end

Resources