I’m sure this is a “duh" newbie-type question, but I’ve been at it for days and cannot figure out why my code isn’t setting a relationship in the database correctly. I have a simple belongs_to relationship between two models.
class Pod < ActiveRecord::Base
belongs_to :instigator, :class_name => “User"
attr_accessor :instigator, :instigator_id, :title
validates_presence_of :instigator, :title
validates_associated :instigator
end
and
class User < ActiveRecord::Base
has_many :instigated_pods, inverse_of: :instigator, :class_name => "Pod", as: "instigator"
end
Then I want to test them with rspec and Factory Girl using (what I think) are, again, pretty simple factories
FactoryGirl.define do
factory :pod do
title "Test pod"
instigator
end
factory :user, aliases: [:instigator] do
username
end
end
With this setup, most tests pass, but my PodsController update test kept failing, and I finally found a test that shows why.
require 'rails_helper'
RSpec.describe Pod, type: :model do
it "saves the relationship to the database" do
pod = FactoryGirl.create(:pod)
expect(pod.save).to be_truthy
expect(pod.reload.instigator).to_not be_nil # passes - cached?
pod_from_database = Pod.find(pod.id)
expect(pod_from_database.instigator).to_not be_nil # <- fails
end
end
It seems that something is preventing the pod.instigator_id from being set in the database, so the relationship isn’t persisting. And I have no clue why!!!
I tried setting validates_presence_of :instigator_id, but that makes most of the standard rspec tests fail, and I saw this from the Rails Guides:
If you want to be sure that an association is present, you'll need to test
whether the associated object itself is present, and not the foreign key used
to map the association.
class LineItem < ActiveRecord::Base
belongs_to :order
validates :order, presence: true
end
In order to validate associated records whose presence is required, you must
specify the :inverse_of option for the association:
class Order < ActiveRecord::Base
has_many :line_items, inverse_of: :order
end
Any help straightening this out would be appreciated!
While I’m not altogether clear why, it appears that removing the line
attr_accessor :instigator, :instigator_id, :title
solves the problem. It appears to be blocking writing these attributes. Any indicator why would be appreciated!
attr_accessor is a ruby method that makes a getter and a setter(make them able to be read and to be written), this is not Database fields which called attr_accessible in Rails 3.
what you did is declare an ruby methods this is why it doesn't worked.
read more here:
http://rubyinrails.com/2014/03/17/what-is-attr_accessor-in-rails/
Related
Given some kind of Thing:
class Thing < ApplicationRecord
include CustomFieldable
#...
end
Which can have custom field values attached to it:
module CustomFieldable
extend ActiveSupport::Concern
included do
has_many :custom_field_values, as: :custom_fieldable, dependent: :destroy
validates_associated :custom_field_values
accepts_nested_attributes_for :custom_field_values
end
end
And where custom field values are basically just a string value (at least for now) with a reference to their owner:
class CustomFieldValue < ApplicationRecord
belongs_to :custom_fieldable, polymorphic: true, dependent: :destroy
belongs_to :custom_field, dependent: :destroy
validates_presence_of :custom_fieldable
validates_presence_of :custom_field
validates_presence_of :string_value
end
And to the custom field, which is just a wrapper around a name:
class CustomField < ApplicationRecord
validates_presence_of :name
validates_uniqueness_of :name
end
When I initialise the Thing with a hash:
"thing"=>{
//...other stuff...
"custom_field_values_attributes"=>{
"0"=>{
"custom_field_id"=>"1",
"string_value"=>"value 1"
}
}
}
I would expect ActiveRecord to set up the association from the CustomFieldValue back to the Thing. But it looks like it is not, because I get a validation error:
There were problems with the following fields:
Custom field values custom fieldable can't be blank
Custom field values is invalid
So it's like when I use accepts_nested_attributes_for, the parent association is not set up. Is that expected behaviour?
Update #1:
Controller logic for permitting the fields looks like this:
class ThingController < ApplicationController
def thing_params(action)
common_params = [ omitting common stuff... ]
params.fetch(:licence).permit(*common_params,
custom_field_values_attributes: [
:custom_field_id, :string_value ])
end
end
Update #2:
If I write two tests for the model, I can see the same thing happening.
Fails:
test "adding a custom field value on construction via nested attributes" do
thing = Thing.new custom_field_values_attributes: [
{ custom_field_id: custom_fields(:environment).id,
string_value: 'Testing' }
]
assert_attribute_not_invalid thing, :custom_field_values
assert_equal 'Testing', thing.custom_field_values[0].string_value
end
Passes:
test "adding a custom field value via nested attributes" do
thing = things(:one)
thing.update_attributes custom_field_values_attributes: [
{ custom_field_id: custom_fields(:environment).id,
string_value: 'Testing' }
]
assert_valid thing
assert_equal 'Testing', thing.custom_field_values[0].string_value
end
So it's like, if the record isn't saved yet, Rails doesn't set up the nested models correctly, but if it's already saved, they get set up correctly.
I tried something on a whim. Changed this:
has_many :custom_field_values, as: :custom_fieldable, dependent: :destroy
To this:
has_many :custom_field_values, as: :custom_fieldable, inverse_of: :custom_fieldable, dependent: :destroy
So it seems that Rails cannot guess the inverse relationship for polymorphic associations - even though I was already forced to tell it this using :as. Specifying it twice works fine.
With the code given, I would say every thing works fine. You create a Thing object and add multiple CustomFieldValue objects. The new CustomFieldValue objects have set custom_field_id to 1. This does not mean that Rails loads the custom_field object. This would only happen when you save the object and reload it. So validates_presence_of :custom_field is right to complain. This attribute is still nil. I think the same will happen for custom_fieldable
I feel a bit stupid but this is not working (and I expected it should work):
class MembershipCard < ActiveRecord::Base
belongs_to :association
belongs_to :personal_record
validates :number, :presence => true
def dis
print "---------------------------- #{personal_record.as_json}---------------------"
number
end
def value
id
end
end
class PersonalRecord < ActiveRecord::Base
has_many :membership_card, :dependent => :nullify
def dis
"#{name} #{surname}"
end
def val
id
end
end
print "---------------------------- #{personal_record.as_json}---------------------"
It's not printing. Any suggestion about why this is happening?
I can't access any associated model in this way and it's a disaster, basically I can't use activerecord.
I solved the problem by myself: it seems that association is used inside rails (damn me), expecially this was crashing my application. Commenting it solved the issue, so I'm going to rename models/controllers and so on.
I've been bashing my head against a wall for a while on this one and I can't get it to work. I have three models:
class Instrument < ActiveRecord::Base
has_many :analytical_methods
has_many :analytes, :through => :analytical_methods
accepts_nested_attributes_for :analytical_methods
attr_accessible :name, :analytical_methods_attributes
end
class AnalyticalMethod < ActiveRecord::Base
belongs_to :instrument
has_many :analytes
accepts_nested_attributes_for :analytes
attr_accessible :name, :analytes_attributes
end
class Analyte < ActiveRecord::Base
belongs_to :analytical_method
attr_accessible :name
end
And I have the following factories:
Factory.define :analyte do |analyte|
analyte.name "Test analyte"
end
Factory.define :analytical_method do |analytical_method|
analytical_method.name "Test method"
analytical_method.association :analyte
end
Factory.define :instrument do |instrument|
instrument.name "Test instrument"
instrument.association :analytical_method
instrument.association :analyte
end
Any time I try to Factory(:instrument) or Factory(:analytical_method), it throws the following error:
NoMethodError:
undefined method `analyte=' for #<AnalyticalMethod:0x00000104c44758>
Am I missing some ridiculous typo or something? The website works perfectly fine, but the tests keep failing. Thanks for any help in returning my sanity!
I believe it's because you're using instrument.association :analyte and analytical_method.association :analyte for a has_many relationship. The association declaration is used for belongs_to relationships.
I typically don't use Factory Girl to create has_many relationships, but if you choose to go this route, you're not the first person to do so. Here's a blog post that's a few years old, but seems to describe what you're trying to do.
Given a simple relationship where Person has_many Telephones. And a telephone only contains a telephonenumber which must be unique!
class Telephone < ActiveRecord::Base
validates_presence_of :contact_id
belongs_to :contact
validates :telephone, {:presence => true, :uniqueness => true}
end
class Contact < ActiveRecord::Base
has_many :telephones
validates_associated :telephones
has_many :emails
has_many :addresses
validates_presence_of :firstname
accepts_nested_attributes_for :telephones, :allow_destroy=>true
validates_presence_of :lastname
end
test "telephone number must be unique" do
john = contacts :johndoe #johndoe is a person with 1 existing number
2.times do
john.telephones.build :telephone=> "123" # 123 doesnt exist yet
end
puts Telephone.count # this gives 1
john.save
puts Telephone.count # this gives 3 !!!! ???
assert not(john.valid?) # This validates unless I remove the save above
end
Can someone explain the outcome of this test.
just calling valid? fails, but that is mentioned in the rdoc (must save first)
saving first does make valid? pass
BUT now I actually have 3 records in the database which breaks my unique requirement.
Is there a better way to do this? I don't understand the outcome of this test, it really goes against my expectations.
Ok if you read the ruby documentation you will notice that they mention that validating a model is not sufficient for uniqueness. YOU MUST use database unique constraints whenever possible. Otherwise it is possible when using two processes/threads/whatever that both will do a validation check, pass as unique, and then insert same values.
tl;dr: Add a unique constraint to the db column.
I've upgraded to Rails 2.3.3 (from 2.1.x) and I'm trying to figure out the accepts_nested_attributes_for method. I can use the method to update existing nested objects, but I can't use it to create new nested objects. Given the contrived example:
class Product < ActiveRecord::Base
has_many :notes
accepts_nested_attributes_for :notes
end
class Note < ActiveRecord::Base
belongs_to :product
validates_presence_of :product_id, :body
end
If I try to create a new Product, with a nested Note, as follows:
params = {:name => 'Test', :notes_attributes => {'0' => {'body' => 'Body'}}}
p = Product.new(params)
p.save!
It fails validations with the message:
ActiveRecord::RecordInvalid: Validation failed: Notes product can't be blank
I understand why this is happening -- it's because of the validates_presence_of :product_id on the Note class, and because at the time of saving the new record, the Product object doesn't have an id. However, I don't want to remove this validation; I think it would be incorrect to remove it.
I could also solve the problem by manually creating the Product first, and then adding the Note, but that defeats the simplicity of accepts_nested_attributes_for.
Is there a standard Rails way of creating nested objects on new records?
This is a common, circular dependency issue. There is an existing LightHouse ticket which is worth checking out.
I expect this to be much improved in Rails 3, but in the meantime you'll have to do a workaround. One solution is to set up a virtual attribute which you set when nesting to make the validation conditional.
class Note < ActiveRecord::Base
belongs_to :product
validates_presence_of :product_id, :unless => :nested
attr_accessor :nested
end
And then you would set this attribute as a hidden field in your form.
<%= note_form.hidden_field :nested %>
That should be enough to have the nested attribute set when creating a note through the nested form. Untested.
check this document if you use Rails3.
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html#label-Validating+the+presence+of+a+parent+model
Ryan's solution is actually really cool.
I went and made my controller fatter so that this nesting wouldn't have to appear in the view. Mostly because my view is sometimes json, so I want to be able to get away with as little as possible in there.
class Product < ActiveRecord::Base
has_many :notes
accepts_nested_attributes_for :note
end
class Note < ActiveRecord::Base
belongs_to :product
validates_presence_of :product_id unless :nested
attr_accessor :nested
end
class ProductController < ApplicationController
def create
if params[:product][:note_attributes]
params[:product][:note_attributes].each { |attribute|
attribute.merge!({:nested => true})
}
end
# all the regular create stuff here
end
end
Best solution yet is to use parental_control plugin: http://github.com/h-lame/parental_control