Rails nested association is not saving - ruby-on-rails

I’m trying to save a nested association, but for some reason it’s not working. Does anyone see anything wrong with this?
Order:
class Order < ApplicationRecord
has_many :replenishable_products, through: :order_items
has_many :order_items
accepts_nested_attributes_for :order_items
end
OrderItem
class OrderItem < ApplicationRecord
belongs_to :order
has_one :replenishable_product
accepts_nested_attributes_for :replenishable_product
end
ReplenishableProduct
class ReplenishableProduct < ActiveRecord::Base
belongs_to :order_item
end
Order Controller
def create
#order = #customer.orders.new(order_params)
authorize #order
if #order.save
render json: #order, serializer: ::V1::OrderSerializer, status: :created
else
render json: #order.errors, status: :unprocessable_entity
end
end
def order_params
params.require(:order).permit(order_items_attributes: [:quantity, :product_id, :selling_unit_of_measure_id,
replenishable_product_attributes: [:product_id, :salesperson_id,
:customer_id, :replenishable_date]])
end
Example request data:
```
{"order"=><ActionController::Parameters {"additional_tax_total"=>"8.27", "basket_id"=>"1", "custom_order_number"=>"pf000", "customer_attributes"=>{"contact_name"=>"Jason Test", "email"=>"new#test.com", "phone"=>"6095453234"}, "order_items_attributes"=>[{"product_id"=>"1", "quantity"=>"5", "replenishable_product_attributes"=>{"customer_id"=>"1", "product_id"=>"1", "replenishable_date"=>"2017-06-09T13:00:39-04:00", "salesperson_id"=>"1"}, "selling_unit_of_measure_id"=>"1"}, {"product_id"=>"2", "quantity"=>"5", "selling_unit_of_measure_id"=>"2"}], "payment_total"=>"103.31", "salesperson_id"=>"1", "shipping_address"=>"Test", "special_instructions"=>"test"} permitted: false>, "customer_id"=>"1", "format"=>"json", "controller"=>"api/v1/orders", "action"=>"create"}
```
So every other association is saving fine, but the replenishable_product is not being saved. I looked over it a few times and I don't see why it wouldn't be saving. Anyone have any ideas?

Related

Access to associations from polymorphic model

I have next models:
class Post < ActiveRecord::Base
has_many :comments, as: :commentable
end
class Article < ActiveRecord::Base
has_many :comments, as: :commentable
end
class Comment < ActiveRecord::Base
belongs_to :commentable, polymorphic: true
end
Now I can get posts and articles via #comment.commentable, but I need via #comment.post or #comment.article. I found some solutions, but it's not working with nested attributes:
accepts_nested_attributes_for :article
accepts_nested_attributes_for :post
My comments_controller:
class CommentsController < ApplicationController
def create
#comment = Comment.new(comment_params)
if #comment.save!
head :ok, location: #comment
else
render json: #comment.errors, status: :unprocessable_entity
end
end
private
def comment_params
params.require(:comment).permit(
:text, text: {},
post_attributes: [:id, text: {}],
article_attributes: [:id, text: {}]
)
end
end
Post params from form like:
const params = {
comment: {
text,
post_attributes: [{
id: post ? post.id : null,
text,
}]
}
}
If post or article not exists it must be created. Or add one more model like here https://gist.github.com/runemadsen/1242485 — but I don't want to do that :(

Why does the JSON patch data from relationships not get saved via Ruby on Rails?

