nil value reaching ActiveRecord validation despite correct value passed to constructor - ruby

Validation on a model object is failing despite what I think is the correct parameter value being whitelisted in the controller and passed to the model's constructor. What am I doing wrong?
OuterModel has_one Location via locatable. The latter is created using accepts_nested_attributes_for and validates only the :country attribute:
(EDIT: I found the error, it was hidden by code that I initially left out of the code here for simplification. See my answer below)
class OuterModel < Parent
has_one :location, as: locatable
accepts_nested_attributes_for :location
end
class Parent < ActiveRecord::Base
after_create :create_location
end
class Location < ActiveRecord::Base
belongs_to :locatable, polymorphic: true
validates :country, inclusion: {in: ["US", "CA"]}
end
Controller:
class OuterModelsController < ApplicationController
def create
#outer = OuterModel.new(outer_params)
if #outer.save
byebug #debug here
redirect_to outer_path(#outer)
end
end
def outer_params
params.require(:outer).permit(:name, :type,
location_attributes: [:country, :state, :city])
end
end
Using byebug I see the #outer.save call is satisfied, but the nested location object is not persisted because of a validation error:
(byebug) #outer.persisted? #true
(byebug) #outer.location.persisted? #false
(byebug) #outer.location.valid? #false
(byebug) #outer.location.country #nil
(byebug) #outer.id #6372
(byebug) #outer.location.errors
<ActiveModel::Errors:0x007f1f33c1eae0
#base=#<Location id: nil, city: nil, country: nil, state: nil, locatable_id: 6732, locatable_type: "OuterModel", created_at: nil, updated_at: nil>,
#messages={:country=>["is not included in the list"]}>
However, the controller outer_params method appears to be sending the correct hash to OuterModel.new:
{"name"=>"A name",
"type"=>"OuterModel",
"location_attributes"=>{
"city"=>"South Deionview", "country"=>"US", "state"=>"IL"
}
}
Why then is the Location.country value nil after the call to save, and why is validation failing?
EDIT: logfile (much simplified) pastebin here. It seems like the correct values are being sent as part of the SQL

The answer lay in a parent of OuterModel that had an after_create hook to create a Location. Added the parent class to the source above.
The Location object was initially created correctly by accepts_nested_attributes_for (as per all evidence from logging) but then the after_create hook replaced it with an empty Location object that failed validation.

Related

Rspec mongoid - Testing embedded document callback (after_save)

I'm trying to create a test to check if my post's embedded document(author) call to it callback method.
Code:
class Post
include Mongoid::Document
include Mongoid::Timestamps::Created
include Mongoid::Timestamps::Updated
{....}
# relations
embeds_one :author, cascade_callbacks: true
accepts_nested_attributes_for :author
{...}
end
Class Author
include Mongoid::Document
include Mongoid::Timestamps::Created
include Mongoid::Timestamps::Updated
{...}
embedded_in :post
after_save :my_callback_method
def save_estimation_logs
{...}
end
{...}
end
test:
RSpec.describe Author, :type => :model do
context "Create author on Post" do
let!(:post) { create(:post, :with_external_author) }
it "should call after_save method my_callback_method when saving" do
expect(post.author).to receive(:my_callback_method)
expect(post.save).to eq true
end
end
end
when i'm trying to run this rspec - i got
Failure/Error: expect(post.author).to receive(:my_callback_method)
(#<Author _id: 5c7ea762f325709edac2ae84, created_at: 2019-03-05 16:44:18 UTC, updated_at: 2019-03-05 16:44:18 UTC>). my_callback_method(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments
Can you guys help me understand how should I test this embedded document callbacks?
First of all, you should trust mongoid to call after_save and test my_callback_method in isolation.
Now, as said in the comments, that you want to check if someone deleted the after_save, you can add a test for:
RSpec.describe Author, :type => :model do
context "Author" do
it "should define my_callback_method for after_save" do
result = Author._save_callbacks.select { |cb| cb.kind.eql?(:after) }.collect(&:filter).include?(:my_callback_method)
expect(result).to eq true
end
end
end
Your code looks correct but there is a number of outstanding issues in Mongoid related to callbacks in persistence. Ensure the callback is called in normal operation (i.e. when you save a post from a Rails console).

How to conduct Rails model validation witih or condition?

I’m using Rails 4.2.3. I have three fields in my model — name, first_name, and last_name. would like to have a validation rule in my model that causes a save to fail if the “name” field is empty unless either the first_name or last_name field is not empty. SOoI tried
validates_presence_of :name, :unless => !:first_name.empty? or !:last_name.empty?
but this doesn’t work. I get the error below
undefined method `validate' for true:TrueClass
What is the proper way to write the validation rule above?
Everything you need to know is here.
You can write the rule by defining a separate method for it:
class Whatever < ActiveRecord::Base
validates :name, presence: true, unless: :firstname_or_surname?
def firstname_or_surname?
firstname.present? || surname.present?
end
end
Or you can use a Proc to define it inline:
class Whatever < ActiveRecord::Base
validates :name, presence: true,
unless: Proc.new { |a| a.firstname.present? || a.surname.present? }
end

NameError when assigning instance of model to reference property

I'm unable to assign a model instance to a reference property of another model. Relevant code is below:
module Blog::Models
class Post < Base; belongs_to :user, dependent: :destroy end
class User < Base; has_many :posts end
...
class BasicFields < V 1.0
def self.up
create_table User.table_name do |t|
...
end
create_table Post.table_name do |t|
...
t.references :user
end
end
...
end
end
module Blog::Controllers
...
class PostEditN
...
def post(post_num)
#post = Post.find(post_num)
#user = User.find(#input.user)
...
#post.user = #user # Error thrown: NameError at /post/edit/1 uninitialized constant User
# #post.user_id = #user.id << This is my currently working solution
#post.save
redirect PostN, post_num
end
end
...
end
...
When I assign something to #post.user using Camping in console mode, it is successful, but I can't seem to accomplish the same behavior in the controller otherwise. I made do by simply assigning the #user.id to the user_id property of the Post instance. However, I would like to figure out why the alternate method works in the Camping console and not when I'm simply running the webserver.
My best guess is that this is a problem with namespaces. In the code you show Useris actually Blog::Models::User. In your controller the context is Blog::Controllers. Have you tried changing the code in the controller to?
#post = Blog::Models::Post.find(post_num)
#user = Blog::Models::User.find(#input.user)
...
I was able to resolve my issue. Seems when I was creating new Post records, I was not initializing the User. Thus, when assigning #post.user it would complain that the user property was uninitialized. The only problem I see is that an operation was attempted to be made on an oprhan Post record, which is invalid data according to the relationship with User.

Factory Girl and Rails with Cucumber gives undefined method error

I have a Model that looks like this
class Course < ActiveRecord::Base
attr_accessible :name
end
And my factories.rb looks like this
FactoryGirl.define do
factory :course do
name "testname"
end
end
Then when I call FactoryGirl.create(:course) in Cucumber like so:
Given /^there are courses in the database$/ do
FactoryGirl.create(:course)
end
I receive an undefined method error:
undefined method `name=' for #<Course id: nil, created_at: nil, updated_at: nil> (NoMethodError)
It all works fine when I use attr_accessor instead of attr_accessible in the Model, but according to other examples I've found it should work with both. Is this a bug or am I doing something wrong?
Links to examples where they say it should work:
How to create factories with attr_accessible?
https://groups.google.com/forum/#!topic/factory_girl/gjLXp96Acyg
https://gist.github.com/334413/2a0f60a9afbff321d3e96727ec17bab53c484128
Either should work provided that the fields in question exist in your database. ActiveRecord generates accessors (which FactoryGirl depends on) for attributes specified in attr_accessible but only if they are defined in the associated database table.

Rails3: Nested model - child validates_with method results in "NameError - uninitialized constant [parent]::[child]"

Consider the following parent/child relationship where Parent is 1..n with Kids (only the relevant stuff here)...
class Parent < ActiveRecord::Base
# !EDIT! - was missing this require originally -- was the root cause!
require "Kid"
has_many :kids, :dependent => :destroy, :validate => true
accepts_nested_attributes_for :kids
validates_associated :kids
end
class Kid < ActiveRecord::Base
belongs_to :parent
# for simplicity, assume a single field: #item
validates_presence_of :item, :message => "is expected"
end
The validates_presence_of methods on the Kid model works as expected on validation failure, generating a final string of Item is expected per the custom message attribute supplied.
But if try validates_with, instead...
class Kid < ActiveRecord::Base
belongs_to :parent
validates_with TrivialValidator
end
class TrivialValidator
def validate
if record.item != "good"
record.errors[:base] << "Bad item!"
end
end
end
...Rails returns a NameError - uninitialized constant Parent::Kid error following not only an attempt to create (initial persist) user data, but also when even attempting to build the initial form. Relevant bits from the controller:
def new
#parent = Parent.new
#parent.kids.new # NameError, validates_* methods called within
end
def create
#parent = Parent.new(params[:parent])
#parent.save # NameError, validates_* methods called within
end
The error suggests that somewhere during model name (and perhaps field name?) resolution for error message construction, something has run afoul. But why would it happen for some validates_* methods and not others?
Anybody else hit a wall with this? Is there some ceremony needed here that I've left out in order to make this work, particularly regarding model names?
After a few hours away, and returning fresh -- Was missing require "Kid" in Parent class. Will edit.

Resources