Reputation: 371
I want to do a compact error checking assignment in ruby.
class User
attr_accessor :x
end
user = User.new
user.x = 5
a = b || user.x
I want to figure out which of these is the first valid attribute and assign it, similarly to how javascript handles different API's, i.e.:
var AudioContext = window.AudioContext||window.webkitAudioContext;
audioContext = new AudioContext();
and figure out which was valid.
With ruby however, similar syntax gives errors when I reference an undefined variable. i.e.:
a = 10
b = 7
c = a || b
c # => 10
vs
a = 10
c = b || a # => Error: b is undefined
Is there a clean way to do this? Or at the very least, what is the best way to do this?
I'm working with a large code that I haven't created, and I am not permitted to change it.
UPDATE: I think the real use case is kind of relevant to this question so i'll explain it.
I have a module which saves something to the DB every time a model in rails is updated, this update requires an id field, this id field is inside the model that includes my module, however not every model maintains the same naming convention for this id. The ternary operator equivalent of what I want to do is
id = defined?(self.id) ? self.id : defined?(self.game_id) ? self.game_id : defined?(self.app_id) ? self.app_id : nil
which is hard to read and write compared to the js equivalent
Upvotes: 1
Views: 213
Reputation: 371
Okay so there is a much more concise way of doing this, but it has a side-effect of assigning something to the undefined var.
This breaks
a = 1
c = b || a # => b is undefined
This works
a = 1
c = b ||= a # => c == 1, b == 1
The above assigns b to c if b is valid, then falls back on a. a is only assigned to b (and c) if b is undefined/invalid.
Upvotes: 0
Reputation: 9157
You can use defined?
to test if a name refers to something recognizable in current scope (method, local variable, etc):
c = defined?(b) ? b : a # of course this assumes that 'a' is defined
Although it's pretty iffy that you're assigning from local variables that haven't been defined - how does that come up?
Or are you always testing for properties? In which case, respond_do?
may be the better choice.
I was using the ternary operator as an example, you could always use an if/elsif/else block, of course.
Since you're only testing methods, respond_to?
is more convenient than defined?
because it takes symbols, rather than expressions, to which you can apply any logic you want:
def invoke_first *names
names.each do |name|
if respond_to? name
return send name
end
end
return nil # or, more likely, raise something
end
or, more concisely:
def invoke_first *names
send names.find(lambda {raise 'no method'}){|n| respond_to?(n)}
end
include in your model and use as:
invoke_first(:foo_id, :bar_id, :baz_id)
Upvotes: 3