Rails: How to check if "update_attributes" is going to fail? - ruby-on-rails

To check if buyer.save is going to fail I use buyer.valid?:
def create
#buyer = Buyer.new(params[:buyer])
if #buyer.valid?
my_update_database_method
#buyer.save
else
...
end
end
How could I check if update_attributes is going to fail ?
def update
#buyer = Buyer.find(params[:id])
if <what should be here?>
my_update_database_method
#buyer.update_attributes(params[:buyer])
else
...
end
end

it returns false if it was not done, same with save. save! will throw exceptions if you like that better. I'm not sure if there is update_attributes!, but it would be logical.
just do
if #foo.update_attributes(params)
# life is good
else
# something is wrong
end
http://apidock.com/rails/ActiveRecord/Base/update_attributes
Edit
Then you want this method you have to write. If you want to pre check params sanitation.
def params_are_sanitary?
# return true if and only if all our checks are met
# else return false
end
Edit 2
Alternatively, depending on your constraints
if Foo.new(params).valid? # Only works on Creates, not Updates
#foo.update_attributes(params)
else
# it won't be valid.
end

The method update_attributes returns false if object is invalid. So just use this construction
def update
if #buyer.update_attributes(param[:buyer])
my_update_database_method
else
...
end
end
If your my_update_database_method has to be call only before update_attributes, then you shoud use merge way, probably like this:
def update
#buyer = Buyer.find(params[:id])
#buyer.merge(params[:buyer])
if #buyer.valid?
my_update_database_method
#buyer.save
else
...
end
end

This may not be the best answer, but it seems to answer your question.
def self.validate_before_update(buyer)#parameters AKA Buyer.validate_before_update(params[:buyer])
# creates temporary buyer which will be filled with parameters
# the temporary buyer is then check to see if valid, if valid returns fail.
temp_buyer = Buyer.new
# populate temporary buyer object with data from parameters
temp_buyer.name = buyer["name"]
# fill other required parameters with valid data
temp_buyer.description = "filler desc"
temp_buyer.id = 999999
# if the temp_buyer is not valid with the provided parameters, validation fails
if temp_buyer.valid? == false
temp_buyer.errors.full_messages.each do |msg|
logger.info msg
end
# Return false or temp_buyer.errors depending on your need.
return false
end
return true
end

you'd better check it in your model through a before_save
before_save :ensure_is_valid
private
def ensure_is_valid
if self.valid?
else
end
end

I've run into the same scenario - needed to know if record is valid and do some actions before update save. I've found out that there is assign_attributes(attributes) method which update method uses before save. So nowadays it's likely correct to do:
def update
#buyer = Buyer.find(params[:id])
#buyer.assign_attributes(params[:buyer])
if #buyer.valid?
my_update_database_method
#buyer.save
else
...
end
end

Related

How to pass parameter when duplicating object in rails controller?

How can I provide .dup method with custom param so every time it is executed param is always True even when object who is being duplicated has this param at false?
Attribute I want to make true is called :original.
Here is my Modifications_controller create action:
def create
#modification = Modification.new(change_params.merge(user: current_user))
respond_to do |format|
if #modification.save
#modification.entity.boxes.each do |b|
#modification.boxes << b.dup #here I need to pass custom param
end
format.js {}
else
format.js {}
end
end
end
#dup doesn't know anything specific about your model logic. If you want to set some attributes to true, simply clone the object and then change the values.
box = b.dup
box.value = true
#modification.boxes << box
You can also consider to extract the feature in a custom method in the model, so that it's easier to write a test for it.
def duplicate
self.dup.tap do |i|
i.value = true
end
end
#modification.boxes << b.duplicate

Why doesn't my Object update?

