Serializing Nested Attributes Active Model Serializer - ruby-on-rails

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

Related

Problem with creating two objects with one controller action with ruby on rails

I am trying to send one post to a create in a controller and create two objects. For some reason the contoller only will create a company. I did a similair thing in sinatra and it worked. I know that the route is correct and so is the object the post sends.
Conrtoller:
def index
stocks = current_user.stocks
render json: stocks, include: ['company']
end
def create
company = Comapny.find_or_create_by(name: params["company"])
stock = current_user.stocks.create(stock_params)
render json: stock, status: :created
end
Params:
def stock_params
params.require(:stock).permit(:name, :price_purchased_at, :number, :info, :restaurant )
end
Serializer:
class StockSerializer < ActiveModel::Serializer
attributes :id, :name, :price_purchased_at, :info, :number, :company
belongs_to :user
belongs_to :company
end
I have tried changing the serializer and the params. I have also tried leaving out the company create line to see if it will create the stock but it still won't create a stock.
You ensure in your create that a company with the expected name exists. But you do not pass the found company to the stock creation method. Therefore, the stock creation fails with Company must exist.
Just change your create method to this:
def create
company = Company.find_or_create_by(name: params['company'])
stock = current_user.stocks.create!(stock_params.merge(company_id: company.id))
render json: stock, status: :created
end

ActiveModel Serializer - Passing params to serializers

AMS version: 0.9.7
I am trying to pass a parameter to an ActiveModel serializer without any luck.
My (condensed) controller:
class V1::WatchlistsController < ApplicationController
def index
currency = params[:currency]
#watchlists = Watchlist.belongs_to_user(current_user)
render json: #watchlists, each_serializer: WatchlistOnlySerializer
end
My serializer:
class V1::WatchlistOnlySerializer < ActiveModel::Serializer
attributes :id, :name, :created_at, :market_value
attributes :id
def filter(keys)
keys = {} if object.active == false
keys
end
private
def market_value
# this is where I'm trying to pass the parameter
currency = "usd"
Balance.watchlist_market_value(self.id, currency)
end
I am trying to pass a parameter currency from the controller to the serializer to be used in the market_value method (which in the example is hard-coded as "usd").
I've tried #options and #instance_options but I cant seem to get it work. Not sure if its just a syntax issue.
AMS version: 0.10.6
Any options passed to render that are not reserved for the adapter are available in the serializer as instance_options.
In your controller:
def index
#watchlists = Watchlist.belongs_to_user(current_user)
render json: #watchlists, each_serializer: WatchlistOnlySerializer, currency: params[:currency]
end
Then you can access it in the serializer like so:
def market_value
# this is where I'm trying to pass the parameter
Balance.watchlist_market_value(self.id, instance_options[:currency])
end
Doc: Passing Arbitrary Options To A Serializer
AMS version: 0.9.7
Unfortunately for this version of AMS, there is no clear way of sending parameters to the serializer. But you can hack this using any of the keywords like :scope (as Jagdeep said) or :context out of the following accessors:
attr_accessor :object, :scope, :root, :meta_key, :meta, :key_format, :context, :polymorphic
Though I would prefer :context over :scope for the purpose of this question like so:
In your controller:
def index
#watchlists = Watchlist.belongs_to_user(current_user)
render json: #watchlists,
each_serializer: WatchlistOnlySerializer,
context: { currency: params[:currency] }
end
Then you can access it in the serializer like so:
def market_value
# this is where I'm trying to pass the parameter
Balance.watchlist_market_value(self.id, context[:currency])
end
Try using scope in controller:
def index
#watchlists = Watchlist.belongs_to_user(current_user)
render json: #watchlists, each_serializer: WatchlistOnlySerializer, scope: { currency: params[:currency] }
end
And in your serializer:
def market_value
Balance.watchlist_market_value(self.id, scope[:currency])
end
You can send your params to your serializer like this
render json: #watchlists, each_serializer: WatchlistOnlySerializer, current_params: currency
and in your serializer you can use this to get the value
serialization_options[:current_params]

Rendering json from two models in Rails index

