Rails: Including nested associative attributes in response - ruby-on-rails

I have three models as follows :
#Product Model
class Product < ActiveRecord::Base
belongs_to :user
has_one :address
validates :title, :description, :user_id, presence: true
validates :product_type, numericality:{:greater_than => 0, :less_than_or_equal_to => 2}, presence: true
accepts_nested_attributes_for :address
end
#Address Model
class Address < ActiveRecord::Base
belongs_to :city
belongs_to :product
def related_city
city = address.city
end
end
#City Model
class City < ActiveRecord::Base
has_many :addresses
end
I am fetching a Product but I need to include associative attributes as well in my JSON response except few attributes.
Here is what I have done so far :
def show
product = Product.find(params[:id])
render json: product.to_json(:include => { :address => {
:include => { :city => {
:only => :name } },
},:user =>{:only=>{:first_name}}}), status: 200
end
This is giving me a syntax error. If I remove the user it is working fine but I need user's name as well in response. Moreover how would I write the above code using ruby's new hash syntax?

You can solve that problem using this gem: Active Model Serializers. It will let you create serializers for each model and use them to render the formatted JSON as you want. Take a look and let me know.

The problem is on the fifth line of your show method you have a comma surrounded by curly braces. Here is the hash sans comma, in the new syntax:
def show
product = Product.find(params[:id])
render json: product.to_json(include: { address: {
include: { city: {
only: :name }}}},
user: {only:{:first_name}}), status:200
end

Related

Can't add multitenancy attribute to nested model

I'm trying to create an application with Ruby on Rails 4.2.1 and PostgreSQL.
I have the following models.
class Builder < ActiveModel
has_many :bills
has_many :bills_partials
end
class Bill < ActiveModel
belongs_to :builder
has_many :bills_partials
accepts_nested_attributes_for :bills_partial, :reject_if => :all_blank, :allow_destroy => true
end
class BillPartial < ActiveModel
belongs_to :builder
belongs_to :bills
end
and the following actions
def new
bill.bills_partials.build
end
def create
#bill = Bill.scoped_to(current_builder).new(bill_partial_params)
if #bill.save
redirect_to bills_path, flash: { success: t('flash.generic.female.created', model: Bill.model_name.human) }
else
render :new
end
end
private
def bill
#bill ||= Bill.scoped_to(current_builder).find_by(id: uuid_or_nil(params[:id])) || Bill.scoped_to(current_builder).new
end
helper_method :bill
def bill_params
params
.require(:bill)
.permit(:description,
:value,
:date,
bills_partials_attributes: [:id,
:due_date,
:valor,
:_destroy])
end
The output for the params is:
{
"description"=>"some desc",
"value"=>"123",
"date"=>"10/10/2010",
"bills_partials_attributes" =>
{
"0" =>
{
"due_date"=>"14/03/2003",
"value"=>"123"
},
"1" =>
{
"due_date"=>"14/03/2003",
"value"=>"123"
}
}
}
The thing is, I have to add current_builder.id to every model, Bill and BillPartial for instance. What is the best way to add the current_builder.id to the builder_id on the BillPartial model?
Well, after looking for something, I solved this by defining
before_create do
self.builder_id = bill.try(:builder_id)
end
On the BillPartial model.

How to create nested models from API request?

