Ruby compact assignment syntax - ruby

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

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)

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.

Related

Ruby && and = operators misudnerstanding

What do you think would be the result of the next expression in Ruby?
a = 10 && b = 25
Try to calculate in the ming and only then use irb. So, if we take a look at the Ruby documentation about Operators Precedence then we will se that && operator has a higher priority than =. So you must think that Ruby will evaluate the expression in the next way:
a = ((10 && b) = 25)
But Ruby does a job in another way:
a = (10 && (b = 25))
# => 25
So, the priority of the = in b = 25 is higher, then &&. Can anybody explain why it is happend like so?
This has to do with the way Ruby identifies bareword identifiers
When it comes across an identifier like a, it has to resolve it by checking to see if it is a keyword, local variable or method in that order.
keyword - if a is a keyword then use it as a keyword
local variable - is there an equal sign to the right of a, then it must be a local variable. If not check if there is any local variable a defined. These points are very important, it is the reason why you can't call writer methods in instance methods without explicitly calling self.
Take for example this code
class Person
attr_accessor :name #creates both attr_reader name & attr_writer name=(text)
def print_name
name # => attr_reader method name is called and returns nil
name = 'Bola'#even though we have the name= method, it doesn't get called
#what happens is a local variable name is created instead
#this is as a result of how ruby interpreted the bareword identifier name
#not a keyword but has an equal sign so must be a local variable
name # this time local variable is used instead of method because it is resolved first
end
end
method if it's not resolved as a keyword or local variable then it assumes it's a method and tries to call it.
So this is how the code is evaluated
what is to the left of && 10, which it can make sense of
compare that 10 to what is on the right of && which is b but it has to evaluate what b is so it resolves it using the procedure I described above which results in local variable b = 25. Assignment operations always return the values on their right which is 25
compare 10 and 25 which returns 25
finally a = 25
With that being said && does have higher priority and the = signs are of the same precedence. Hope this explanation was clear.

Plus equals with ruby send message

I'm getting familiar with ruby send method, but for some reason, I can't do something like this
a = 4
a.send(:+=, 1)
For some reason this doesn't work. Then I tried something like
a.send(:=, a.send(:+, 1))
But this doesn't work too. What is the proper way to fire plus equals through 'send'?
I think the basic option is only:
a = a.send(:+, 1)
That is because send is for messages to objects. Assignment modifies a variable, not an object.
It is possible to assign direct to variables with some meta-programming, but the code is convoluted, so far the best I can find is:
a = 1
var_name = :a
eval "#{var_name} = #{var_name}.send(:+, 1)"
puts a # 2
Or using instance variables:
#a = 2
var_name = :#a
instance_variable_set( var_name, instance_variable_get( var_name ).send(:+, 1) )
puts #a # 3
See the below :
p 4.respond_to?(:"+=") # false
p 4.respond_to?(:"=") # false
p 4.respond_to?(:"+") # true
a+=1 is syntactic sugar of a = a+1. But there is no direct method +=. = is an assignment operator,not the method as well. On the other hand Object#send takes method name as its argument. Thus your code will not work,the way you are looking for.
It is because Ruby doesn't have = method. In Ruby = don't work like in C/C++ but it rather assign new object reference to variable, not assign new value to variable.
You can't call a method on a, because a is not an object, it's a variable, and variables aren't objects in Ruby. You are calling a method on 4, but 4 is not the thing you want to modify, a is. It's just not possible.
Note: it is certainly possible to define a method named = or += and call it, but of course those methods will only exist on objects, not variables.
class Fixnum
define_method(:'+=') do |n| self + n end
end
a = 4
a.send(:'+=', 1)
# => 5
a
# => 4
This might miss the mark a bit, but I was trying to do this where a is actually a method dynamically called on an object. For example, with attributes like added_count and updated_count for Importer I wrote the following
class Importer
attr_accessor :added_count, :updated_count
def increment(method)
send("#{method}=", (send(method) + 1))
end
end
So I could use importer.increment(:added_count) or importer.increment(:updated_count)
Now this may seem silly if you only have these 2 different counters but in some cases we have a half dozen or more counters and different conditions on which attr to increment so it can be handy.

Ruby Struct instance cannot find attributes which have '?' in their name

