1

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?

MurifoX
  • 14,991
  • 3
  • 36
  • 60

1 Answers1

0

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
Elvn
  • 3,021
  • 1
  • 14
  • 27
  • Let me try your suggestion. One question, do i have to change the form too? I am using a `user` form. Do i have to build a `person` one? – MurifoX Jul 19 '15 at 13:26
  • Yes. Flip the logic in your form to person/user instead of user/person. – Elvn Jul 19 '15 at 13:29
  • One thing, the form example you posted doesn't have a submit/button, so I assume you left this out on purpose for the example. Or, are you using the form for display only? – Elvn Jul 19 '15 at 14:27
  • Hmmm it works for me with nested attributes on `belongs_to` ! I'm using Mongoid and not ActiveRecord though, but I don't see why that would be a problem ? – Cyril Duchon-Doris Jul 19 '15 at 15:44
  • @CyrilDD I don't know Mongoid. Might be different, since it's not using Active Record. According to the Rails spec w/ Act. Rec. the nested attr. relationship works solely through the parent to the child. If you have seen the opposite successfully done, I would certainly be willing to change my position. – Elvn Jul 19 '15 at 19:03
  • Hmm now that you mention it, I am using the `nested_form` gem, so maybe this gem makes it work for `belong_to`. But anyway, I don't understand why it would not work for `belongs_to`. The only difference between `has_one` and `belongs_to` is the location of the foreign key ? – Cyril Duchon-Doris Jul 19 '15 at 20:15
  • Some answers to [this question](http://stackoverflow.com/questions/7365895/does-accepts-nested-attributes-for-work-with-belongs-to) seem to confirm `accepts_nested_attributes` works with `belongs_to` with Rails 4. – Cyril Duchon-Doris Jul 19 '15 at 20:18
  • I don't see an answer that confirms that. – Elvn Jul 19 '15 at 20:20
  • I think we should see what @MurifoX comes back with. SO is complaining that we're having too much discussion in the comments. – Elvn Jul 19 '15 at 20:24
  • I have inverted the logic, created a form based on `@person` and the `nested` part is now for the user. Put a `validates :username, uniqueness: true, on: :create` on my `User` model and tried to update an existing record. Rails fails to validate it saying that the `username` is already on the database, ignoring the `on: :create` part, just like the other way around. I still don't get it. – MurifoX Jul 19 '15 at 22:17
  • Would you please post your updated models? This is a very interesting problem. – Elvn Jul 19 '15 at 22:57
  • Also, would you post the logs showing the database access. I think it must be executing a POST and not a PATCH. Possible? – Elvn Jul 19 '15 at 23:34