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
Related
I am trying to use multiple permits in a single method similar to the following (psuedocode)
def index
model.create(
params.permit(:b, :c)
)
params.permit(:a)
end
This is my actual code
def create
params.permit(:create_special_categories)
balance_sheet = ::BalanceSheet.create!(
balance_sheet_params.merge(date: Time.zone.now.to_date, entity: #entity)
)
balance_sheet.create_special_categories if params[:create_special_categories]
render json: balance_sheet, serializer: ::Api::V3::BalanceSheetSerializer
end
def balance_sheet_params
params.permit(
:id,
:entity,
:entity_id,
:date,
:name
)
end
However, I get the following error...
ActionController::UnpermittedParameters:
found unpermitted parameter: :create_special_categories
UPDATE
my solution was to avoid strong parameters all together.
def create
balance_sheet = ::BalanceSheet.new(
date: Time.zone.now.to_date, entity: #entity
)
balance_sheet.name = params[:name]
balance_sheet.save!
balance_sheet.create_special_categories if params[:create_special_categories]
render json: balance_sheet, serializer: ::Api::V3::BalanceSheetSerializer
end
This line doesn't have any effect, params.permit are not chained or added to a previous permit, you must use the result, that is why it's almost always used in a separate method.
params.permit(:create_special_categories)
What you must do is use what that returns for your following statements
permitted_params = params.permit(:create_special_categories)
Model.create(permitted_params)
...however you really should outsource this to a special method like you already have. You will have to tweak this to your use-case obviously.
def balance_sheet_params
if params[:create_special_categories]
params.permit(:id,
:entity,
:entity_id,
:date,
:name,
:create_special_categories)
else
params.permit(
:id,
:entity,
:entity_id,
:date,
:name)
end
end
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
I have three models, Server has many maintenances and Maintenance belongs to Server and User.
create_table :maintenances do |t|
t.references :server, foreign_key: true
t.references :user, foreign_key: true
t.string :name
t.text :content
end
In console I can create records as follows:
Server.create(:hostname => "Sissy", :description => "Webserver")
Maintenance.create(:server_id => 1, :user_id => 1, :name => "Test", :content => "Test" )
My Question is: How can I do this in my Controller create action?
Problem is that :user_id is not part of the maintenance params hash, so if I write
def create
#server = Server.find(params[:id])
#maintenance = #server.maintenances.create!(maintenance_params)
end
private
def maintenance_params
params.require(:maintenance).permit(:user_id => current_user.id,
:id,
:name,
:content)
end
I'm getting
Syntax error, unexpected ',', expecting =>
...ser_id => current_user.id, :id, :name, :content, :start, :pl...
... ^):
app/controllers/maintenances_controller.rb:41: syntax error, unexpected ',', expecting =>
You can add , user_id inside your create action itself. Try this.
def create
#server = Server.find(params[:id])
params[:maintenance][:user_id] = current_user.id
#maintenance = #server.maintenances.create!(maintenance_params)
end
A good way is by passing a block to the create! method:
#maintenance = #server.maintenances.create!(maintenance_params) do |m|
m.user = current_user
end
The record is yielded to the block (before it is validated/saved).
This also works with new, create, update and update!.
But you should consider if you should be using the bang method create! here as it will raise an uncaught ActiveRecord::RecordNotValid error if any of the validations fail.
def create
#server = Server.find(params[:id])
#maintenance = #server.maintenances.new(maintenance_params) do |m|
m.user = current_user
end
if #maintenance.save
redirect_to #maintenance
else
render :new
end
end
The ActiveRecord::Persistence bang methods should only really be used in things like seed files or where a record not passing the validations is an exceptional event.
Yes, you can't do anything like that in strong_params method. Nor is it its purpose. Separate the whitelisting and default params.
I usually do it like this:
def create
#server = Server.find(params[:id])
#maintenance = #server.maintenances.create!(maintenance_params.merge(user_id: current_user.id))
end
private
def maintenance_params
params.require(:maintenance).permit(:id, :name, :content)
end
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
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.