If I create a Struct with an attribute which contains a question mark, any instance of that class will not be able to find that method. For e.g.
Test = Struct.new(:value, :value?)
t = Test.new(true,true)
t.value
=> true
t.value?
NoMethodError: undefined method `value?' for #<struct Test value=true, :value?=true>
Any idea ? I am using Ruby 1.9.3-p286.
Yo'll have to concede that some method names in Ruby are special. For example, if you defined method
o = Object.new
def o.kokot= n
return n + 1
end
And call
o.kokot 1
#=> 1
The result will still be 1, not 2 as you might expect. This is peculiarity of = sign in method names. In your case of Structs, question mark seems to have such peculiarity, too, which prevents you from retrieving value by calling:
t.value?
You have to call
t[:value?]
That's it, have a nice day.

Ruby min max assignment operators

When programming ruby I always find myself doing this:
a = [a, b].min
This means compare a and b and store the smallest value in a. I don't like writing the code above as I have to write a twice.
I know that some non-standard dialects of C++ had an operator which did exactly this
a <?= b
Which I find very convenient. But I'm not really interested in the operator as much as I'm in the feature of avoiding repetition. I would also be happy if I could write
a.keep_max(b)
a can be a quite long variable, like my_array[indice1][indice2], and you don't want to write that twice.
I did alot of googling on this and found no result, hopefully this question will pop up and be useful for others aswell.
So, is there any non-repeitive way to express what I want in ruby?
What you would like to do is in fact not possible in ruby (see this question). I think the best you can do is
def max(*args)
args.max
end
a = max a, b
I don't understand your question. You can always do something like this ...
module Comparable
def keep_min(other)
(self <=> other) <= 0 ? self : other
end
def keep_max(other)
(self <=> other) >= 0 ? self : other
end
end
1.keep_min(2)
=> 1
1.keep_max(2)
=> 2
Well, that won't work for all objects with <=> because not all of them are implementing Comparable, so you could monkey-patch Object.
Personally I prefer clarity and tend to avoid monkey-patching. Plus, this clearly is a binary predicate, just like "+", therefore method-chaining doesn't necessarily make sense so I prefer something like this to get rid of that array syntax:
def min(*args)
args.min
end
def max(*args)
args.max
end
min(1, 2)
=> 1
max(1, 2)
=> 2
But hey, I'm also a Python developer :-)
You can define your own method for it:
class Object
def keep_max(other)
[self, other].max
end
end
a = 3
b = 7
puts a.keep_max(b)
But you should be careful defining methods on Object as it can have unpredictable behaviour (for example, if objects cannot be compared).
def keep_max(var, other, binding)
eval "#{var} = [#{var}, #{other}].max", binding
end
a = 5
b = 78
keep_max(:a, :b, binding)
puts a
#=> 78
This basically does what you want. Take a look at Change variable passed in a method

Setting variable A with name stored in variable B

I have the following two variables:
a = 1;
b = 'a';
I want to be able to do
SOMETYPEOFEVALUATION(b) = 2;
so that the value of variable a is now set to 2.
a # => 2
Is this possible?
Specifically, I am working with the Facebook API. Each object has a variety of different connections (friends, likes, movies, etc). I have a parser class that stores the state of the last call to the Facebook API for all of these connections. These states are all named corresponding to the the GET you have to call in order to update them.
For example, to update the Music connection, you use https://graph.facebook.com/me/music?access_token=... I store the result in a variable called updated_music. For books, its updated_books. If I created a list of all these connection type names, I ideally want to do something like this.
def update_all
connection_list.each do |connection_name|
updated_SomeTypeOfEvalAndConcatenation(connection_name) = CallToAPI("https://graph.facebook.com/me/#{connection_name}?access_token=...")
end
end
Very new to both Rails and StackOverflow so please let me know if there is a better way to follow any conventions.
Tried the below.
class FacebookParser
attr_accessor :last_albums_json,
def update_parser_vars(service)
handler = FacebookAPIHandler.new
connections_type_list = ['albums']
connections_type_list.each do |connection_name|
eval "self.last_#{connection_name}_json = handler.access_api_by_content_type(service, #{connection_name})['data']"
end
#self.last_albums_json = handler.access_api_by_content_type(service, 'albums')['data']
end
end
And I get this error
undefined local variable or method `albums' for #<FacebookParser:0xaa7d12c>
Works fine when I use line that is commented out.
Changing an unrelated variable like that is a bit of a code smell; Most programmers don't like it when a variable magically changes value, at least not without being inside an enclosing class.
In that simple example, it's much more common to say:
a=something(b)
Or if a is a more complex thing, make it a class:
class Foo
attr_accessor :a
def initialize(value)
#a = value
end
def transform(value)
#a = "new value: #{value}"
end
end
baz = "something"
bar = Foo.new(2)
bar.a
=> 2
bar.transform(baz)
bar.a
=> "new value: something"
So while the second example changes an internal variable but not through the accessor, at least it is part of an encapsulated object with a limited API.
Update Ah, I think the question is how do do like php's variable variables. As mu suggests, if you want to do this, you are probably doing the wrong thing... it's a concept that should never have been thought of. Use classes or hashes or something.
how about
eval "#{b}=2"
and with instance variables you can also do instance_variable_set("#name", value)
EDIT:
you can also use send method if you have a setter defined(and you have), try this:
class FacebookParser
attr_accessor :last_albums_json,
def update_parser_vars(service)
handler = FacebookAPIHandler.new
connections_type_list = ['albums']
connections_type_list.each do |connection_name|
send("last_#{connection_name}_json=",
handler.access_api_by_content_type(
service, connection_name)['data']))
end
end
end
problem with your original code is that
eval ".... handler.access_api_by_content_type(service, #{connection_name})"
would execute
... handler.access_api_by_content_type(service, albums)
# instead of
... handler.access_api_by_content_type(service, 'albums')
so you had to write
eval ".... handler.access_api_by_content_type(service, '#{connection_name}')" <- the quotes!
this is why people usually avoid using eval - it's easy to do this kind of mistakes
These sort of things are not usually done using local variables and their names in Ruby. A usual approach could include hashes and symbols:
data = Hash.new
data[:a] = 1 # a = 1
b = :a # b = 'a'
and then, later
data[b] = 2 # SOMETYPEOFEVALUATION(b) = 2
data[:a] # => 2

Resources