I've a setter method for an attr_accessor in rails
# setter method of the shopify_p accessor
def shopify_v=(s)
begin
self.product.shop.connect_to_store
#shopify_v = s if s.save
ensure
ShopifyAPI::Base.site = nil
end
end
I'd like it to return true if save is successful or false if the save action doesn't work.
Instead it always outputs the s object (or #shopify_v, I don't know).
How can I make it return true or false depending on the save action?
Thanks,
Augusto
UPDATE #1
Here is the getter method of the same attr_accessor.
Basically it downloads the object from the server only in case it has never done it before.
# getter method of the shopify_v accessor
def shopify_v
if #shopify_v.nil?
begin
self.product.shop.connect_to_store
#shopify_v = ShopifyAPI::Variant.find(self.shopify_id)
ensure
ShopifyAPI::Base.site = nil
end
puts "remote"
return #shopify_v
else
puts "local"
return #shopify_v
end
end
def shopify_v=(s)
begin
self.product.shop.connect_to_store
#shopify_v = s if s.save
ensure
ShopifyAPI::Base.site = nil
end
#shopify_v.present? # will return true or false
end
I would use exceptions, since being unable to save is an exceptional condition. Also, whenever practical, a method should either have a side-effect, or return a value.
For example:
class ShopifyError < StandardError ; end
def shopify_v=(s)
begin
self.product.shop.connect_to_store
raise ShopifyError unless s.save
#shopify_v = s
ensure
ShopifyAPI::Base.site = nil
end
end
and in the caller:
begin
...
model.v = s
...
rescue ShopifyError
# set flash[:notify], or whatever error handling is appropriate
end
Also, there are cleaner ways to structure the getter. Consider doing something like this:
# getter method of the shopify_v accessor
def shopify_v
#shopify_v ||= fetch_v
end
private
def fetch_v
begin
self.product.shop.connect_to_store
ShopifyAPI::Variant.find(self.shopify_id)
ensure
ShopifyAPI::Base.site = nil
end
end
A setter always returns the value being set, no matter what you try to return. So you should use another method name, for example:
def set_shopify_v(s)
self.product.shop.connect_to_store
status = s.save
#shopify_v = s if status
status
rescue => exc
# Rails.logger.error(...)
false
ensure
ShopifyAPI::Base.site = nil
end
Related
Define a class which when initialized with a string e.g. 'abc' will return true if the method 'abc?' is called on it. Any other method with a trailing '?' will return false. All other methods which doesn't have a trailing '?' will raise NoMethodError
You can use method_missing to respond to messages for which there is no method.
In method_missing we can check the method name and if it ends in a ? check if it minus the ? is equal to the string (self).
When using method_missing it is custom to also define respond_to?.
class StringInquirer < String
private
def method_missing(method_name, *args, &block)
if method_name.to_s.end_with?('?')
self == method_name.to_s.delete('?')
else
super
end
end
def respond_to?(method_name, include_private = false)
method_name.to_s.ends_with('?') || super
end
end
name = StringInquirer.new('sally')
name.sally? # => true
Note this is case sensitive.
name.Sally? # => false
class NewStrInq < String
def initialize(val)
self.class.send(:define_method, "#{val}?") do
true
end
end
def method_missing(method)
method.to_s[-1] == '?' ? false : (raise NoMethodError)
end
end
class StringInquirer < String
def initialize(str)
define_singleton_method(str + '?') { true }
super(str)
end
end
name = StringInquirer.new('sally')
name.sally? # => true
name.kim? # => NoMethodError
name.nil? # => false
By raising NoMethodError for all methods ending in a question mark you will loose nil? etc.
What I'm thinking of is something where I can say:
e = Foo.new
e.bar = "hello"
e.save
e.reload
e.bar.nil!
e.reload
e.bar.nil? => true
Kind of #touch but sets nil and saves.
EDIT
Super sorry guys. I mean this:
e = Foo.new
e.bar = "hello"
e.save
e.reload
e.bar.nil!
e.reload
e.bar.nil? => true
Maybe something like:
module ActiveRecord
class Base
def nil!(*names)
unless persisted?
raise ActiveRecordError, <<-MSG.squish
cannot nil on a new or destroyed record object. Consider using
persisted?, new_record?, or destroyed? before nilling
MSG
end
unless names.empty?
changes = {}
names.each do |column|
column = column.to_s
changes[column] = write_attribute(column, nil)
end
primary_key = self.class.primary_key
scope = self.class.unscoped.where(primary_key => _read_attribute(primary_key))
if locking_enabled?
locking_column = self.class.locking_column
scope = scope.where(locking_column => _read_attribute(locking_column))
changes[locking_column] = increment_lock
end
clear_attribute_changes(changes.keys)
result = scope.update_all(changes) == 1
if !result && locking_enabled?
raise ActiveRecord::StaleObjectError.new(self, "nil")
end
#_trigger_update_callback = result
result
else
true
end
end
end
end
Put that in an initializer and it'll let you null out the title of a comment with Comment.last.nil!(:title).
You can't save a nil to the database, and furthermore, once an object has been created as a particular class you can never change that. It can only be converted by creating a new object, something an in-place modifier like this hypothetical nil! does.
The closest thing you can get is:
e = Foo.new
e.bar = "hello"
e.save
e.reload
e.delete!
e.reload
e.destroyed? # => true
f = Foo.find_by(id: e.id)
f.nil? # => true
I want to assign a confirmation code to my users while creating one. And I also titleize some columns before saving-updating them. So my user.rb looks like this (it may be a bit messy):
// user.rb
*** some code ***
before_save { titleize_column(:name)
titleize_column(:surname)
capitalize_column(:complaints)
capitalize_column(:education)
capitalize_column(:job)
capitalize_column(:complaintsdetails)
capitalize_column(:prediagnosis)
capitalize_column(:existingdiagnosis)
capitalize_column(:knownilnessesother)
capitalize_column(:usedmedicine)
capitalize_column(:operation)
capitalize_column(:trauma)
capitalize_column(:allergy)
capitalize_column(:otherhabits)
capitalize_column(:motherother)
capitalize_column(:fatherother)
capitalize_column(:siblingsother)
}
before_save :generate_confirmation_code
protected
def generate_confirmation_code
unless self[:confirmed]
if(self[:type] == 'Patient')
update_attribute :confirmation_code, SecureRandom.urlsafe_base64(20)
update_attribute :confirmed, false
else
update_attribute :confirmed, true
end
end
end
protected
def capitalize_column(attr)
unless self[attr].nil?
self[attr] = Unicode::capitalize self[attr]
end
end
protected
def titleize_column(attr)
unless self[attr].nil?
words = self[attr].split
words.each_with_index do |v,i|
words[i] = Unicode::capitalize v
end
self[attr] = words.join(" ")
end
end
I'm using separate methods for titleizing and capitalizing columns because they may be nil when first creating a user, so I'm checking if it is null or not in those methods. This structure works fine on a normal signup with strong parameters. However, if I try to use twitter signup with the method below, it gives me the error 'stack level too deep' and I can see that it calls the generate_confirmation_code 123 times from the application trace and then these happens:
app/models/user.rb:83:in each'
app/models/user.rb:83:ineach_with_index'
app/models/user.rb:83:in titleize_column'
app/models/user.rb:20:inblock in '
app/models/user.rb:64:in generate_confirmation_code' (x123 times)
app/models/user.rb:101:infrom_omniauth'
app/controllers/socials_controller.rb:4:in `create'
// method for signing up/logging in a user from twitter
class << self
def from_omniauth(auth_hash)
if exists?(uid: auth_hash['uid'])
user = find_by(uid: auth_hash['uid'])
else
user = find_or_create_by(uid: auth_hash['uid'], provider: auth_hash['provider'], type: 'Patient')
user.password_digest = User.digest('111111')
user.name = auth_hash['info']['name']
user.location = get_social_location_for user.provider, auth_hash['info']['location']
user.avatar = auth_hash['info']['image']
user.url = get_social_url_for user.provider, auth_hash['info']['urls']
user.save! // THIS IS THE LINE 101!
conversation = Conversation.create()
user.conversation = conversation
admin = Admin.first
admin.conversations << conversation
user.progress = Progress.create(active_state:1)
end
user
end
I think I'm messing up by using before_save not properly, but do not know how to do it right. What am I doing wrong here?
update_attribute also fires the save callbacks, thereby looping the before_save infinitely, thus producing stack level too deep.
You can just simply assign values in a before_save callback methods, because they will simply be saved afterwards anyway. See the following:
def generate_confirmation_code
unless self[:confirmed]
if(self[:type] == 'Patient')
self.confirmation_code = SecureRandom.urlsafe_base64(20)
self.confirmed = false
else
self.confirmed = true
end
end
end
You are calling update_attribute inside before_save callback method, instead you can just assign values to attributes. The method signature generate_confirmation_code should be like below -
def generate_confirmation_code
unless self[:confirmed]
if(self[:type] == 'Patient')
self.confirmation_code = SecureRandom.urlsafe_base64(20)
self.confirmed = false
else
self.confirmed = true
end
end
end
ruby-1.9.3-p194
Rails 3.0.9
I encountered so much strange behavior of conditional expression evaluation.
Look at piece of code:
module SimpleCaptcha
module ControllerHelpers
def simple_captcha_valid?
t = Logger.new(STDOUT)
return true if Rails.env.test?
if params[:captcha]
data = 'SHGHGD'
result = data == params[:captcha].delete(" ").upcase
t.debug data
t.debug params[:captcha].delete(" ").upcase
t.debug result
else
return false
end
end
end
end
Here is what I see in debug console:
SHGHGD
WEWE
nil
As you can see nil is a result of evaluation result = data == params[:captcha].delete(" ").upcase
But why???
data is 'SHGHGD'
params[:captcha].delete(" ").upcase is WEWE
Why nil? it must be false.
This is actually because of the logger - Logger.debug false outputs nil. To understand why you need to look into the logger class (logger.rb). The debug, info, warn etc. methods all end up calling
def add(severity, message = nil, progname = nil, &block)
severity ||= UNKNOWN
if #logdev.nil? or severity < #level
return true
end
progname ||= #progname
if message.nil?
if block_given?
message = yield
else
message = progname
progname = #progname
end
end
end
and message will be nil, progname will be the value you've passed (ie a string). The keyline is progname ||= #progname. Because progname is false, it is overwritten with the value of #progname, which is nil so that is what gets output.
I have the following module/class:
module Pigeons
class FedEx
attr_accessor :signature_name
def initialize(account)
self.account = account
end
def response(number)
body = "...some xml..."
return HTTParty.post('http://example.com', :body => body)
end
def track(number)
details = response(number)
self.signature_name = details[:delivery_signature_name]
end
end
end
What I'd like to be able to do is this:
#fedex ||= Pigeons::FedEx.new('123abc')
tracker = fedex.track('1234567890')
tracker.signature_name
Everything is working up until the tracker.signature_name part, which throws an undefined method 'signature_name' for nil:NilClass error.
The problem is in this line:
self.signature_name = details[:delivery_signature_name]
details[:delivery_signature_name] turns out to be nil, which is then assigned to self.signature_name and then becomes return value of track method. And here
tracker = fedex.track('1234567890')
tracker.signature_name
tracker will be nil and you try to call a method on it.
You probably meant to write this instead:
def track(number)
details = response(number)
self.signature_name = details[:delivery_signature_name]
self # <== here
end
You need two methods called signature_name= and response that do whatever they are supposed to do in your class.