I have an Ruby on Rails api where the data is handled in JSON. When I want to update an entity all the attributes are getting updated persistently but changed relationships arent' getting handled correctly, the entity stays the same as before.
JSON data before and after the PATCH:
{"data":{"id":"26","type":"candidate","attributes":
{"place":"Ort","zip_code":"PLZ","address":"Adresse",
"date_of_birth":"2019-01-01T00:00:00.000Z","title":"Frau",
"first_name":"Vorname","last_name":"Nachname",
"email_address":"email#example.ch",
"confirm_terms_and_conditions":true},"relationships":
{"occupational_fields":{"data":[]}}}}
PATCH input:
Started PATCH "/candidates/26" for 127.0.0.1 at 2019-01-22
19:40:53 +0100
Processing by CandidatesController#update as JSON
Parameters: {"data"=>{"id"=>"26", "attributes"=>{"place"=>"Ort",
"zip_code"=>"PLZ", "address"=>"Adresse", "title"=>"Frau",
"first_name"=>"Vorname", "last_name"=>"Nachname",
"email_address"=>"email#example.ch",
"confirm_terms_and_conditions"=>true, "date_of_birth"=>"2019-01-
01T00:00:00.000Z"}, "relationships"=>{"occupational_fields"=>
{"data"=>[{"type"=>"occupational-fields", "id"=>“4“}]}},
"type"=>"candidates"}, "id"=>"26", "candidate"=>{}}
This are my models, Candidates and OccupationalFields are related via a has_many belongs_to_many relationship through one CandidatesOccupationalField:
class Candidate < ApplicationRecord
has_many :candidates_occupational_fields, dependent: :destroy
has_many :occupational_fields, through:
:candidates_occupational_fields, dependent: :nullify
end
class CandidatesOccupationalField < ApplicationRecord
belongs_to :candidate
belongs_to :occupational_field
end
class OccupationalField < ApplicationRecord
has_many :candidates_occupational_fields, dependent: :destroy
has_many :candidates, through: :candidates_occupational_fields,
dependent: :nullify
end
This is the used controller:
class CandidatesController < ApplicationController
before_action :set_candidate, only: %i[show update destroy]
# GET /candidates
def index
#candidates = Candidate.all
render json: CandidateSerializer.new(#candidates).serialized_json
end
# GET /candidates/1
def show
#candidate = Candidate.find(params[:id])
render json: CandidateSerializer.new(#candidate).serialized_json
end
# POST /candidates
def create
#candidate = Candidate.new(candidate_params)
if #candidate.save
render json: CandidateSerializer.new(#candidate), status: :created
else
render json: #candidate.errors, status: :unprocessable_entity
end
end
# PATCH/PUT /candidates/1
def update
#candidate = Candidate.find(params[:id])
if #candidate.update(candidate_params)
render json: CandidateSerializer.new(#candidate)
else
render status: :unprocessable_entity
end
end
# DELETE /candidates/1
def destroy
#candidate.destroy
end
private
# Use callbacks to share common setup or constraints between actions.
def set_candidate
#candidate = Candidate.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def candidate_params
params.require(:data)[:attributes]
.permit(:place, :zip_code, :address,
:date_of_birth, :title, :first_name,
:last_name, :email_address,
:confirm_terms_and_conditions,
occupational_field_ids: [])
end
end
The JSON formatting is handled by fastjsonapi, this are the used serializers:
class CandidateSerializer
include FastJsonapi::ObjectSerializer
attributes :place, :zip_code, :address, :date_of_birth,
:title, :first_name, :last_name, :email_address,
:confirm_terms_and_conditions
has_many :occupational_fields
end
class OccupationalFieldSerializer
include FastJsonapi::ObjectSerializer
attributes :field
has_many :candidates
end
Thank you for your help.
The problem was, that the used serializer fast_jsonapi can't be used as deserializer and the Rail's strong parameters can't handle the json input. It works with the gem restful-jsonapi and modified params as shown in the example of the readme of restful-jsonapi.

Rails 5 API - has_many through create action is returning 2 records although only 1 is persisted in the database

I have the following M2M through associations for these 3 models
Customer -> Residences <- Properties
Also Property model is related to Address:
class Address < ApplicationRecord
has_one :property
end
A customer will always exist before creating a Property.
A property is created by submitting an Address.
Here is the controller action, which works except on success the render always returns 2 properties (ie. basically 2 residence records).
However, only one is in the database. I understand it is related to stale objects, but cannot figure out how to solve it.
I tried adding #customer.reload and #customer.reload.residences and #customer.reload.properties but still get 2 records.
# POST /customers/:id/properties
def create
#customer = set_customer
Customer.transaction do
address = Address.find_by_place_id(address_params[:place_id])
if address.nil?
#property = #customer.properties.create
#property.address = Address.new(address_params)
if #property.save
#customer.reload
render json: #customer, status: :created
else
render json: #property.errors, status: :unprocessable_entity
end
else
# irrelevant code to the problem
end
end
end
def set_customer
Customer.find(params[:customer_id])
end
A comment on this question (from #Swaps) indicates using << instead of create can sometimes result in duplicates, but whichever way I do it I always get 2.
EDIT
I managed to force it work like this but this feels like a hack:
if #property.save
#customer = set_customer
render json: #customer, status: :created
else
** UPDATE - the models **
class Customer < ApplicationRecord
has_many :residences
has_many :properties, through: :residences
end
class Residence < ApplicationRecord
belongs_to :customer
belongs_to :property
end
class Property < ApplicationRecord
belongs_to :address
has_many :residences
has_many :customers, through: :residences
end
class Address < ApplicationRecord
has_one :property
has_one :location # ignore this, not relevant
end
You're trying to do manually what ActiveRecord can do automatically with accepts_nested_attributes_for. It even works with has_many through operations.
class Customer < ApplicationRecord
has_many: :residences, inverse_of :customer
has_many: :properties, through: :residences
accepts_nested_attributes_for :residences
end
class Residence < ApplicationRecord
belongs_to :customer
belongs_to :property
validates_presence_of :customer
validates_presence_of :property
accepts_nested_attributes_for :property
end
class Property < ApplicationRecord
has_one :address
has_many :residences
has_many :customers, through: :residences
accepts_nested_attributes_for :address
end
class Address < ApplicationRecord
belongs_to :property
end
class CustomersController < ApplicationController
def create
customer = Customer.new(customer_params)
if customer.save
redirect_to customer, notice: 'Customer saved!'
else
render :new
end
end
def customer_params
params.require(:customer).permit(
name:, ...,
residences_attributes: [
property_attributes: [
name, ...,
address_attributes: [
street, city, state, postal_code, ...
]
]
]
)
end
end
References:
https://www.pluralsight.com/guides/ruby-on-rails-nested-attributes
https://robots.thoughtbot.com/accepts-nested-attributes-for-with-has-many-through
Could you please try this?
def create
#customer = set_customer
Customer.transaction do
address = Address.find_by_place_id(address_params[:place_id])
if address.nil?
#customer.properties.new(address_params)
if #customer.save
render json: #customer, status: :created
else
render json: #customer.errors, status: :unprocessable_entity
end
else
# irrelevant code to the problem
end
end
end
I was thinking do you really need #property instance variable. Is it for your view files?
Update 1
Could you please add your Customer and Residence model as like this:
Customer model
class Customer < ApplicationRecord
has_many :residences
has_many :properties, through: :residences
end
Residence model
class Residence < ApplicationRecord
belongs_to :customer
belongs_to :property
end
The problem was stale objects in ActiveRecord versus what is in the database after saving.
The ".reload" did not work, I have to actually force ActiveRecord using my hack to force ActiveRecord to find the customer in the database again, and that forces a reload (I presume it invalidates the AR cache):
def create
#customer = set_customer
Customer.transaction do
address = Address.find_by_place_id(address_params[:place_id])
if address.nil?
#property = #customer.properties.create
#property.address = Address.new(address_params)
if #property.save!
#customer = set_customer # force reload from db
render json: #customer, status: :created
end
else
address.update!(address_params)
if #customer.properties.find_by_id(address.property.id).nil?
# although we updated the address, that is just a side effect of this action
# the intention is to create an actual residence record for this customer
#customer.properties << address.property
#customer = set_customer # force reload from db
render json: #customer, status: :created
else
#customer.errors.add(:customer, 'already has that property address')
render json: ErrorSerializer.serialize(#customer.errors), status: :unprocessable_entity
end
end
end
end
def set_customer
Customer.find(params[:customer_id])
end

nested attribute in rails

I am trying to build a small recipe website. But when I try to use nested attribute for saving my child(recipe_ingredients) table through parent(recipe) action. It only generating ids for me without any data in it. Could any one help?
Model for recipe:
class Recipe < ActiveRecord::Base
has_many :recipe_ingredients
accepts_nested_attributes_for :recipe_ingredients, allow_destroy: true
end
Model for recipe_ingredients:
class RecipeIngredient < ActiveRecord::Base
belongs_to :recipe, inverse_of: :recipe_ingredients
end
Controller for recipe:
def create
#recipe = Recipe.new(recipe_params)
#recipe.recipe_ingredients.build
***binding.pry***
if #recipe.save
render json: #recipe, status: :created, location: #recipe
else
render json: #recipe.errors, status: :unprocessable_entity
end
end
def recipe_params
params.require(:recipes)
.permit(:name, :category, instruction: [], recipe_ingredients_attributes: [:amount, :ingredient, :measure])
end
```
After I check with rails console, my recipe_params is like below
[7] pry(#<RecipesController>)> recipe_params
Unpermitted parameter: recipe_ingredients
=> {"name"=>"an example recipe", "category"=>"fry", "instructions"=>["do it", "ignore it"]}
I don't know how to solve that "Unpermitted parameter" problem. Please help~ Thank you~
Try to change params.require(:recipes) in your recipe_params method to params.require(:recipe)
Hope it helps.

Proper way to create instances of models with has_many :through relationship

I have the following two classes and I'm struggling to find information about how to properly create an event and register users. Any help would be greatly appreciated - my code is below
class UserEvents < ActiveRecord::Base
belongs_to :user
belongs_to :event
end
class User < ActiveRecord::Base
has_many :user_events
has_many :events, :through => :user_events
end
class Event < ActiveRecord::Base
has_many :user_events
has_many :attendees, :class_name => "User", :through :user_events
end
Controller action for creating an event
def create
new_event = event.new(event_params)
current_user.events << new_event
new_event.attendees << current_user
if new_event.save and current_user.save
render json: new_event, status: :ok
else
render json: { errors: "Creation failed" }, status: :unprocessable_entity
end
Am I going about this correctly? Should I be using an array of ID's to keep track of the attendees instead? I've also seen it done in ways similar to:
current_user.events.create ( ... )
I'm just not sure which approach would be best to take
You can setup your event to accept nested attributes, then created all the necessary objects with a single save. Here's an example:
class Event < ActiveRecord::Base
has_many :user_events
has_many :attendees, :class_name => "User", through: :user_outings
accepts_nested_attributes_for :user_events, allow_destroy: true
end
Then modify event_params in your controller to accept attributes for user_events. Example:
def event_params
params.permit([
:..event attributes...,
user_events_attributes: [...user event attributes here...]
])
end
If it's only ever going to be the current user going to a new event, then this is overkill. But if you're adding other users to the event at event creation time, then this is how you'd do it.
If it's just the current user, then you can modify your create action to look like:
def create
new_event = event.new(event_params)
new_event.attendees << current_user
if new_event.save and current_user.save
render json: new_event, status: :ok
else
render json: { errors: "Creation failed" }, status: :unprocessable_entity
end
Edit: You can also do accepts_nested_attributes and use attendees but you may need to use the inverse_of option in your model. See https://robots.thoughtbot.com/accepts-nested-attributes-for-with-has-many-through for more details.

Resources