I have this test:
describe 'check_account_status' do
it 'should send the correct reminder email one week prior to account disablement' do
# Three weeks since initial email
reverification = create(:reverification)
initial_notification = reverification.twitter_reverification_sent_at.to_datetime
ActionMailer::Base.deliveries.clear
Timecop.freeze(initial_notification + 21) do
Reverification.check_account_status
ActionMailer::Base.deliveries.size.must_equal 1
ActionMailer::Base.deliveries.first.subject.must_equal I18n.t('.account_mailer.one_week_left.subject')
reverification.reminder_sent_at.class.must_equal ActiveSupport::TimeWithZone
reverification.notification_counter.must_equal 1
must_render_template 'reverification.html.haml'
end
end
This test produces this error:
check_account_status#test_0001_should send the correct reminder email one week prior to account disablement [/Users/drubio/vms/ohloh-ui/test/models/reverification_test.rb:67]:
Expected: ActiveSupport::TimeWithZone
Actual: NilClass
Here is my code:
class Reverification < ActiveRecord::Base
belongs_to :account
FIRST_NOTIFICATION_ERROR = []
class << self
def check_account_status
Reverification.where(twitter_reverified: false).each do |reverification|
calculate_status(reverification)
one_week_left(reverification)
end
end
private
def calculate_status(reverification)
#right_now = Time.now.utc.to_datetime
#initial_email_date = reverification.twitter_reverification_sent_at.to_datetime
#notification_counter = reverification.notification_counter
end
def one_week_left(reverification)
# Check to see if three weeks have passed since the initial email
# and check to see if its before the one day notification before
# marking an account as spam.
if (#right_now.to_i >= (#initial_email_date + 21).to_i) && (#right_now.to_i < (#initial_email_date + 29).to_i)
begin
AccountMailer.one_week_left(reverification.account).deliver_now
rescue
FIRST_NOTIFICATION_FAILURE << account.id
return
end
update_reverification_fields(reverification)
end
end
def update_reverification_fields(reverification)
reverification.notification_counter += 1
reverification.reminder_sent_at = Time.now.utc
reverification.save!
reverification.reload
end
end
Forgive the indentation, but what seems to be the problem, is that my reverification object doesn't update when it leaves the check_account_status method. I've placed puts statements through out the code and I can see without a doubt that the reverification object is indeed updating. However after it leaves the update_reverification_fields and returns to the test block, the fields are not updated. Why is that? Has anyone encountered this?
I believe you have a scope issue, the methods you call from check_account_status certainly don't return the updated object back to your method and Ruby only passes parameters by value.
Try something like this instead:
def check_account_status
Reverification.where(twitter_reverified: false).each do |reverification|
reverification = calculate_status(reverification)
reverification = one_week_left(reverification)
end
end
private
def calculate_status(reverification)
# ...
reverification
end
def one_week_left(reverification)
# ...
reverification = update_reverification_fields(reverification)
reverification
end
def update_reverification_fields(reverification)
# ...
reverification
end
The problem is that reverification object in your test and objects inside of check_account_status are different instances of the same model.
def update_reverification_fields(reverification)
reverification.notification_counter += 1
reverification.reminder_sent_at = Time.now.utc
reverification.save!
reverification.reload
end
This reload here, it's doing nothing. Let's walk through your test.
# check_account_status runs a DB query, finds some objects and does things to them
Reverification.check_account_status
# this expectation succeeds because you're accessing `deliveries` for the
# first time and you don't have it cached. So you get the actual value
ActionMailer::Base.deliveries.size.must_equal 1
# this object, on the other hand, was instantiated before
# `check_account_status` was called and, naturally, it doesn't see
# the database changes that completely bypassed it.
reverification.reminder_sent_at.class.must_equal ActiveSupport::TimeWithZone
So, before making expectations on reverification, reload it, so that it pulls latest data from the DB.
reverification.reload # here
reverification.reminder_sent_at.class.must_equal ActiveSupport::TimeWithZone

Rails - 'can't dump hash with default proc' during custom validation

I have 2 models. User and Want. A User has_many: Wants.
The Want model has a single property besides user_id, that's name.
I have written a custom validation in the Want model so that a user cannot submit to create 2 wants with the same name:
validate :existing_want
private
def existing_want
return unless errors.blank?
errors.add(:existing_want, "you already want that") if user.already_wants? name
end
The already_wants? method is in the User model:
def already_wants? want_name
does_want_already = false
self.wants.each { |w| does_want_already = true if w.name == want_name }
does_want_already
end
The validation specs pass in my model tests, but my feature tests fail when i try and submit a duplicate to the create action in the WantsController:
def create
#want = current_user.wants.build(params[:want])
if #want.save
flash[:success] = "success!"
redirect_to user_account_path current_user.username
else
flash[:validation] = #want.errors
redirect_to user_account_path current_user.username
end
end
The error I get: can't dump hash with default proc
No stack trace that leads to my code.
I have narrowed the issue down to this line:
self.wants.each { |w| does_want_already = true if w.name == want_name }
if I just return true regardless the error shows in my view as I would like.
I don't understand? What's wrong? and why is it so cryptic?
Thanks.
Without a stack trace (does it lead anywhere, or does it just not appear?) it is difficult to know what exactly is happening, but here's how you can reproduce this error in a clean environment:
# initialize a new hash using a block, so it has a default proc
h = Hash.new {|h,k| h[k] = k }
# attempt to serialize it:
Marshal.dump(h)
#=> TypeError: can't dump hash with default proc
Ruby can't serialize procs, so it wouldn't be able to properly reconstitute that serialized hash, hence the error.
If you're reasonably sure that line is the source of your trouble, try refactoring it to see if that solves the problem.
def already_wants? want_name
wants.any? {|want| want_name == want.name }
end
or
def already_wants? want_name
wants.where(name: want_name).count > 0
end

Add value to :params[]

What i have: (Action in Controller)
def create
#test = Test.new(params[:test])
#test.save
devicefiles = params[:devicefiles]
if devicefiles != nil
devicefiles.each do |attrs|
devicenote = Testdevicenote.new(attrs, :test_id => #test.id)
devicenote.save
end
end
end
This controller action does not show any error message and is rendering the view, but :test_id is not being saved in the database. How can i solve this?
EDIT: Ok whoops, I see it now...
Models only take one hash on initialize, not 2.
Testobjectnote.new(attrs.merge(:test_id => #test.id))
In short no one here has any clue, because that's not enough information. We dont know how your models are setup.
But when debugging models that "won't save" it's often good to use the bang version save, save!. save returns true or false letting you know if it was able to save the record. But save! will raise exceptions when the model can't be saved, and the exception will tell you why.
That exception will likely tell you why the record is not being saved.
Also, its usually better to use the associations, rather than manage the ids yourself.
def create
#test = Test.new(params[:test])
if params[:devicefiles]
params[:devicefiles].each do |attrs|
#test.testdevicenotes << Testdevicenotes(attrs)
end
end
#test.save
end
It's hard to say because you didn't post your view with the form that is posting to the create action, but if it's a typical Rails form, it should probably look like:
def create
#test = Test.new(params[:test])
#test.save
devicefiles = params[:test][:devicefiles]
if devicefiles != nil
devicefiles.each do |attrs|
devicenote = Testdevicenote.new(attrs, :test_id => #test.id)
devicenote.save
end
end
objectfiles = params[:test][:objectfiles]
if objectfiles != nil
objectfiles.each do |attrs|
objectnote = Testobjectnote.new(attrs, :test_id => #test.id)
objectnote.save
end
end
end
This assumes that :devicefiles and :objectfiles are inside the form :test

Is there a more ruby way of doing this

Ok so i have this helper
def current_company_title
(Company.find_by_id(params["company_id"]).name rescue nil) || (#companies.first.name rescue nil) current_user.company.name
end
Basically what I am achieving with this is the following ...
If the param["company_id"] exists then try to get the company and if not then
if #companies exists grab the first company name and if not then get the current users company name
This works but the rescues seem like a hack...any idea on another way to achieve this
Indeed rescue is kind of a hack, id' probably split it up into two methods and then use try to fetch the name if available: http://api.rubyonrails.org/classes/Object.html#method-i-try
def current_company
#current_company ||= Company.find_by_id(params[:company_id]) || #companies.try(:first) || current_user.try(:company)
end
def current_company_name
current_company.try(:name)
end
Company.find_by_id(params["company_id"]).name`
find and its derivates are meant to be used when you're sure-ish you'll have a positive result, and only in some cases (row was deleted, etc) errors. That's why it raises an exception. In your case, you're assuming it's gonna fail, so a regular where, which would return nil if no rows was found, would do better, and remove the first rescue
#companies.first.name rescue nil
could be replaced by
#companies.first.try(:name)
I'll let you check the api for more on the topic of try. It's not regular ruby, it's a Rails addition.
Less "magic", simple code, simple to read:
def current_company_title
company = Company.where(id: params["company_id"]).presence
company ||= #companies.try(:first)
company ||= current_user.company
company.name
end
Ps. Not a big fan of Rails' try method, but it solves the problem.
def current_company_title
if params["company_id"]
return Company.find_by_id(params["company_id"]).name
elsif #companies
return #companies.first.name
else
return current_user.company.name
end
end
The rescues are a hack, and will obscure other errors if they occur.
Try this:
(Company.find_by_id(params["company_id"].name if Company.exists?(params["company_id"]) ||
(#companies.first.name if #companies && #companies.first) ||
current_user.company.name
then you can extract each of the bracketed conditions to their own methods to make it more readable, and easier to tweak the conditions:
company_name_from_id(params["company_id"]) || name_from_first_in_collection(#companies) || current_user_company_name
def company_name_from_id(company_id)
company=Company.find_by_id(company_id)
company.name if company
end
def name_from_first_in_collection(companies)
companies.first.name if companies && companies.first
end
def current_user_company_name
current_user.company.name if current_user.company
end
[Company.find_by_id(params["company_id"]),
#companies.to_a.first,
current_user.company
].compact.first.name

Resources