Response body not being serialized - ruby-on-rails

I have an ActiveModel Serializer that changes the fields names to lowerCamelCase. But It's not working on my response.body when I try test it on rspec
ActiveModel::Serializer.setup do |config|
config.key_format = :lower_camel
end
class DevelopmentAgentsSerializer < ActiveModel::Serializer
attributes :id, :name, :email, :created_at, :updated_at, :phone
end
class DevelopmentAgentsController < ApplicationController
def index
#development_agents = DevelopmentAgent.all
render json: #development_agents
end
end
it "returns a list of development agents" do
get :index, format: :json
expect(JSON.parse(response.body)).to eq(JSON.parse({development_agents: serialized_development_agent}.to_json))
end
expected: {"development_agents"=>[{"id"=>3, "name"=>"Some name", "email"=>nil, "createdAt"=>"2019-08-06T17:30:47.372-03:00", "updatedAt"=>"2019-08-06T17:30:47.372-03:00", "phone"=>"(21)999999999"}]}
got:
{"development_agents"=>[{"id"=>3, "name"=>"Some name", "email"=>nil, "created_at"=>"2019-08-06T17:30:47.372-03:00", "updated_at"=>"2019-08-06T17:30:47.372-03:00", "phone"=>"(21)999999999"}]}

By convention, the serializer's name is singular, i.e. DevelopmentAgentSerializer instead of DevelopmentAgentsSerializer (Don't forget to also change the file name). If this convention is not followed, the serializer you defined won't be used, and the response will just be #development_agents.as_json

Related

Serializing Nested Attributes Active Model Serializer

