tl;dr the validation works in the browser, but fails with rspec. I'm not sure why.
answer: I was attempting to mass assign user_id, but user_id wasn't mass-assignable. Was building it correctly in the controller, incorrectly in rspec.
This is my Listing model. Every listing has a price and a user_id associated with it.
In my "listing" model, I have "price".
In my "user" model I have a boolean attribute called "vip".
The max value for "price" depends on the User's VIP-status. If VIP, max price is 400. Else, max price is 200.
I wrote a method "listing_ceiling" that should return the correct max value depending on the user's VIP-ness.
It works in the browser, but when I run through my tests, rspec comes back with the error:
undefined method `vip?' for nil:NilClass
I'm not exactly sure where I'm falling short here. It works correctly in the browser, but fails when testing with rspec. Here's the code:
validates_numericality_of :price, :presence => true, :greater_than => 10, :less_than => :listing_ceiling
def listing_ceiling
if self.user.vip?
400.01
else
200.01
end
end
Any ideas why it would fail this way?
The Listing#user returns nil. There could be two problems.
Your listing does not have a user. i.e. foreign key user_id is nil for the Listing in question. OR
Your listing has a value in user_id column. But there is no User with that id.
Solution is to assign a user to your listing.
You should check your RSpec, the problem doesn't lie in your cutom validation method but in your test setup. As the error message states user == nil!
Related
I have a form field in ROR 4 app called as 'measure'. It is not a database column, but its values will help model create child entries of its own via acts_as_tree : https://github.com/rails/acts_as_tree
I have to throw a validation when 'measure' is invalid. So I have created a virtual attribute known as measure and check for its validations only on a certain condition.
model someModel
attr_accessor :measure
validates_presence_of :measure, :if => condition?
Problem is when I am saving the code, I am thrown a validation which is fine. I am also thrown the same validation when I am trying to update the record in some other method of the model. The only way to surpass that is by writing this code:
# I do not want to do this, is there a better way?
self.measure = "someRandomvalue"
self.save
I am making this as virtual attribute only for throwing validations. Is there a better way of throwing validations? The form has other validations, I do not want the error for this validations to be shown differently just because it is not an attribute.
I want it to validated only when active record is saved via create and update action of the controller and not when it is being updated by some random method of model.
I have seen other developers in my team doing similar thing and was always curious about one thing - "What are you trying to achieve doing things the way you are doing?". You see, I am not sure if validators should be used for values that will not be serialized.
Anyways, you may try using format validator instead of presence, which worked in my team's case:
# Rails 3/4
validates :measure, format: { with: /^.+$/, allow_nil: true }
# Rails 2
validates_format_of :measure, :with => /^.+$/, :allow_nil => true
You may also try using allow_blank instead of allow_nil.
I would rather create a custom validator along the lines of validates_accessor_of for values that I know will never be serialized.
HTH
I am trying to handle a Stripe Webhook for Invoice.created and want to save the invoice line item. My challenge is that the files variables change based on the line item type.
I received a undefined method 'name' for nil:NilClass when I tried to import the line items since, depending on the type of line item, the plan object could be null.
I was able to solve the problem by separating the update_attributes into 2, one would only happen if the plan object exists. The following is what I was to get to work. My hope is that there is a better way.
#invoice_line_item = InvoiceLineItem.where(stripe_line_item_id: line_item.id).first_or_create(invoice_id: #invoice.id)
#invoice_line_item.update_attributes(
amount: line_item.amount,
currency: line_item.currency,
period_start: Time.at(line_item.period.start).strftime("%m-%d-%Y"),
period_end: Time.at(line_item.period.end).strftime("%m-%d-%Y"),
proration: line_item.proration,
item_type: line_item.type)
if line_item.plan.present?
#invoice_line_item.update_attributes(
plan_name: line_item.plan.name,
plan_interval: line_item.plan.interval,
plan_amount: line_item.plan.amount,
trial_period_days: line_item.plan.trial_period_days)
end
You could try
line_item.plan.try(:name)
and similarly for all the line_item.plan elements
Try (in rails) will give you nil if the subject is nil http://apidock.com/rails/Object/try
It's not really excluding the nil values, but if line_item.plan is nil, then the sub-values will also be nil. If that's the right behavior, then you should try try.
Update: I just hit on this coderwall post this morning (https://coderwall.com/p/wamyow) which mentions the use of delegate with allow_nil: true. Could you do something like
class InvoiceLineItem < ActiveRecord::Base
delegate :name, :interval, :amount, :trial_period_days, to: :plan, allow_nil: true
... rest of the class ...
end
Then looking more closely, I wonder why you're updating all the plan attributes on the line_item if they are available via the relationship? Am I missing something?
I noticed that one of my model fields would not update through my app in the browser after it had been initially set. When I went to investigate this I discovered that the field was only declared through a custom validator:
validate :amount_validator
def amount_validator
if self.amount == nil
errors.add(:amount, "Please fill in the amount.")
end
end
I thought the issue was that this was missing:
validates :amount, presence: true
I added this but I still couldn't update the field through the browser. When I saved the value and the page refreshed it had reverted to its original value. I read another SO question that indicated I should try updating this field through the console and see if there were any errors. I did this, it worked with no errors. Went back into the browser and the value had changed but I still could not update it through the browser. Thanks for your help.
Depending on what rails version you're using, the error might be around accessible attributes (Rails 3) or strong paramenters (Rails 4).
On Rails 3, make sure that you have this in your model:
attr_accessible :amount
On Rails 4, make sure that you are allowing the attribute in the hash that you pass to update_attributes in your controller:
your_model.update_attributes(params.require(:your_model_name).permit([:amount]))
Is it possible to pass :symbols to the valid? method so that I can define if the object is valid up to a certain point?
Eg. if I have an object Person and want to call Person.valid?(:basic_info) and it will return true only if a certain subset of fields (say name & gender) are present?
I saw something that I thought might be of use but cannot get it working, it's conditional validations http://guides.rubyonrails.org/active_record_validations_callbacks.html#conditional-validation , in particular grouping conditional validations, but I couldn't get it working...
Can anyone help me out here please...
I don't think there already present like this however you can write a method on your own like following
def is_valid_field?(field)
self.valid?
self.errors[field].blank?
end
and then just person.is_valid_field?(:basic_info)
To validate basic_info you'll have to define a custom validator:
class Person < ActiveRecord::Base
validate :basic_info_present
def basic_info_present
if name.blank? || gender.blank?
errors.add(:basic_info, "can't be in blank")
end
end
end
If you then want to see if there are errors on the specific field, you can use #Salil's approach.
Note however that since there is no actual attribute called basic_info in your model, the validation errors here will not come up in forms, etc. (although they will be in the errors hash). That may or may not be what you want.
I got this to work using conditional validations, so now i can use .valid?(:basic) say for when i only want to check that the person has a name with the call...
validates_presence_of :name, :when => [:basic]
Documentation here: http://apidock.com/rails/ActiveRecord/Validations/valid%3F
This way I can have the object return true when calling .valid? even when it doesn't have a name, good times...
Here is the command that I'm executing in Rails Console:
Person.create!(:firstName=>"matt", :lastName=>"master", :gender => 1)
My result is this error message:
ActiveRecord::RecordInvalid: Validation failed: Firstname can't be blank
My model validation code looks as such:
class Person < ActiveRecord::Base
belongs_to :user, :class_name => 'User', :foreign_key => 'fk_ssmUserId'
validates_presence_of :firstName, :lastName
When I comment out validates_presence_of everything works and my data is entered properly into the database, so I know that the values are actually being passed into the new object. I even inspected the new object created inside the Rails::ActiveRecord::Validations code to make sure it was being instantiated correctly before being saved. It was. Also, I have other models with validates_presence_of that work 100% fine every time. It's just this one model. I am using Rails 3.1.0.rc1.
Any ideas?
Thanks!
:firstName=>"matt", :lastName=>"master", :gender => 1
check that the key correspond to your model columns. Seems to be everything should be fine.
I have a suspicion that this error relates to the fact that you're creating and saving the object using the .create! method from the console. Your Person class appears to require a foreign key, a value which is probably not being instantiated when you create an object at the console. To test this, try typing:
test = Person.create(:firstName=>"matt", :lastName=>"master", :gender => 1)
with no bang after the .create method. This should not generate an error. Now type:
test
It's very likely that you have required key values set as "nil" and that the object can't be saved from console until you fill in the appropriate values.