Why not interpreted code can affect "messages" behavior in Ruby? - ruby-on-rails

I've got a validator in ActiveRecord model, in which I faced with some realy strange behavior.
Example:
if status_changed?
p status # output on line below
# <= "my_status_1"
p my_conditions_1 # output on line below
# <= false
if my_conditions_1
errors.add(:status, 'Error1')
status = status_was
end
p status # output on line below
# <= nil
# my_conditions_2 depends on "status variable"
if my_conditions_2
errors.add(:status, 'Error2')
status = 2
end
end
Second condition always failed, because status somehow was setted to nil. But when I changed status to self.status everything started working as expected.
UPDATED
I've got the rule, that in case of assigning attribute I have to use self, thanks everyone who explained it. But part of the code's behavior still doesn't obvious to me
More general example:
class Detector
def status
"Everything ok"
end
def check
p status
# <= "Everything ok"
if false
status = "Danger!"
end
p status
# <= nil
end
end
detector = Detector.new
detector.check
Can someone explain it? How not interpreted code can "redirect" message from method to a variable? Is it ok?

To access object's attribute it's fine to do it with attribute.
While updating this attribute one should be using self.attribute, because otherwise how should Rails know you mean to set its attribute, not define local variable?
Rule of thumb: use self for assigning attribute, don't use it for reading the attribute.
EDIT
Regarding your update:
As #Jörg W Mittag said (who would say better?):
Well, status is un-initialized, und un-initialized local variables
evaluate to nil, just like instance variables.
To make your code sample behave as you expect you would want to call status as a method. Look:
class Detector
def status
"Everything ok"
end
def check
p status
# <= "Everything ok"
status = "Danger!" if false
status() # or method(:status).call
# <= "Everything ok"
end
end
First p status works because Ruby looks for local variable status. When it does not find it, it looks for a method called status (by method lookup). So it prints it "Everything ok".
Then in parses the if statement and sees, that there's un-initialized local variable status. Thus, when you reference it, it is legitimately nil.
So in other words, make Ruby know exactly, what you mean.

If you are updating the attribute then you must use self
self.status = 'something'
otherwise rails will assume status as a local variable so
puts self.status
#=> "something"
status = 'abc'
puts self.status
#=> "something"
self.status = 'something else'
puts self.status
#=> "something else"
But you can access the attribute with just status.
why status was set to nil?
Maybe because of this line
status = status_was
before status_changed? maybe the self.status was nil

Related

Rails/Ruby incorrectly showing variable not defined