I would like to render json in an index method that joins data from a foreign-key related table in Rails. I have a model that has user_id as a foreign key.
class Batch < ActiveRecord::Base
belongs_to :user
I am then rendering that table and exposing the user_id field.
def index
panels = Batch.where("user_id = #{current_user.id} or (user_id <> #{current_user.id} and public_panel = true)")
render json: panels, only: [:id, :description, :name, :public_panel, :updated_at, :user_id], root: false
end
What I would like to do is to somehow expose the users name, which is on the Users model. ie: user_id.user_name
EDIT
From reading documentation, I think I need to use include but would also like to alias one of the fields. I have a field called name in both tables.
Something is wrong with this include
def index
panels = Batch.include([:users]).where("user_id = #{current_user.id} or (user_id <> #{current_user.id} and public_panel = true)")
render json: panels, only: [:id, :name, :description, :public_panel, :updated_at, :user_id], root: false
end
EDIT2
Thank you #Paritosh Piplewar ... I am getting some errors with the syntax. To complicate matters the field I am after is user.name, not user.user_name. This will conflict with batches.name, so I need to alias it.
Started GET "/api/batches" for 10.0.2.2 at 2014-08-13 15:39:03 +0200
SyntaxError (/home/assay/assay/app/controllers/api/batches_controller.rb:11: syntax error, unexpected tLABEL
user: { only: :first_name },
^
/home/assay/assay/app/controllers/api/batches_controller.rb:11: syntax error, unexpected ',', expecting keyword_end
user: { only: :first_name },
^
/home/assay/assay/app/controllers/api/batches_controller.rb:12: syntax error, unexpected ',', expecting keyword_end):
EDIT 3
The original question has been answered, but the json returned is like this
data: [{"id"=>1306, "updated_at"=>Wed, 13 Aug 2014 12:37:23 UTC +00:00, "description"=>"asc", "user_id"=>1, "public_panel"=>true, "user"=>{"name"=>"Joe Bloggs"}}, {"id"=>1307,
This bit is causing problems for my Angular.js front-end.
"user"=>{"name"=>"Joe Bloggs"}}
i assume you mean user.user_name
this is how you can do it
def index
panels = Batch.where("user_id = #{current_user.id} or (user_id <> #{current_user.id} and public_panel = true)")
data = panels.as_json(include:
{user: { only: :name }},
only: [:id, :description, :public_panel, :updated_at, :user_id],
root: false)
render json: data
end
You can define custom method and do it. Something like this
class Batch < ActiveRecord::Base
belongs_to :user
def user_name
user.name
end
end
Now, in controller you can simply do this
def index
panels = Batch.where("user_id = ? or (user_id <> ? and public_panel = true)", current_user.id, current_user.id)
data = panels.as_json(methods: :user_name,
only: [:id, :description, :public_panel, :updated_at, :user_id, :user_name],
root: false)
render json: data
end
For more complex json views than just redering a single model, I'd tell you to use jbuilder. Jbuilder is now part of the rails framework.
It's as easy as
Remove render line and make panels an instance variavle (#panels)
Create a index.json.jbuilder file under app/views/api/batches
Create the view you want
json.array #panels do |panel|
panel.(panel,
:id,
:description,
:public_panel,
:user_id,
:updated_at
)
json.user panel.user.user_name
end

ActiveRecord::RecordNotFound - in a descendant class' associated_controller#index

I am attempting to locate a parent object in a nested controller, so that I can associate the descendant resource with the parent like so:
# teams_controller.rb <snippet only>
def index
#university = Univeresity.find(params[:university_id])
#teams = #university.teams
end
When I call find(params[:university_id]) per the snippet above & in line 6 of teams_controller.rb, I receive ActiveRecord::RecordNotFound - Couldn't find University without an ID.
I'm not only interested in fixing this issue, but would also enjoy a better understanding of finding objects without having to enter a University.find(1) value, since I grant Admin the privilege of adding universities.
The Rails Guides say the following about the two kinds of parameters in a website:
3 Parameters
You will probably want to access data sent in by the user or other
parameters in your controller actions. There are two kinds of
parameters possible in a web application. The first are parameters
that are sent as part of the URL, called query string parameters. The
query string is everything after “?” in the URL. The second type of
parameter is usually referred to as POST data. This information
usually comes from an HTML form which has been filled in by the user.
It’s called POST data because it can only be sent as part of an HTTP
POST request. Rails does not make any distinction between query string
parameters and POST parameters, and both are available in the params
hash in your controller:
It continues a little further down, explaining that the params hash is an instance of HashWithIndifferentAccess, which allows usage of both symbols and strings interchangeably for the keys.
From what I read above, my understanding is that Rails recognizes both parameters (URL & POST) and stores them in the same hash (params).
Can I pass the params hash into a find method in any controller action, or just the create/update actions? I'd also be interested in finding a readable/viewable resource to understand the update_attributes method thats called in a controller's 'update' action.
Please overlook the commented out code, as I am actively searching for answers as well.
Thanks in advance.
Here are the associated files and server log.
Webrick
teams_controller.rb
class TeamsController < ApplicationController
# before_filter :get_university
# before_filter :get_team
def index
#university = University.find(params[:univeristy_id])
#teams = #university.teams
end
def new
#university = University.find(params[:university_id])
#team = #university.teams.build
end
def create
#university = University.find(params[:university_id])
#team = #university.teams.build(params[:team])
if #team.save
redirect_to [#university, #team], success: 'Team created!'
else
render :new, error: 'There was an error processing your team'
end
end
def show
#university = University.find(params[:university_id])
#team = #university.teams.find(params[:id])
end
def edit
#university = University.find(params[:university_id])
#team = #university.teams.find(params[:id])
end
def update
#university = University.find(params[:university_id])
#team = #university.teams.find(params[:id])
if #team.update_attributes(params[:team])
redirect_to([#university, #team], success: 'Team successfully updated')
else
render(:edit, error: 'There was an error updating your team')
end
end
def destroy
#university = University.find(params[:university_id])
#team = #university.teams.find(params[:id])
#team.destroy
redirect_to university_teams_path(#university)
end
private
def get_university
#university = University.find(params[:university_id]) # can't find object without id
end
def get_team
#team = #university.teams.find(params[:id])
end
end
team.rb
class Team < ActiveRecord::Base
attr_accessible :name, :sport_type, :university_id
has_many :home_events, foreign_key: :home_team_id, class_name: 'Event'
has_many :away_events, foreign_key: :away_team_id, class_name: 'Event'
has_many :medias, as: :mediable
belongs_to :university
validates_presence_of :name, :sport_type
# scope :by_university, ->(university_id) { where(team_id: team_id).order(name: name) }
# scope :find_team, -> { Team.find_by id: id }
# scope :by_sport_type, ->(sport_type) { Team.where(sport_type: sport_type) }
# scope :with_university, joins: :teams
# def self.by_university(university_id)
# University.where(id: 1)
# University.joins(:teams).where(teams: { name: name })
# end
def self.by_university
University.where(university_id: university_id).first
end
def self.university_join
University.joins(:teams)
end
def self.by_sport_type(sport_type)
where(sport_type: sport_type)
end
def self.baseball
by_sport_type('Baseball/Softball')
end
end
university.rb
class University < ActiveRecord::Base
attr_accessible :address, :city, :name, :state, :url, :zip
has_many :teams, dependent: :destroy
validates :zip, presence: true, format: { with: /\A\d{5}(-\d+)?\z/ },
length: { minimum: 5 }
validates_presence_of :name, :address, :city, :state, :url
scope :universities, -> { University.order(name: 'ASC') }
# scope :by_teams, ->(university_id) { Team.find_by_university_id(university_id) }
# scope :team_by_university, ->(team_id) { where(team_id: team_id).order(name: name)}
def sport_type
team.sport_type
end
end
views/teams/index.html.erb
Placed in gists for formatting reasons
rake routes output: (in a public gist)
enter link description here
rails console
You're not going to want to have both:
resources :universities #lose this one
resources :universities do
resources :teams
end
As for params... you have to give a param. So, when you go to http://localhost:3000/teams there are no params, by default. If you go to http://localhost:3000/teams/3 then params[:id] = 3 and this will pull up your third team.
Keep in mind the nomenclature of an index. The index action of Teams, is going to list all of the teams. All of them. There is no one University there, so what are you actually trying to find? If anything, you'd have, for your University controller:
def show
#university = University.find(params[:id])
#teams = #university.teams
end
so, the address bar will be showing http://localhost:3000/universities/23, right? params[:id] = 23, then you can find the teams associated with that university.

Passing instance variables into API in rails

I'm trying to pass in some instance variables to call an API with that specific object's attributes. A user fills in their car details (make, model, and year) which creates an offer object. That is supposed to be passed into Edmund's API to retrieve the info for that car. The code works fine if I set it with a specific make/model/year but I can't make it return info for a created offer object.
Here's my controller:
def show
#offer = Offer.find(params[:id])
#wanted_ad = WantedAd.find(params[:wanted_ad_id])
#make = #offer.ownermake
#model = #offer.ownermodel
#year = #offer.owneryear
respond_to do |format|
format.html # show.html.erb
format.json { render json: #offer }
end
end
And here's my model:
class Offer < ActiveRecord::Base
attr_accessible :user_id, :wanted_ad_id, :estvalue, :image1, :offerprice, :ownercartype, :ownerdesc, :ownermake, :ownermileage, :ownermodel, :owneryear
belongs_to :user
belongs_to :wanted_ad
has_one :car
def self.carsearch
#car = []
carinfo = HTTParty.get("http://api.edmunds.com/v1/api/vehicle/#{make}/#{model}/#{year}?api_key=qd4n48eua7r2e59hbdte5xd6&fmt=json")
carinfo["modelYearHolder"].each do |p|
c = Car.new
c.make = p["makeName"]
return carinfo
end
end
end
My car model is simply:
class Car < ActiveRecord::Base
attr_accessible :make, :model, :year
belongs_to :offer
end
And I'm trying to call it from a view file with <%= Offer.carsearch %>. I'm probably all sorts of messed up but this is my first time working with an API and I'm very lost.
I think you got several logical errors in your carsearch method:
You're fetching a carinfo, iterate through an array, instantiate a new car but nothing happens with the c object and at the end of the first iteration you exit the whole function returning the retrieved carinfo...
Is this probably what you've meant?
def carsearch
#cars = []
# where do `make`, `model` and `year` come from here?
# probably method parameters!?
carinfo = HTTParty.get("http://api.edmunds.com/v1/api/vehicle/#{make}/#{model}/#{year}?api_key=qd4n48eua7r2e59hbdte5xd6&fmt=json")
carinfo["modelYearHolder"].each do |p|
c = Car.new
c.make = p["makeName"]
# initialize other attributes (year, model)?
#cars << c
end
return #cars
end

Resources