gdxn96
gdxn96

Reputation: 371

Ruby compact assignment syntax

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

Answers (2)

gdxn96
gdxn96

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

Dmitri
Dmitri

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.

Edit

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

Related Questions