I've a Rails API and I've two models:
class Event < ActiveRecord::Base
belongs_to :category
has_many :event_categories
has_many :events, through: :event_categories
attr_accessible :title, :description, :event_categories_attributes
accepts_nested_attributes_for :event_categories
end
and
class EventCategory < ActiveRecord::Base
belongs_to :event
belongs_to :category
attr_accessible :category_id, :event_id, :principal
validates :event, :presence => true
validates :category, :presence => true
validates_uniqueness_of :event_id, :scope => :category_id
end
In a first moment, EventCategory didn't exist so I created Event resources sending params like event[title]='event1', event[description] = 'blablbla' thought POST REST request.
My API EventsController was like this (I haven't a new method because I don't need views):
def create
#event = Event.create(params[:event])
if #event
respond_with #event
else
respond_with nil, location: nil, status: 404
end
end
This way worked correctly for me. Now, with the new EventCategory model I don't know how I could create EventCategories models at the same time.
I've trying this... but it doesn't work:
def create
#event = Event.new(params[:event])
#event.event_categories.build
if #event.save
respond_with #event
else
respond_with nil, location: nil, status: 404
end
end
Rails told me:
{
"event_categories.event": [
"can't be blank"
],
"event_categories.category": [
"can't be blank"
]
}
I send the category_id like this:
event[event_categories_attributes][0][category_id] = 2
Any ideas?
In your create action, instead of this:
#event.event_categories.build
Try this:
#event.event_categories = EventCategory.new do |ec|
ec.event = #event
ec.category = the_cattegory_you_want_to_specify
# You need both of these as you are validating the presence of event AND category
end

Filtering with geokit rails - dosent show any data

I am building an API for a webbapp using Ruby On Rails, i am using Jbuilder for API/JSON and Geokit.
I have this models:
Campaign
class Campaign < ActiveRecord::Base
belongs_to :company
belongs_to :venue
belongs_to :category
has_and_belongs_to_many :venues
has_many :cities, through: :venues
has_many :uses
end
Company
class Company < ActiveRecord::Base
has_many :venues
has_many :campaigns
validates :name, presence: true
end
Venue
class Venue < ActiveRecord::Base
acts_as_mappable :default_units => :kms,
:lat_column_name => :lat,
:lng_column_name => :lng
belongs_to :company
belongs_to :city
has_and_belongs_to_many :campaigns
end
I am trying to list all campaigns by given latitude and longitude coordinates, like this:
class API::CampaignsController < ApplicationController
respond_to :json
def index
#campaigns = filter
respond_with #campaigns
end
def filter
if params[:city_id]
Campaign.includes(:venues, :cities).where('cities.id' => params[:city_id])
elsif params[:lat] && params[:lng]
lat = params[:lat].to_f
lng = params[:lng].to_f
Campaign.includes(:venues, :cities).within(10, :origin => [lat, lng])
end
end
end
But all I get is a empty hash:
{
campaigns: [ ]
}
Any ides on what I am doing wrong and how I can fix this?
As per the server log, GET "/api/campaigns?lat=55.563466?lng=12.973509", you are not passing params[:city_id] so your BOTH conditions in filter method will fail and no query would be executed. Hence, empty campaigns.
acts_as_mappable is on Venue model so within is available only for that model. I think you want filter method as below:
def filter
if params[:city_id]
Campaign.includes(:venues, :cities).where('cities.id' => params[:city_id])
elsif params[:lat] && params[:lng]
lat = params[:lat].to_f
lng = params[:lng].to_f
Venue.within(10, :origin => [lat, lng]).includes(:campaigns, :city).each do |venue|
campaigns << venue.campaigns
end
campaigns
end
end
Also, the url should be /api/campaigns?lat=55.563466&lng=12.973509.
NOTICE:
Pass lng as a separate query param with & prefix as &lng and not as ?lng

Nested objects in form not validating