In debugging console, while app running (using binding.pry to interrupt it), I can see that my variable Rails.configuration.hardcoded_current_user_key is set:
pry(#<TasksController>)> Rails.configuration.hardcoded_current_user_key
=> "dev"
But it doesn't appear to be defined:
pry(#<TasksController>)> defined?(Rails.configuration.hardcoded_current_user_key)
=> nil
Yet it works fine to store and test its value:
pry(#<TasksController>)> tempVar = Rails.configuration.hardcoded_current_user_key
=> "dev"
pry(#<TasksController>)> defined?(tempVar)
=> "local-variable"
What is going on?
This is because Rails config implements respond_to? but not respond_to_missing?, and defined? only recognizes respond_to_missing?:
class X
def respond_to?(name, include_all = false)
name == :another_secret || super
end
private
def method_missing(name, *args, &block)
case name
when :super_secret
'Bingo!'
when :another_secret
'Nope.'
else
super
end
end
def respond_to_missing?(name, include_all = false)
name == :super_secret || super
end
end
x = X.new
puts x.super_secret # => Bingo!
p defined?(x.super_secret) # => "method"
puts x.another_secret # => Nope.
p defined?(x.another_secret) # => nil
It's recommended to implement respond_to_missing? along with method_missing, I too wonder why Rails did it that way.
You shouldn't be using defined? on anything but the "stub" of that, or in other words, merely this:
defined?(Rails)
Anything beyond that is highly unusual to see, and I'm not even sure it's valid.
defined? is not a method, but a construct that tests if the following thing is defined as a variable, constant or method, among other things. It won't evaluate your code, it will just test it as-is. This means method calls don't happen, and as such, can't be chained.
If you want to test that something is assigned, then you should use this:
Rails.configuration.hardcoded_current_user_key.nil?

Ruby: 'gets' method called inside a method saves Nil instead of Input

Ok guys, this in a Dungeon Text Adventure project that runs on terminal.
When user says he wants to "go north" it splits the string: first word checks for method and second word for parameter. That happens someplace else, i copy pasted the 2 methods that are giving me the following problem:
When it calls go(north) and north isn't a valid connection, it shows the options and asks user for direction again, the input the user enters at that moment is stored as Nil for some reason.
Why?? I also tried STDIN.gets.chomp.downcase! and had same Nil results
Here's the code:
def find_room_in_direction(direction)
##-> if room connects with other on that direction, returns connection
if find_room_in_dungeon(#player.location).connections.include?(direction.to_sym)
return find_room_in_dungeon(#player.location).connections[direction.to_sym]
##-> if direction connection is not found,
##-> show possible connections & return trigger to ask again inside go()
elsif !find_room_in_dungeon(#player.location).connections.include?(direction.to_sym)
puts "I don't see any #{direction}..."
puts "This room only connects #{(find_room_in_dungeon(#player.location)).connections.keys.join(', ')}"
puts "Where should we go?"
return :redo
end
end
def go(direction)
current_direction = #player.location
new_direction = find_room_in_direction(direction)
##-> if REDO trigger received, ask for new direction & try again
if new_direction == :redo
puts "REDOING DIRECTION"
##-> HERE IS THE PROBLEM:
##-> this does ask for input
new_direction = gets.chomp.downcase!
##-> but it saves Nil instead of the input, so that puts shows ''
puts "#{new_direction}"
##-> so this call trows an error: cant call method to Nil
new_direction = find_room_in_direction(new_direction)
##-> if user entered valid direction from start, the following would run
elsif new_direction != :redo && current_direction != new_direction
#player.location = new_direction
puts "You go #{direction},"
puts "and enter #{show_current_description}"
end
end
Any suggestions?
Thanks!
You are using downcase! instead of downcase. downcase! changes a string in-place and returns nil if there were no changes (which is what is happening here).
str = "teSt"
puts str.downcase # test
puts str # teSt
str.downcase!
puts str # test
See the documentation for downcase!
Just bang chomp. As for downcase there are two variants - chomp and chomp!

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

Specifying allowed values for a method parameter

Like in title - for example I have a method DrawMe(what) and I want to allow to run this method when what argument is equal to one of this values: {"house", "garden", "cat", "dog"} - and if not then this method should be stopped and an error should be printed. Any ideas?
class Draw
ALLOWED = %w[house garden cat dog]
def self.me(what)
raise ArgumentError, "Unknown drawable '#{what}'" unless ALLOWED.include?(what)
# Otherwise, carry on!
puts "I'm going to draw a #{what}!"
end
end
Draw.me('garden') #=> I'm going to draw a garden!
Draw.me('cat' ) #=> I'm going to draw a cat!
Draw.me('morals') #=> RuntimeError: Unknown drawable 'morals'
However, note that most of the time you should not be ensuring that developers passed the right type of value into your method. Your method will raise its own error if something explodes as a result of misuse; it's a waste of your time and the computer's time to attempt to check and catch errors like this.
Edit: If you need to use this frequently, you could monkeypatch it in everywhere:
class Object
def ensure_in( enumerable )
unless enumerable.include?( self )
raise ArgumentError, "#{self} must be one of #{enumerable}"
end
end
end
def me(what)
what.ensure_in( ALLOWED )
# Go ahead
end

Returning true or error message in Ruby

I'm wondering if writing functions like this is considered good or bad form.
def test(x)
if x == 1
return true
else
return "Error: x is not equal to one."
end
end
And then to use it we do something like this:
result = test(1)
if result != true
puts result
end
result = test(2)
if result != true
puts result
end
Which just displays the error message for the second call to test.
I'm considering doing this because in a rails project I'm working on inside my controller code I make calls to a model's instance methods and if something goes wrong I want the model to return the error message to the controller and the controller takes that error message and puts it in the flash and redirects. Kinda like this
def create
#item = Item.new(params[:item])
if !#item.nil?
result = #item.save_image(params[:attachment][:file])
if result != true
flash[:notice] = result
redirect_to(new_item_url) and return
end
#and so on...
That way I'm not constructing the error messages in the controller, merely passing them along, because I really don't want the controller to be concerned with what the save_image method itself does just whether or not it worked.
It makes sense to me, but I'm curious as to whether or not this is considered a good or bad way of writing methods. Keep in mind I'm asking this in the most general sense pertaining mostly to ruby, it just happens that I'm doing this in a rails project, the actual logic of the controller really isn't my concern.
I would say that methods that return different types (e.g. boolean vs. string vs. numbers) under different circumstances are a bad practice.
If you have some sort of test method that wants to return details of why the test has not passed then you can return a pair of values (an Array) as follows:
def test(x)
if x == 1
return true, "x is fine"
else
return false, "Error: x is not equal to one."
end
end
and then write the section of your controller code as:
valid, message = #item.save_image(params[:attachment][:file])
if !valid
flash[:notice] = message
redirect_to(new_item_url) and return
end
If you're talking about a save_image method that will succeed the majority of the time but may fail and you want to indicate this failure and the reason then I would use exceptions e.g.
def save_image(file)
raise "No file was specified for saving" if file.nil?
# carry on trying to save image
end
and then your controller code would be along the lines of:
begin
result = #item.save_image(params[:attachment][:file])
rescue Exception => ex
flash[:notice] = ex.message
redirect_to(new_item_url) and return
end

Resources