Using variables for operator/methods - ruby

How do you access an operator/method via a variable?
So instead of this:
c = Computer.new
c.computer_name = "bla"
c.operating_system = "Windows XP"
c.user_name = "joesmith"
c.save
(plus many more)
I want to use a hash and assign the values dynamically:
c = Computer.new
params.each do |key,val|
c."#{key}" = val.to_s
end
c.save
Also, what is the correct terminology here?

I didn't try it, but Computer.new params should work.
Be careful of mass assignment though (it's not specific to DataMapper).

One trivial way (not DataMapper-specific, but assumes a prop_name= method), is to use send:
c.send("#{key}=".to_sym, val.to_s)

Related

assigning variables in Ruby blocks to higher scope

I'm trying to use the retriable gem.
I have a function that returns a value that's wrapped inside a retriable block like below.
Retriable.retriable do
x = some_function(1, 2)
end
....do something with x...
The only way I've figured out how to do this is by using a global variable i.e #x inside the block but I don't think that's necessarily the best way. I would appreciate it if someone could point me to a better way of doing this.
Thanks
In the general case, you solve this by declaring the variable outside the loop:
x = nil
%w[foo bar baz].each do |word|
x = word
end
puts x # => "baz"
See it in action here: https://repl.it/#jrunning/AbsoluteShamelessVerification
This exact case can be solved like this:
x = Retriable.retriable do
some_function(1, 2)
end
But in general, no, you can't declare variables in outer scopes. That's why we have scopes.
You should be able to assign a value to a new variable by whatever the block returns so:
x = Retriable.retriable do
some_function(1, 2)
end

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.

Use input name in return value variable name

I'm trying to make this simple method return a value related to the name of its input. For instance if I give the method "people_array" it should return "people_array_of_arrays."
If I were using the method in IRB I would get something like:
people_array = ["George\tMichael", "Kim\tKardashian", "Kanyne\tWest"]
=> ["George\tMichael", "Kim\tKardashian", "Kanyne\tWest"]
make_array_of_arrays(people_array)
=> people_array_of_arrays
people_array
=> ["George\tMichael", "Kim\tKardashian", "Kanyne\tWest"]
people_array_of_arrays
=> [["George", "Micahel"], ["Kim", "Kardashian"], ["Kayne", "West"]]
I have written this so far, but have not been able to figure out how to return a nicely named array of arrays. All I could think of was string interpolation but that isn't exactly what I need.
def make_array_of_arrays(array)
formatted_array = []
array.each do |feed|
mini_array = feed.split("\t")
formatted_array.push(mini_array)
end
#{array}_of_arrays = formatted_array
end
I saw there was a method variablize, but that returns an instance variable which isn't exactly what I want. Any pointers?
I do not think that it can be easily done. Suppose you were able to define a local variable in some way within the method definition. But the scope of that local variable is limited to the method definition. So the moment you go outside of the method definition, the local variable name is gone. So in order to do it, you have to somehow get the binding information of the environment outside of the method definition, and define a local variable within that. I do not know if that is possible.
With instance variables, things get a little easier using instance_variable_set, but I am not sure how to implement it fully. First of all, getting the name of the original variable is tricky.
And what you are trying to do is not the right approach. You should think of different ways.
I think the best you can do is to use an instance variable instead of a local variable, and also give the name of the variable explicitly instead of the array itself:
def make_array_of_arrays(variable_name)
array = instance_variable_get("##{variable_name}")
# Your code here
instance_variable_set("##{variable_name}_of_arrays", formatted_array)
end
#people_array = ["George\tMichael", "Kim\tKardashian", "Kanyne\tWest"]
make_array_of_arrays(:people_array)
#people_array_of_arrays
#=> [["George", "Micahel"], ["Kim", "Kardashian"], ["Kayne", "West"]]
This also might be useful.
No need for meta-programming (unless I misunderstand your question). Simply return your formatted array:
def make_array_of_arrays(array)
formatted_array = []
array.each do |feed|
mini_array = feed.split("\t")
formatted_array.push(mini_array)
end
formatted_array
end
Then you can assign the return value to whatever name you want:
people_array_of_arrays = make_array_of_arrays(people_array)
Note, you can use map to simplify your make_array_of_arrays method:
def make_array_of_arrays(array)
array.map do |feed|
feed.split("\t")
end
end
The big problem here is there's no good way to access the name of a variable.
Barring that and building on Sean Vieira's addition, you could do some eval magic to get this:
def make_array_of_arrays(array, array_name)
new_array = array.map { |feed| feed.split("\t") }
eval("def #{array_name}_of_arrays; return #{new_array}; end")
end
This basically creates a function for your *_of_arrays line that returns the array you're looking for.
If you could find a way to get the name of the variable, you'd have everything you want.
Not that I really officially endorse this method. And I can't for the life of me figure out why you'd want to do this. It's very unidiomatic, and will be confusing for anyone looking at that chunk of code.
This is not easy nor advisable.
Think of Ruby Objects as people (you and me) communicating by phone (phonenumbers being object_id's). Imagine I am in your list of phonenumbers under the name (variable) 'sTeEnSlAg' , and also under 'steenslg'. Then you phone me and ask "Please give me the name you are registered under on my phone, post-fixed with "_of_arrays".
What do you think would be the polite version of my answer?

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

What is the proper way to phrase this statement in Ruby?

I'm new at this, and I'm having trouble finding the proper way to phrase this in Ruby. And I don't know if the Ruby API in SketchUp is different. But, thats what I'm trying to use it for.
def self.initialize_job_info
return{
'salesperson' => ' = $pg_settings['salespersons'[['salesperson']['id']]] if ('on' = $pg_settings['salespersons'[['salesperson']['defsales']]])'
This is what I'm basically trying to do:
This part of the code works as it should
def self.initialize_job_info
return{
'salesperson' => ''
It sets an empty form's initial value of job_info['salesperson']'s value to ' ' if no pre-existing value is found.
So, there is a value I want to place in the Hash that is being passed from $pg_settings.
The value I want is, and I hope this make sense, the value of this specific 'id'
$pg_settings['salespersons'] {//which is a list of 'salesperson'
<salesperson> id="561" name="name" phone="phone number" defsales="on" email="email" </salesperson>
if (defsales == "on") then 'salesperson' => 'value="id"'
Does this make sense?
I'm pulling my hair out, so any help you can give on this would be great.
if those names not inside the quotes are variables that you want to get the values from it should probably be:
'salesperson' => " = $pg_settings[#{salespersons}[[#{salesperson}][#{id}]]] if (#{on} = $pg_settings[#{salespersons}[[#{salesperson}][#{defsales}]]])"
but as Geo said, more detail on the actual purpose/intent would help
BTW, that construc tis called string interpolation (http://en.wikibooks.org/wiki/Ruby_Programming/Syntax/Literals#Interpolation)
If you want to interpolate the strings, as in include their value in a string, then maybe this examples can help you:
a = "a string"
b = "this is"
c = "#{b} #{a}"
In the example above, c will have the value: this is a string . Also, while interpolating, valid Ruby code is accepted. So, this is ok too:
c = "#{ b.sub("this","") } #{a}"
And in this case, c will have the value is a string . So, if you need to interpolate something, first think about how you would do it using normal code, and then just add #{} around it.
I figured it out.
Here is the working code
def self.initialize_job_info
return{
'salesperson' => self.default,
}
end
def self .default
salespersons = $pg_settings['salespersons']
salespersons.each do |salesperson|
if (salesperson['defsales'] == 'on')
return salesperson['id']
end
end
end
Looks like I was a long way off.......lol

Resources