I have a nested form where users can book appointments. However, I've noticed an issue with the form where a user can fill out the required Client model fields and not the required Appointment model fields and the form still submits since for some reason the validation on the Appointment model isn't being triggered. The only time the Appointment validation is triggered is when the associated form fields are populated. How do I get the nested form to verify that the Appointment fields are being filled out? Since clients can have multi
Customer model:
class Customer < ActiveRecord::Base
has_many :appointments
accepts_nested_attributes_for :appointments
attr_accessible :name, :email, :appointments_attributes
validates_presence_of :name, :email
validates :email, :format => {:with => /^[^#][\w.-]+#[\w.-]+[.][a-z]{2,4}$/i}
validates :email, :uniqueness => true
end
Appointment model:
class Appointment < ActiveRecord::Base
belongs_to :customer
attr_accessible :date
validates_presence_of :date
end
Customers controller:
class CustomersController < ApplicationController
def new
#customer = Customer.new
#appointment = #customer.appointments.build
end
def create
#customer = Customer.find_or_initialize_by_email(params[:customer])
if #customer.save
redirect_to success_customers_path
else
# Throw error
#appointment = #customer.appointments.select{ |appointment| appointment.new_record? }.first
render :new
end
end
def success
end
end
Customers form view:
= simple_form_for #customer, :url => customers_path, :method => :post, :html => { :class => "form-horizontal" } do |customer_form|
= customer_form.input :name
= customer_form.input :email
= customer_form.simple_fields_for :appointments, #appointment do |appointment_form|
= appointment_form.input :date
UPDATE: Providing routes
resources :customers, :only => [:new, :create] do
get :success, :on => :collection
end
If a customer has to have an appointment:
class Customer < ActiveRecord::Base
has_many :appointments
accepts_nested_attributes_for :appointments
attr_accessible :name, :email, :appointments_attributes
validates_presence_of :name, :email, :appointment # <- add your appointment
....
end
This will require each customer has at least one appointment.
EDIT based on comment
Instead of using build in your controller, I think you can use create instead which will then associate that appointment with the customer and force validation.
Customers controller:
def edit
#customer = Customer.find_or_initialize_by_email(params[:customer])
#appointment = #customer.appointments.create
end
And you'd do the same in your new method

"Accepts nested attributes" not actually accepting attributes in model

I'm working on a project where there are tasks that make up a scavenger hunt. When a user creates a new hunt, I'd like the hunts/show.html.erb file to show the hunt as well as the tasks associated with that hunt. But the models are giving me trouble. I've got the hunt model setup to that it accepts nested attributes for the tasks model. So when the user creates a new hunt, she also creates three tasks automatically. I can get the new hunt to save, but I can't get those new tasks to save. Here are my models.
What's missing? Do I need an "attr accessible" statement in the HunTasks.rb file?
class Hunt < ActiveRecord::Base
has_many :hunt_tasks
has_many :tasks, :through => :hunt_tasks
accepts_nested_attributes_for :tasks, :reject_if => lambda { |a| a[:content].blank? }, :allow_destroy => true
attr_accessible :name
validates :name, :presence => true,
:length => { :maximum => 50 } ,
:uniqueness => { :case_sensitive => false }
end
class Task < ActiveRecord::Base
has_many :hunt_tasks
has_many :hunts, :through => :hunt_tasks
attr_accessible :name
validates :name, :presence => true,
:length => { :maximum => 50 } ,
:uniqueness => { :case_sensitive => false }
end
class HuntTask < ActiveRecord::Base
belongs_to :hunt # the id for the association is in this table
belongs_to :task
end
Here's what my Hunt controller looks like:
class HuntsController < ApplicationController
def index
#title = "All Hunts"
#hunts = Hunt.paginate(:page => params[:page])
end
def show
#hunt = Hunt.find(params[:id])
#title = #hunt.name
#tasks = #hunt.tasks.paginate(:page => params[:page])
end
def new
if current_user?(nil) then
redirect_to signin_path
else
#hunt = Hunt.new
#title = "New Hunt"
3.times do
hunt = #hunt.tasks.build
end
end
end
def create
#hunt = Hunt.new(params[:hunt])
if #hunt.save
flash[:success] = "Hunt created!"
redirect_to hunts_path
else
#title = "New Hunt"
render 'new'
end
end
....
end
The major difference between your example and the railscast is that you are doing many-to-many instead of one to many (I think his was Survey had many Questions). Based on what you described, I wonder if the HuntTask model is necessary. Are the tasks for one hunt ever going to be resused in another hunt? Assuming they are, then looks like your answer is here:
Rails nested form with has_many :through, how to edit attributes of join model?
You'll have to modify your new action in the controller to do this:
hunt = #hunt.hunt_tasks.build.build_task
Then, you'll need to change your Hunt model to include:
accepts_nested_attributes_for :hunt_tasks
And modify your HuntTask model to include:
accepts_nested_attribues_for :hunt

Resources