I'm wanting to build a Rails API that creates nested records in the database.
I'm wanting to post data to the database for nested or say associated model with a has_ many association [see code below].
Is this the correct approach? Or is there any alternate approach for the update action? I want to record the users Address which will be in array of hashes in json format.
I have two models User and Address.
Association is User has_many Addresses.
My update action:
def update
#user = User.find(id: params[:id])
if #user.update(first_name: params[:first_name],email: params[:email],phone: params[:phone],password: Time.now.to_i, gstin: params[:gstin],addresses)
#user.addresses.each do |a|
a.update_attributes(first_name: params['first_name'],last_name: params['last_name'],city: params['city'],state: params['state'],phone: params['phone'],zip: params['zip'],address: params['address'])
end
render json: #user, status: 200
else
render json: { errors: #user.errors }, status: 422
end
end
thats not correct way to do
you should use accepts_nested_attributes_for
Class User
has_many :addresses
accepts_nested_attributes_for :addresses
end
for more details and example follow this
I figured it out and writing the answer for the people who get stuck.
As I was not using strong params and I needed only the address in json format, I parsed the address json like this:
def update
#user = User.find(params[:id])
if #user.update(first_name: params[:first_name],email: params[:email],phone: params[:phone], gstin: params[:gstin])
updated_addreses = JSON.parse(params[:addresses])
updated_addreses.each do |add|
#user.addresses.update(first_name: add['first_name'],city: add['city'],state: add['state'],phone: add['phone'],zip: add['zip'],address: add['address'])
end
render json: {user_id: #user.id.to_s}, status: 200
else
render json: { errors: #user.errors }, status: 422
end
end
Related
I am struggling to get this working. I have three models
Student
Classroomattnd
Classroom
Using the has_many :through relationship. All my relationships are defined correctly and I have setup the nested form using the accepts_nested_attributes.
So when creating a new student I want to select from a list of classrooms instead of creating a new classroom. The form part also works fine the part I am not getting is when I create the student it complains about the following error.
Couldn't find Classrooom with ID=3 for Student with ID=
I have searched around for few days now but can not get the answer I need to get this working.
def new
#student = Student.new
#student.classrooms.build
end
def edit
end
def create
#student = Student.new(student_params)
respond_to do |format|
if #student.save
format.html { redirect_to #student, notice: 'Student was successfully created.' }
format.json { render :show, status: :created, location: #student }
else
format.html { render :new }
format.json { render json: #student.errors, status: :unprocessable_entity }
end
end
end
Can someone help here, someone must of face this issue before?
Also in the rails console when I run the following it works:
classroom = Classroom.last
student = Student.create(name: 'Dave', classrooms:[classroom])
Your parameter handling isn't supporting nesting. You can look at request parameters in your server log or inspect the fieldnames of your generated form to be sure of your target. It's going to be something along the lines of
def student_params
params.require(:student).permit(:student => [:name, :classroom => [:id, :name]])
end
Or maybe as below. In this second case I'm not assuming everything in the form is nested under a student container. Also note the switch from classroom to classroom_attributes which is a change I have sometimes needed to make even though the form above is what the docs indicate.
def student_params
params.require(:name).permit(:classroom_attributes => [:id, :name])
end
Hopefully that gives you a notion of how to tailor your parameter definition to what your form is generating. Also note your error messages give you indication of what part of your definition is failing, eg the missing Student id in the error you quote.
I'm trying to figure out a problem I seem to keep having in setting up polymorphic associations in my Rails 4 app.
I have a project model and an address model. The associations are:
Profile
has_many :addresses, as: :addressable
accepts_nested_attributes_for :addresses, reject_if: :all_blank, allow_destroy: true
Address
belongs_to :addressable, :polymorphic => true
I previously asked this question on the same problem. I couldn't (and still can't) understand the answers in that post: Rails 4 - Polymorphic associations
This time around - I'm having a problem that is triggered when I try to update a profile by inserting an address. The error message identifies the problem as coming from the update action in the profiles controller. The update action has:
My profiles controller update action has:
def update
# successful = #profile.update(profile_params)
# Rails.logger.info "xxxxxxxxxxxxx"
# Rails.logger.info successful.inspect
# user=#profile.user
# user.update.avatar
# Rails.logger.info "prof xxxxxxxxxxxxx"
# Rails.logger.info #profile.update(profile_params)
respond_to do |format|
if #profile.update(profile_params)
format.html { redirect_to #profile }
format.json { render :show, status: :ok, location: #profile }
else
format.html { render :edit }
format.json { render json: #profile.errors, status: :unprocessable_entity }
end
end
end
The error message says:
ERROR: duplicate key value violates unique constraint "index_addresses_on_addressable_type_and_addressable_id"
DETAIL: Key (addressable_type, addressable_id)=(Profile, 1) already exists.
Does anyone know what this message means, and how to address it?
In your database, you have set a unique constraint: , you can go to the database to see what you have set by the name of "index_addresses_on_addressable_type_and_addressable_id". As the error message show, you try to update a record with value ( Profile , 1) which has already been used by another record.
To solve this issue, there are two solutions:
one is from the database side:
you need to know why there is a unique constraint about addresses. if it is not need , you can remove it from the database.
the other is ensure the (addressable_type, addressable_id) is unique before you update your data into database.
hope this can give a kind of help
I have in my Rails 5 api project two models: Places and Beacons (Place has_many Beacons, foreign key: place_id). This API accepts JSON, such as this one:
{
"place":{
"name": "bedroom"
},
"beacon":{
"SSID": "My Wi-Fi",
"BSSID": "00:11:22:33:44:55",
"RSSI": "-55"
}
}
This JSON works just fine, with these classes:
def create
#place = Place.new(place_params)
#beacon = Beacon.new(beacon_params)
if #place.save
#beacon.place_id=#place.id
if #beacon.save
render :json => {:place => #place, :beacon => #beacon}, status: :created, location: #places
else
render json: #beacon.errors, status: :unprocessable_entity
end
else
render json: #place.errors, status: :unprocessable_entity
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_place
#place = Place.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def place_params
params.require(:place).permit(:name)
end
def beacon_params
params.require(:beacon).permit(:SSID, :BSSID, :RSSI)
end
However, I want that to pass multiple Beacons objects in the same JSON in an array (even no beacons at all). How can I save then all beacons in the parameter array and generate a response containing all of them?
I'm assuming Place has_many :beacons. If so, you can use nested attributes. This lets you assign and update a nested resource, in this case Beacons. First, in your Place model:
accepts_nested_attributes_for :beacons
Then, change your place_params method in your controller to permit the beacon's nested attributes:
def place_params
params.require(:place).permit(:name, beacons_attributes: [:id, :SSID, :BSSID, :RSSI])
end
And your json:
{
"place":{
"name": "bedroom",
"beacons_attributes": [
{
"SSID": "My Wi-Fi",
"BSSID": "00:11:22:33:44:55",
"RSSI": "-55"
}, {
//more beacons here
}
]
},
}
You can have zero, one, or more beacons, and can do the same when updating as well if you include the beacons' id's.
You also won't have to create the #beacon manually, and can just do this: #place = Place.new(place_params), and it'll automatically create the beacons and associate them to the Place.
You can read more here, if you'd like more information or clarification: http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
Edit: I missed where you asked how to include the beacons in the response. The quickest way is to just include it in the json rendering:
render :json => #place.to_json(:include => :beacons)
You can use Active model serializers (https://github.com/rails-api/active_model_serializers), jbuilder (https://github.com/rails/jbuilder), or more simply just override the to_json method on your model.
I'm new to wicked form and I was following the railcast episode on wicked forms but I keep receiving this error "Couldn't find Company with 'id'=info". So I know that the problem is clearly in my controllers somewhere. I know it's something super simple that I'm just racking my brain on so I know you guys will be a giant help. Here is the code, any and all help appreciated!
Code for companies Controller:
def create
#company = Company.new(company_params)
respond_to do |format|
if #company.save
#object = #company.id
format.html { redirect_to(company_steps_path(#company)) }
format.json { render :show, status: :created, location: #company }
else
format.html { render :new }
format.json { render json: #company.errors, status: :unprocessable_entity }
end
end
end
Code for company_steps Controller:
class CompanyStepsController < ApplicationController
include Wicked::Wizard
steps :info, :address, :quote
def show
#company = Company.find(params[:id])
render_wizard
end
def update
#company = Company.where(id: params[:id])
#company.attributes = params[:company]
render_wizard #company
end
end
When you use #find and the record is not found ActiveRecord raise a ActiveRecord::RecordNotFound with a message like "Couldn't find Company with id='somevalue'".
I assume your id column is of type integer and you pass a string.
In your #show method params[:id] == 'info'.
Check your link_to, redirect_to and routes.
At some point you generate this url http://localhost:3000/company_steps/info (probably in a view).
You do a GET request on it, which match GET "/company_steps/:id" company_steps#show.
The method #show is call in the controller CompanyStepsController with params[:id] == 'info'.
As we see previously you get a ActiveRecord::RecordNotFound exception because ActiveRecord can't find the record with a id 'info'.
The error is raise in your controller, but the problem is probably in your views or in a redirect. You need a id and you pass a string.
EDIT: as discussed in comments
Ok params[:id] == 'info' is generated by wicked.
They use id to control the flow of steps.
You need to use nested routes to have rails generate something like params[:company_id].
resources :companies do
resources :steps, controller: 'companies/steps'
end
So rake routes should give you:
/companies/:company_id/steps/:id
in the controller
params[:company_id] == 42
params[:id] == 'info'
https://github.com/schneems/wicked/wiki/Building-Partial-Objects-Step-by-Step
I need to validate a field before the create method
In my _form.html.erb I have two models, one is the owner model, and the other is a model I create to have other arguments, I need to validate those arguments before getting in the create method, I can use an if, but it is not the best practice to do it.
def create
#customer = Customer.new(customer_params)
#read the city name, since this is requested by city name (string) and it shoud be "id" in the system
city = city_params()
#customer.city_id = City.find_by(name: city["name"]).id
respond_to do |format|
if #customer.save
format.html { redirect_to #customer, notice: 'Customer was successfully created.' }
format.json { render action: 'show', status: :created, location: #customer }
else
format.html { render action: 'new' }
format.json { render json: #customer.errors, status: :unprocessable_entity }
end
end
end
I need to validate the the city name, because the customer owner must have the city_id, and the _form requests the name (string), so I need to find the city but previously I need to validate the city name has a value and it exists,
How can I validate this in the model ?
If I were you, I would start out by keeping all of this logic in the controller and use a filter to find the city:
class CustomersController < ApplicationController
before_action :find_city, only: [:create, :update]
def create
#customer = Customer.new(customer_params)
#read the city name, since this is requested by city name (string) and it shoud be "id" in the system
#customer.city_id = #city.try(:id) # This returns `nil` if the city was not found
respond_to do |format|
if #customer.save
format.html { redirect_to #customer, notice: 'Customer was successfully created.' }
format.json { render action: 'show', status: :created, location: #customer }
else
format.html { render action: 'new' }
format.json { render json: #customer.errors, status: :unprocessable_entity }
end
end
end
private
def find_city
#city = City.find_by(name: params[:city][:name]) # No need for strong parameters for this
end
end
Then make sure you're validating the presence of city_id in your Customer class:
class Customer < ActiveRecord::Base
validates :city_id, presence: true
end
Later on, if you find that you need this logic to be extracted from the controller, then consider looking at creating a service object or a form object. Because this is a simple case (only 2 classes are involved), I would hold off on creating those constructs for now though. The controller layer is sufficient enough to handle this simple logic.
Why not move the logic directly into the model? I can tell you from experience that you do not want to mess your model up with tons of logic involving other model classes. Customer should not really know much about City in my opinion.
before_validate
You could use the before_validate callback in your model:
#app/models/customer.rb
Class Customer < ActiveRecord::Base
attr_accessor :city_name
before_validate :set_city
private
def set_city
city_id = City.find_by(name: city_name).id
end
end
--
Custom Validation Method
I think the bottom line is you'll be best using a custom validation method for this. You basically want to return the user to the form with an error saying "City not found" or similar; which is entirely within the remit of a custom validation method:
#app/models/customer.rb
Class Customer < ActiveRecord::Base
validate :check_city_id
private
def check_city_id
errors.add(:city_id, "City doesn't exist") unless City.try city_id
end
end
--
System
This kind of issue can be handled by simply giving the user options to select the id at input; rather than selecting by name:
#app/views/customers/new.html.erb
<%= form_for #customer do |f| %>
<%= f.select :city_id, City.all.collect {|p| [ p.name, p.id ] } %>
<% end %>
I think your method of giving the user the ability to pick a city name, and then validating in the backend is very inefficient; whilst giving the user a rigid set of options to select a buyer by city is far more robust
We have something called callbacks http://api.rubyonrails.org/classes/AbstractController/Callbacks/ClassMethods.html ..using this we can trigger our required validations in the model.
You can create your own logic of validation like example
before_create :method_name
def method_name
your logic.....example: validates :city_name ..
end