I have the following code and can't seem to get my JSON to output as per my serializer.
I receive the following log [active_model_serializers] Rendered SimpleJobSerializer with Hash
My controller is as below:
# Jobs Controller
def home
return_limit = 2
#dev_jobs = Job.where(category: 'developer').limit(return_limit)
#marketing_jobs = Job.where(category: 'marketing').limit(return_limit)
#sales_jobs = Job.where(category: 'sales').limit(return_limit)
#jobs = {
developer: #dev_jobs,
marketing: #marketing_jobs,
sales: #sales_jobs
}
render json: #jobs, each_serializer: SimpleJobSerializer
end
And my serializer:
class SimpleJobSerializer < ActiveModel::Serializer
attributes :title, :company, :job_type, :date
def date
d = object.created_at
d.strftime("%d %b")
end
end
I am receiving the full API response but expect to only receive title, company, job_type and date.
It's worth mentioning the jobs model is completely flat and there are currently no associations to take into account. It seems to be just the nesting of the jobs into the #jobs object that's stopping serialization.
Any help would be much appreciated.
each_serializer expects you to pass an array but here you're passing a hash:
#jobs = {
developer: #dev_jobs,
marketing: #marketing_jobs,
sales: #sales_jobs
}
Since you want that structure, I'd recommend two approachs depending on which you prefer. One is to change the serializer which should control the format:
class JobsSerializer < ActiveModel::Serializer
attributes :developer, :marketing, :sales
def developer
json_array(object.where(category: "developer"))
end
def marketing
json_array(object.where(category: "marketing"))
end
def sales
json_array(object.where(category: "sales"))
end
def json_array(jobs)
ActiveModel::ArraySerializer.new(jobs, each_serializer: SimpleJobSerializer)
end
end
You can still leave your current serializer as is:
class SimpleJobSerializer < ActiveModel::Serializer
attributes :title, :company, :job_type, :date
def date
d = object.created_at
d.strftime("%d %b")
end
end
Or option 2 would be to do this in the controller:
#jobs = {
developer: ActiveModel::ArraySerializer.new(#dev_Jobs, each_serializer: SimpleJobSerializer),
marketing: ActiveModel::ArraySerializer.new(#marketing_jobs, each_serializer: SimpleJobSerializer),
sales: ActiveModel::ArraySerializer.new(#sales_jobs, each_serializer: SimpleJobSerializer)
}
render json: #jobs

Rails single table inheritance validation

There is a Request model in my app. On different pages I need different validations, for example on /contacts I need to validate a lot of fields, whereas in a 'call me back later' popup I need to validate only phone number and name.
My problem is: data is saved, but without validations and type is not saved aswell.
Structure:
request.rb
class Request < ApplicationRecord
self.inheritance_column = :_type_disabled
def self.types
%w(ContactRequest CallMeBackRequest)
end
scope :contacts, -> { where(type: 'ContactRequest') }
scope :callmebacks, -> { where(type: 'CallMeBackRequest') }
end
routes.rb:
resources :contact_requests, only: [:new, :create], controller: 'requests', type: 'ContactRequest'
resources :call_me_back_requests, only: [:new, :create], controller: 'requests', type: 'CallMeBackRequest'
contact_request.rb:
class ContactRequest < Request
validates :name, :phone, :email, :company_name, presence: true
def self.sti_name
"ContactRequest"
end
end
call_me_back_request.rb:
class CallMeBackRequest < Request
validates :name, :phone, presence: true
def self.sti_name
"CallMeBack"
end
end
requests_controller.rb:
class Front::RequestsController < FrontController
before_action :set_type
def create
#request = Request.new(request_params)
respond_to do |format|
if #request.save
format.js
else
format.js { render partial: 'fail' }
end
end
end
private
def set_request
#request = type_class.find(params[:id])
end
def set_type
#type = type
end
def type
Request.types.include?(params[:type]) ? params[:type] : "Request"
end
def type_class
type.constantize
end
def request_params
params.require(type.underscore.to_sym).permit(Request.attribute_names.map(&:to_sym))
end
end
My form starts with:
=form_for Request.contacts.new, format: 'js', html: {class: 'g-contact__sidebar-right g-form'}, remote: true do |f|
I tried using ContactRequest.new - result was the same.
What I get when I hit the console:
Request.contacts.create!(name: "something") - does get saved, no validations are applied (why?). No type field is populated - why?
ContactRequest.create!(name: "something") - does not get saved, validations are applied
ContactRequest.create!(name: ..., all other required fields) - does get saved, but field type is empty - why?
Whatever I use for my form - ContactRequest.new or Request.contacts.new - neither validations are applied nor field type is set correctly.
Can anyone point me in the right direction? I'm mainly using this tutorial and other SO question, but without success.
Figured it out - since I'm not using the dedicated pages and paths for those contacts, i.e. contact_requests_path and corresponding new.html.haml, I need to pass the type parameter as a hidden field.
So my form now looks like this:
=form_for ContactRequest.new, format: 'js', html: {class: 'g-contact__sidebar-right g-form'}, remote: true do |f|
=f.hidden_field :type, value: "ContactRequest"
Considering validations - I don't know what I did, but after restarting the server a few times, they work now. The only this I remember really changing was the sti name here:
class CallMeBackRequest < Request
validates :name, :phone, presence: true
def self.sti_name
"CallMeBack" <- changed it from "CallMeBack" to "CallMeBackRequest"
end
end

Rails: return model (not bound to active record) as xml

I have a model class that is not bound to Active record.
class ProcessingStatus
attr_accessor :status, :timestamp
end
The model acts as a processing status holder and will eventually be returned to the calling method.
Since this is invoked as an active resource method, this needs to go back (serialized) as xml.
Here is my action method:
def activate
#process_status = ProcessingStatus.new
if Account.activate(params[:account])
#process_status.status = "success"
else
#process_status.status = "fail"
end
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #process_status }
end
end
This doesn't seem to return a valid xml though.
If I try and output the #process_status like below
return render :text => "The object is #{#process_status}"
this is what I get:
The object is #<ProcessingStatus:0x00000005e98860>
Please tell me what I am missing.
Edit #1,
Based on the comment below, I modified my code to include the serialization libraries.
class ProcessingStatus
include ActiveModel::Serialization
include ActiveModel::Serializers::JSON
include ActiveModel::Serializers::Xml
attr_accessor :status
def attributes
#attributes ||= {'status' => 'nil'}
end
end
I am getting closer:) Now get the output as follows for .xml request.
but the value that I assigned is not reflected.
#process_status.status = "success" / "fail"
<processing-status><status>nil</status></processing-status>
but when i make a json request, it is appearing correct!
{"processing_status":{"status":"success"}}
You need to define method to_xml in your model, or include Serialization module as below:
class ProcessingStatus
include ActiveModel::Serialization
attr_accessor :status, :timestamp
end
Here you've got more info: http://api.rubyonrails.org/classes/ActiveModel/Serialization.html

How to validate if a string is json in a Rails model

I'm building a simple app and want to be able to store json strings in a db. I have a table Interface with a column json, and I want my rails model to validate the value of the string. So something like:
class Interface < ActiveRecord::Base
attr_accessible :name, :json
validates :name, :presence => true,
:length => { :minimum => 3,
:maximum => 40 },
:uniqueness => true
validates :json, :presence => true,
:type => json #SOMETHING LIKE THIS
:contains => json #OR THIS
end
How do I do that?
I suppose you could parse the field in question and see if it throws an error. Here's a simplified example (you might want to drop the double bang for something a bit clearer):
require 'json'
class String
def is_json?
begin
!!JSON.parse(self)
rescue
false
end
end
end
Then you could use this string extension in a custom validator.
validate :json_format
protected
def json_format
errors[:base] << "not in json format" unless json.is_json?
end
Currently (Rails 3/Rails 4) I would prefer a custom validator. Also see https://gist.github.com/joost/7ee5fbcc40e377369351.
# Put this code in lib/validators/json_validator.rb
# Usage in your model:
# validates :json_attribute, presence: true, json: true
#
# To have a detailed error use something like:
# validates :json_attribute, presence: true, json: {message: :some_i18n_key}
# In your yaml use:
# some_i18n_key: "detailed exception message: %{exception_message}"
class JsonValidator < ActiveModel::EachValidator
def initialize(options)
options.reverse_merge!(:message => :invalid)
super(options)
end
def validate_each(record, attribute, value)
value = value.strip if value.is_a?(String)
ActiveSupport::JSON.decode(value)
rescue MultiJson::LoadError, TypeError => exception
record.errors.add(attribute, options[:message], exception_message: exception.message)
end
end
The best way is to add a method to the JSON module !
Put this in your config/application.rb :
module JSON
def self.is_json?(foo)
begin
return false unless foo.is_a?(String)
JSON.parse(foo).all?
rescue JSON::ParserError
false
end
end
end
Now you'll be enable to use it anywhere ('controller, model, view,...'), just like this :
puts 'it is json' if JSON.is_json?(something)
I faced another problem using Rails 4.2.4 and PostgreSQL adapter (pg) and custom validator for my json field.
In the following example:
class SomeController < BaseController
def update
#record.json_field = params[:json_field]
end
end
if you pass invalid JSON to
params[:json_field]
it is quietly ignored and "nil" is stored in
#record.json_field
If you use custom validator like
class JsonValidator < ActiveModel::Validator
def validate(record)
begin
JSON.parse(record.json_field)
rescue
errors.add(:json_field, 'invalid json')
end
end
end
you wouldn't see invalid string in
record.json_field
only "nil" value, because rails does type casting before passing your value to validator. In order to overcome this, just use
record.json_field_before_type_cast
in your validator.
If you don't fancy enterprise-style validators or monkey-patching the String class here's a simple solution:
class Model < ApplicationRecord
validate :json_field_format
def parsed_json_field
JSON.parse(json_field)
end
private
def json_field_format
return if json_field.blank?
begin
parsed_json_field
rescue JSON::ParserError => e
errors[:json_field] << "is not valid JSON"
end
end
end
Using JSON parser, pure JSON format validation is possible. ActiveSupport::JSON.decode(value) validates value "123" and 123 to true. That is not correct!
# Usage in your model:
# validates :json_attribute, presence: true, json: true
#
# To have a detailed error use something like:
# validates :json_attribute, presence: true, json: {message: :some_i18n_key}
# In your yaml use:
# some_i18n_key: "detailed exception message: %{exception_message}"
class JsonValidator < ActiveModel::EachValidator
def initialize(options)
options.reverse_merge!(message: :invalid)
super(options)
end
def validate_each(record, attribute, value)
if value.is_a?(Hash) || value.is_a?(Array)
value = value.to_json
elsif value.is_a?(String)
value = value.strip
end
JSON.parse(value)
rescue JSON::ParserError, TypeError => exception
record.errors.add(attribute, options[:message], exception_message: exception.message)
end
end
The most simple and elegant way, imo. The top upvoted answers will either return true when passing a string containing integers or floats, or throw an error in this case.
def valid_json?(string)
hash = Oj.load(string)
hash.is_a?(Hash) || hash.is_a?(Array)
rescue Oj::ParseError
false
end

globalize2 with xml/json support

I'm implementing a distributed application, server with rails and mobile clients in objective c (iPhone). To enable internationalization, I use the rails plugin 'globalize2' by joshmh.
However, it turned out that this plugin does not translate attributes when calling to_xml or to_json on an ActiveRecord. Does anyone know of a workaround / patch? Do you have any ideas how to fix this, where to alter globalize2?
Using:
Rails 2.3.5
globalize2: commit from 2010-01-11
With Globalize2 (and with model_translations as well) translated attribute in a model is not a real attribute but is a method. Thus and so when you execute to_json method you can use :methods, as Joris suggested, but in a simpler way:
class Post < ActiveRecord::Base
attr_accessible :title, :text
translates :title, :text
end
class PostsController < ApplicationController
def index
#posts = Post.all
respond_to do |format|
format.html
format.json { render :json => { :posts => #posts.to_json(:only => :id, :methods => :title) }}
format.js
end
end
end
Here I would like to receive only post id and title in json response. For additional information see to_json (Serialization) in Rails API.
I found this fork on github: http://github.com/leword/globalize2
But it looks like it is based on an older version.
I was looking for this myself, but solved my problem using the :methods option:
If you want to translate one attribute in #item, you can use:
class Item < ActiveRecord::Base
translates :name
def t_name
self.name
end
end
And in your controller:
render :text => #item.to_xml(:methods => [ :t_name ])
If your api path is something like /en/api/item.xml, you should get the english translation in the t_name attribute
For a belongs_to relation:
belongs_to :category
def category_name
self.category.name
end
And in your controller:
render :text => #item.to_xml(:methods => [ :category_name ])
Your use case is probably different. Above is a workaround that works for me.

Resources