Rails 4, validating nested models - ruby-on-rails-4

I have 2 models:
class Person
has_one :user
validates :document, uniqueness: true, on: :create
class User
belongs_to :person
accepts_nested_attributes_for :person
Then i have a form for users that information about the person:
<%= form_for #user do |f| %>
<%= f.fields_for :person do |person_fields| %>
<%= person_fields.label "Document" %>
<%= person_fields.text_field :document %>
<% end %>
<%= f.label "Username" %>
<%= f.text_field :username %>
<% end %>
When i go for /users/edit/1 for example, it loads the user its person attributes. If i change the username and save, it says that the document number is already at use, bypassing the person validation of uniqueness only on create.
Am i missing something here? Are validations like this not supposed to work on nested forms?

Nested attributes allow you to save attributes on associated records through the parent (has_one, has_many), not on the parent through the child (belongs_to).
Your model relationship is not correct; According to Rails api nested attributes are valid for the child through the parent, not the parent through the child as you have specified in your models below.
Your models
class Person
has_one :user
validates :document, uniqueness: true, on: :create
class User
belongs_to :person
accepts_nested_attributes_for :person (sets up nested attributes for parent)
You could set up nested attributes for the child via the parent, as such
Suggested change
class Person
has_one :user
validates :document, uniqueness: true, on: :create
accepts_nested_attributes_for :user
class User
belongs_to :person
Nesting on your form will work only if the nesting associations in the model are set up properly.
Sidebar: The beauty of :Inverse_of
This is a sidebar, but relevant in this context. Inverse_of gives you a reverse reference from the child to the parent. Rails spec for :inverse_of. You should try this out and see how it works for your app. It's a very good thing.
class Person
has_one :user, inverse_of: person
validates :document, uniqueness: true, on: :create
accepts_nested_attributes_for :user
class User
belongs_to :person, inverse_of: user
Debugging questions (added)
I added the on: :create validation to one of my models as a test. I would ask you do a similar test without the model nesting; I think it's informative to test the logic in the model for the on: create, which will validate the controller actions. And only then layer on the nesting attributes. What do you think?
Duplicate field on create => ROLLBACK
Started POST "/locations" for 127.0.0.1 at 2015-07-20 08:03:36 -0500
Processing by LocationsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"Fxxx", "location"=>{"name"=>"Foo", "food_trailer"=>"true", "street"=>"100 Main", "city"=>"Fredericksburg", "state"=>"Texas", "country"=>"US", "phone"=>"", "website"=>"", "short_desc"=>"A Texan, Ms. Diana has been cookin' up barbecue for 50 years and, spreadin' the “barbecue love” in Paris (France!) since 2010.", "known_for"=>"Tings", "meats_beef"=>"0", "meats_beef_ribs"=>"0", "meats_pork"=>"1", "meats_pork_ribs"=>"0", "meats_chicken"=>"0", "meats_turkey"=>"0", "meats_sausage"=>"0", "meats_venison"=>"0", "sauce"=>"0", "sides"=>"None"}, "commit"=>"Add Restaurant"}
[1m[36mUser Load (0.0ms)[0m [1mSELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1[0m [["id", 8]]
[1m[35m (0.0ms)[0m BEGIN
[1m[36mLocation Exists (46.9ms)[0m [1mSELECT 1 AS one FROM "locations" WHERE "locations"."short_desc" = 'A Texan, Ms. Diana has been cookin'' up barbecue for 50 years and, spreadin'' the “barbecue love” in Paris (France!) since 2010.' LIMIT 1[0m
[1m[35mLocation Exists (0.0ms)[0m SELECT 1 AS one FROM "locations" WHERE ("locations"."latitude" IS NULL AND "locations"."longitude" IS NULL) LIMIT 1
[1m[36m (46.9ms)[0m [1mROLLBACK[0m
Duplicate field on edit => COMMIT
Started PATCH "/locations/46" for 127.0.0.1 at 2015-07-20 08:04:30 -0500
Processing by LocationsController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"xxx", "location"=>{"name"=>"Grillrestaurant Rusticana", "street"=>"Grillparzerstraße 5, 81675", "city"=>"München", "state"=>"Alabama", "country"=>"US", "phone"=>"", "website"=>"", "short_desc"=>"A Texan, Ms. Diana has been cookin' up barbecue for 50 years and, spreadin' the “barbecue love” in Paris (France!) since 2010.", "known_for"=>"", "meats_beef"=>"0", "meats_beef_ribs"=>"0", "meats_pork"=>"0", "meats_pork_ribs"=>"0", "meats_chicken"=>"0", "meats_turkey"=>"0", "meats_sausage"=>"0", "meats_venison"=>"0", "sauce"=>"0", "sides"=>""}, "commit"=>"Save changes to Restaurant", "id"=>"46"}
[1m[36mLocation Load (0.0ms)[0m [1mSELECT "locations".* FROM "locations" WHERE "locations"."id" = $1 LIMIT 1[0m [["id", 46]]
[1m[35m (0.0ms)[0m BEGIN
[1m[36mLocation Exists (0.0ms)[0m [1mSELECT 1 AS one FROM "locations" WHERE ("locations"."latitude" = 48.1334073 AND "locations"."id" != 46 AND "locations"."longitude" = 11.6100255) LIMIT 1[0m
[1m[35mSQL (15.6ms)[0m UPDATE "locations" SET "short_desc" = $1, "meats_beef" = $2, "meats_pork" = $3, "meats_beef_ribs" = $4, "meats_pork_ribs" = $5, "meats_chicken" = $6, "meats_turkey" = $7, "meats_sausage" = $8, "meats_venison" = $9, "sauce" = $10, "latitude" = $11, "updated_at" = $12 WHERE "locations"."id" = $13 [["short_desc", "A Texan, Ms. Diana has been cookin' up barbecue for 50 years and, spreadin' the “barbecue love” in Paris (France!) since 2010."], ["meats_beef", "f"], ["meats_pork", "f"], ["meats_beef_ribs", "f"], ["meats_pork_ribs", "f"], ["meats_chicken", "f"], ["meats_turkey", "f"], ["meats_sausage", "f"], ["meats_venison", "f"], ["sauce", "f"], ["latitude", 48.13340729999999], ["updated_at", "2015-07-20 13:04:30.437500"], ["id", 46]]
[1m[36m (46.9ms)[0m [1mCOMMIT[0m

Related

Rails :has_many :through sort order issue in model

I am having some weird behavior in a Rails model and I'm not quite sure why. Thanks to anyone who can point me as to what I'm missing or not understanding.
The Problem
I have a model Car with a has_many :through relationship on Part through CarsPart. I expect accessing a car's parts to be in alphabetical order. I created the model specs and the controller specs. In the controller, the parts are ordered in alphabetical order as called out in the has_many relationship. In the controller spec, they are in order. In the model spec, they are not. I don't think it's tied to the testing framework because i've debugged the running server and see the same behavior.
The Code
Car
class Car < ActiveRecord::Base
has_many :cars_parts
has_many :parts, -> { order('name asc') }, through: :cars_parts, :dependent => :destroy
accepts_nested_attributes_for :cars_parts, :allow_destroy => true
validates :make, :model, presence: true
validate :validate_cars_parts
def validate_cars_parts
errors.add(:parts, "wrong number") if self.cars_parts.size < 1
self.cars_parts.each do |car_part|
part = Part.find(car_part.part_id)
errors.add(:parts, "doesn't exist") if part == nil
end
end
end
Part
class Part < ActiveRecord::Base
belongs_to :cars_part
has_many :cars, through: :cars_parts
validates :name, presence: true
end
CarsPart
class CarsPart < ActiveRecord::Base
belongs_to :car
belongs_to :part
end
Cars Controller Spec Passes
require 'rails_helper'
RSpec.describe CarsController, type: :controller do
render_views
context 'property features' do
describe "GET #show/id returns ordered parts" do
before :each do
#p4 = FactoryGirl.create(:part, :name => 'BPart2')
#p3 = FactoryGirl.create(:part, :name => 'APart2')
#p6 = FactoryGirl.create(:part, :name => 'BPart3')
#p5 = FactoryGirl.create(:part, :name => 'APart3')
#p2 = FactoryGirl.create(:part, :name => 'BPart1')
#p1 = FactoryGirl.create(:part, :name => 'APart1')
end
it 'returns parts in alphabetical order' do
car = FactoryGirl.create(:car, make: 'Nissan', model: 'Murano', parts: [#p4, #p3, #p6, #p5, #p2, #p1])
get :show, id: car.id, format: :json
expect(response).to have_http_status(:success)
expect(response).to render_template(:show)
response_json = JSON.parse(response.body)
response_json['parts'].each do |part|
puts part['id']
end
expect(response_json['parts'].size).to eq(6)
expect(response_json['parts'][0]['id']).to eq(#p1.id)
expect(response_json['parts'][1]['id']).to eq(#p3.id)
expect(response_json['parts'][2]['id']).to eq(#p5.id)
expect(response_json['parts'][3]['id']).to eq(#p2.id)
expect(response_json['parts'][4]['id']).to eq(#p4.id)
expect(response_json['parts'][5]['id']).to eq(#p6.id)
end
end
end
end
Car Model Spec Fails
require 'rails_helper'
RSpec.describe Car, type: :model do
context 'car parts' do
before :each do
#p4 = FactoryGirl.create(:part, :name => 'BPart2')
#p3 = FactoryGirl.create(:part, :name => 'APart2')
#p6 = FactoryGirl.create(:part, :name => 'BPart3')
#p5 = FactoryGirl.create(:part, :name => 'APart3')
#p2 = FactoryGirl.create(:part, :name => 'BPart1')
#p1 = FactoryGirl.create(:part, :name => 'APart1')
end
it 'returns parts in alphabetical order' do
car = FactoryGirl.create(:car, make: 'Nissan', model: 'Murano', parts: [#p4, #p3, #p6, #p5, #p2, #p1])
expect(car).to be_valid
expect(car.errors.messages.size).to eq(0)
expect(car.parts.size).to eq(6)
car.parts.each do |part|
puts part.id
end
expect(car.parts[0]['name']).to eq('APart1')
expect(car.parts[1]['name']).to eq('APart2')
expect(car.parts[2]['name']).to eq('APart3')
expect(car.parts[3]['name']).to eq('BPart1')
expect(car.parts[4]['name']).to eq('BPart2')
expect(car.parts[5]['name']).to eq('BPart3')
end
end
end
Summary
Please help me understand why when accessing them through the model they are not ordered, but when the controller looks them up and renders them, they are.
Thank you.
EDIT
The only way I have managed to get this to work is to create a 'ordered_parts' method that does the ordering and remove the order from the has_many. I had to change all my controllers to call 'ordered_parts' instead of the 'parts' method. Surely there is a better way. If anyone knows, please help.
def ordered_parts
self.parts.order('name asc')
end

rails 4 nested attributes won't create has_many model

I'm new to rails and have spent way too many hours on this. Thanks a lot, in advance, for any help!
I can't seem to get fields_for and/or accepts_nested_attributes_for to work for my nested attributes.
I have a smash_client that has_many contracts and a form that tries to create a smash_client with a parameter and at the same time it tries to also set a parameter on the contract object. The contract belongs_to the smash_client.
I've tried a lot of different solutions and have read the docs but I'm still missing something. I get this in my params hash, in the smash_clients_controller.rb
..., "smash_client"=>{"name"=>"fasdf", "user"=>"adam"}, "smash_client_id"=>{"instance_type"=>"spot"},...
from
= form_for #smash_client do |f|
.field
= f.label :name
= f.text_field :name
.field
= fields_for :smash_client_id do |c|
%p
= c.radio_button :instance_type, 'spot'
= c.label :instance_type, 'spot'
= c.radio_button :instance_type, 'on_demand'
= c.label :instance_type, 'on demand'
.actions
= f.submit 'Save'
and
class SmashClient < ActiveRecord::Base
has_many :contracts, dependent: :destroy
accepts_nested_attributes_for :contracts, allow_destroy: true,
reject_if: proc { |attributes| attributes[:instance_type].blank? }
...
def new
#smash_client = SmashClient.new
3.times { #smash_client.contracts.build }
end
...
def smash_client_params
#smash_client_params = params.require(:smash_client).
permit( :user, :name, contracts_attributes: [:instance_type] )
end
end
and
class Contract < ActiveRecord::Base
belongs_to :smash_client
after_create :determine_instance_type_and_start
before_destroy :stop_instances
...
end
I think the nested params would work if I hard coded it because if I try something like this, in the console, I don't get errors and I get a new SmashClient and Contract.
smash_client_params = {name: 'something', user: 'blah', contracts_attributes: [{instance_type: 'spot'}]}
SmashClient.create( smash_client_params )
I tried using :contracts, #smash_client.contracts and a few other things in the fields_for section. Also tried using select and collection_select but I can't seem to nail down the form.
sorry for the long post. Hopefully I got all the useful info with nothing extra in the question.
I'd really appreciate some direction or answers.
Thanks in advance.
I finally found it. The :instance_type had to be whitelisted in the Contract model. Thanks again, kalyani. I appreciate the help. Here's the changes to the code above:
.field
= fields_for :contracts do |c|
= c.label :instance_type, 'spot instance'
= c.radio_button :instance_type, 'spot', checked: true
= c.label :instance_type, 'on demand instance'
= c.radio_button :instance_type, 'on_demand'
and
def contract_params
params.require(:contract).
permit(:id, :name, :instance_id, :smash_client_id, :instance_type)
end
Instead of : fields_for :smash_client_id do |c|
write it as: fields_for :contracts do |c|
Refer:
1. http://apidock.com/rails/ActionView/Helpers/FormHelper/fields_for
http://railscasts.com/episodes/196-nested-model-form-part-1
Rails 4 Nested Attributes Unpermitted Parameters ---- refer this for writing the code in controller and view the correct way

ActiveAdmin form validation before save

I have form fields from one model status_history that I am including in my member edit. I am wanting to make it so that if the fields for status_history are empty, then it will not save. Currently it is saving blank items to status_history when I save a members edit.
My member form looks like this
form(:html => { :multipart => true }) do |f|
f.semantic_errors *f.object.errors.keys
columns do
column do
...
end
column do
f.inputs "Status" do
f.semantic_fields_for :status_histories, StatusHistory.new do |sh|
sh.inputs :class => "" do
sh.input :status, as: :select, collection: {Active: "active", Inactive: "inactive", Separated: "separated"}
sh.input :date, :as => :datepicker
sh.input :reason
end
end
table_for member.status_histories do
column "status" do |status_histories|
status_histories.status
end
column "date" do |status_histories|
status_histories.date
end
column "reason" do |status_histories|
status_histories.reason
end
end
end
...
end
end
f.actions
end
models/status_histories
class StatusHistory < ActiveRecord::Base
belongs_to :member
STATUS_TYPES = [ "active", "inactive", "separated" ]
validates :status, inclusion: STATUS_TYPES
validates :date, :presence => true
validates :reason, :presence => true
end
Even adding a button that would toggle the semantic_fields_for would work but currently if I leave them blank I get validates errors.
How would I override the save method to check if status and date are present and if so save the status_history and if not, do not save the status_history but save the rest of the member fields?
Try this:
in Member ActiveRecord model
accept_nested_attributes_for :status_histories, reject_if: :all_blank
http://apidock.com/rails/ActiveRecord/NestedAttributes/ClassMethods/accepts_nested_attributes_for

Rails - Multiple Nested Attributes

I am trying to use a select tag to save multiple nested children in a parent.
This is the error I'm looking at
Couldn't find all UserLocations with IDs (1, 2) (found 0 results, but was looking for 2)
I have the following setup under Rails 4 and Devise:
User
class User < ActiveRecord::Base
has_many :user_locations
accepts_nested_attributes_for :user_locations, :allow_destroy => true
end
UserLocation (locations that the user has)
class UserLocation < ActiveRecord::Base
belongs_to :user
belongs_to :location
end
Location (predefined list of locations the user can choose upon)
class Location < ActiveRecord::Base
has_many :user_locations
has_many :users, through: :user_locations
end
However, when trying to save the selected UserLocations, they won't be saved.
Rails Select Tag (You can choose multiple items)
<%= f.select :user_location_ids, options_for_select(Location.all.collect { |l| [ l.name, l.id ] }, #user.user_locations.collect{ |l| l.id }), {}, { multiple: true } %>
I have put the user_location_ids in my application_controller as user_location_ids: []
Cheers
Solved
The solution is to overwrite the default setter method for multiple nested models model_ids=(value). Do not use the plural of the model, e.g. models_ids=(value), because that is wrong!
def user_location_ids=(value)
for slot in value do
unless slot == ""
location = Location.find_by(id: slot.to_i)
unless location.nil?
self.user_locations << UserLocation.create(user_id: self.id, location_id: location.id)
end
end
end
end

Mongoid Model won't persist has_one via embeds_one relation

I'm having trouble with embeds in a mongoid4-based rails 4 app. I've been looking for an answer everywhere for the past 2 days. So here is the code.
This is a church management app, with a Service model, embedding a team and a band. Each team/band has several roles such as "presidence", "communion" that refer to a user.
My models :
class Service
include Mongoid::Document
...
embeds_one :team, autobuild: true
embeds_one :band, autobuild: true
...
accepts_nested_attributes_for :team, :band
end
class Team
include Mongoid::Document
embedded_in :service
has_one :presidence, :class_name => 'User', autosave: true
has_one :message, :class_name => 'User', autosave: true
...
end
class Band
include Mongoid::Document
has_one :lead, :class_name => 'User', autosave: true
has_one :guitar, :class_name => 'User', autosave: true
...
embedded_in :service
end
class User
include Mongoid::Document
embeds_one :profile
belongs_to :team, :inverse_of => :presidence
belongs_to :team, :inverse_of => :message
belongs_to :band, :inverse_of => :lead
belongs_to :band, :inverse_of => :guitar
def fullname
"#{profile.firstname} #{profile.lastname}"
end
def self.find_by_talent(talent)
self.where("profile.talents.name" => talent)
end
end
The services controller :
# POST /services
# POST /services.json
def create
#service = Service.new(service_params)
respond_to do |format|
if #service.save
format.html { redirect_to #service, notice: 'Service was successfully created.' }
format.json { render action: 'show', status: :created, location: #service }
else
format.html { render action: 'new' }
format.json { render json: #service.errors, status: :unprocessable_entity }
end
end
end
...
def service_params
params.require(:service).permit(:date, :time, :place, :name, :theme, :team => [:id, :precedence, :message ], :band => [:id, :lead, :guitar ])
end
And the form in _form.html.erb :
<%= form_for(#service) do |f| %>
...
<%= f.fields_for #service.team do |tf| %>
<%= tf.collection_select :presidence, User.find_by_talent(:presidence), :_id, :fullname, {:include_blank => "select a person"} %>
<%= tf.collection_select :message, User.find_by_talent(:message), :id, :fullname, {:include_blank => "select a person"} %>
<% end %>
<%= f.fields_for #service.band do |bf| %>
<%= bf.collection_select :lead, User.find_by_talent(:lead), :id, :fullname, {:include_blank => "select a person"} %>
<%= bf.collection_select :guitar, User.find_by_talent(:guitar), :id, :fullname, {:include_blank => "select a person"} %>
<% end %>
...
<% end %>
When creating a service, everything seems to run fine, but this is what I get in the console :
2.0.0-p195 :001 > s = Service.last
=> #<Service _id: 52ea18834d61631e7e020000, date: "2014-02-02", time: "10:00", place: "Where it's at", name: "My great name", theme: "The service's theme">
2.0.0-p195 :002 > s.team
=> #<Team _id: 52ea18834d61631e7e030000, >
2.0.0-p195 :003 > s.team.presidence
=> nil
Why is s.team.presidence not created ? s.team looks weird, too, with an ending comma...
Here is the content of my rails log :
Started POST "/services" for 127.0.0.1 at 2014-01-30 10:16:51 +0100
Processing by ServicesController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"Ph6lbdHC2FbiANn/fGSzHWprenj3fWKXM40Hrsc5+AM=", "service"=>{"date"=>"2014-02-02", "name"=>"My great name", "theme"=>"The service's theme", "time"=>"10:00", "place"=>"Where it's at", "team"=>{"presidence"=>"52ea18324d61631e81010000", "message"=>"52ea18324d61631e81010000"}, "band"=>{"lead"=>"52ea18324d61631e81010000", "guitar"=>"52ea18324d61631e81010000"}}, "commit"=>"Create Service"}
MOPED: 127.0.0.1:27017 COMMAND database=admin command={:ismaster=>1} runtime: 0.6610ms
MOPED: 127.0.0.1:27017 UPDATE database=service_boot_camp_development collection=users selector={"band_id"=>BSON::ObjectId('52ea18834d61631e7e010000'), "_id"=>{"$nin"=>[]}} update={"$set"=>{"band_id"=>nil}} flags=[:multi]
COMMAND database=service_boot_camp_development command={:getlasterror=>1, :w=>1} runtime: 0.5800ms
MOPED: 127.0.0.1:27017 INSERT database=service_boot_camp_development collection=services documents=[{"_id"=>BSON::ObjectId('52ea18834d61631e7e020000'), "date"=>"2014-02-02", "time"=>"10:00", "place"=>"Where it's at", "name"=>"My great name", "theme"=>"The service's theme", "team"=>{"_id"=>BSON::ObjectId('52ea18834d61631e7e030000')}, "band"=>{"_id"=>BSON::ObjectId('52ea18834d61631e7e010000')}}] flags=[]
COMMAND database=service_boot_camp_development command={:getlasterror=>1, :w=>1} runtime: 2.7460ms
I guess I'm doing something wrong, but I have no clue if it is in the database model or in the form... or anything else...
You will not be able to do it this way. When you create an embedded document, its _id and all of its data are embedded directly within the parent document. This is in contrast to an association, where the document with the belongs_to gets a foreign key which points to its associated parent document. So here, your User documents each have a team_id and band_id, but when the database tries to get the documents, it can't find them, since you can't query directly for embedded documents; you need the parent document first. For more, see the Mongoid documentation.
Another potential issue is that you have multiple belongs_to definitions in the User models. This will also cause an issue, because for each one of those, Mongoid will attempt to create a team_id and band_id. You should name them separately and specify a class name; perhaps names like :presiding_team and :message_team, :lead_band and :guitar_band, etc. This answer should show you what that would look like.
I would recommend making the Team and Band separate referenced documents instead of embedded documents, since you won't be able to reference users effectively while they're embedded.
Hope this helps.