Ruby - Invoking a class from a CONSTANT that contains the class name - ruby

I have a class that calls different suppliers to find if an item is available. How do I execute the class that each constant returns?
class ItemProvider
ADAPTER_ONE = Providers::ItemFromSupplierOne
ADAPTER_TWO = Providers::ItemFromSupplierTwo
def get_item(item)
id = ItemDetail.new(item)
%w(ADAPTER_ONE ADAPTER_TWO).each do |provider|
item_detail = provider.new(id)
break if item_detail.valid?
end
item_detail
end

Your problem is that you aren't making an array that contains the constants' values; you're making an array with the strings "ADAPTER_ONE" and "ADAPTER_TWO". The %w() syntax always makes an array of strings — it doesn't resolve variable names.
What you want is to change your get_item code to something like this:
def get_item(item)
id = ItemDetail.new(item)
[ADAPTER_ONE, ADAPTER_TWO].each do |provider|
item_detail = provider.new(id)
break item_detail if item_detail.valid?
end or nil # break automatically makes the block return the value you break with
end
As an aside, personally, I think I'd rewrite it like this:
def get_item(item)
id = ItemDetail.new(item)
[ADAPTER_ONE, ADAPTER_TWO].map {|provider| provider.new(id) }.find &:valid?
end

Yup you have an array of strings not constants but if you want to go down that road in using classes from strings well it will be nice if you look at http://blog.sidu.in/2008/02/loading-classes-from-strings-in-ruby.html#.UuGdmGQ1i2w .Maybe it is not directly related to your problem but it is a good read.

Related

How to create and use variables dynamically named by string values in Ruby?

I'm using SitePrism to create some POM tests. One of my page classes looks like this:
class HomePage < SitePrism::Page
set_url '/index.html'
element :red_colour_cell, "div[id='colour-cell-red']"
element :green_colour_cell, "div[id='colour-cell-green']"
element :blue_colour_cell, "div[id='colour-cell-blue']"
def click_colour_cell(colour)
case colour
when 'red'
has_red_colour_cell?
red_colour_cell.click
when 'green'
has_green_colour_cell?
green_colour_cell.click
when 'blue'
has_blue_colour_cell?
blue_colour_cell.click
end
end
end
The method click_colour_cell() get its string value passed from a Capybara test step that calls this method.
If I need to create additional similar methods in the future, it can become rather tedious and unwieldy having so many case switches to determine the code flow.
Is there some way I can create a variable that is dynamically named by the string value of another variable? For example, I would like to do something for click_colour_cell() that resembles the following:
def click_colour_cell(colour)
has_#colour_colour_cell?
#colour_colour_cell.click
end
where #colour represents the value of the passed value, colour and would be interpreted by Ruby:
def click_colour_cell('blue')
has_blue_colour_cell?
blue_colour_cell.click
end
Isn't this what instance variables are used for? I've tried the above proposal as a solution, but I receive the ambiguous error:
syntax error, unexpected end, expecting ':'
end
^~~ (SyntaxError)
If it is an instance variable that I need to use, then I'm not sure I'm using it correctly. if it's something else I need to use, please advise.
Instance variables are used define properties of an object.
Instead you can achieve through the method send and string interpolation.
Try the below:
def click_colour_cell(colour)
send("has_#{colour}_colour_cell?")
send("#{colour}_colour_cell").click
end
About Send:
send is the method defined in the Object class (parent class for all the classes).
As the documentation says, it invokes the method identified by the given String or Symbol. You can also pass arguments to the methods you are trying to invoke.
On the below snippet, send will search for a method named testing and invokes it.
class SendTest
def testing
puts 'Hey there!'
end
end
obj = SendTest.new
obj.send("testing")
obj.send(:testing)
OUTPUT
Hey there!
Hey there!
In your case, Consider the argument passed for colour is blue,
"has_#{colour}_colour_cell?" will return the string"has_blue_colour_cell?" and send will dynamically invoke the method named has_blue_colour_cell?. Same is the case for method blue_colour_cell
Direct answer to your question
You can dynamically get/set instance vars with:
instance_variable_get("#build_string_as_you_see_fit")
instance_variable_set("#build_string_as_you_see_fit", value_for_ivar)
But...
A Warning!
I think dynamically creating variables here and/or using things like string-building method names to send are a bad idea that will greatly hinder future maintainability.
Think of it this way: any time you see method names like this:
click_blue_button
click_red_button
click_green_button
it's the same thing as doing:
add_one_to(1) // instead of 1 + 1, i.e. 1.+(1)
add_two_to(1) // instead of 1 + 2, i.e. 1.+(2)
add_three_to(1) // instead of 1 + 3, i.e. i.+(3)
Instead of passing a meaningful argument into a method, you've ended up hard-coding values into the method name! Continue this and eventually your whole codebase will have to deal with "values" that have been hard-coded into the names of methods.
A Better Way
Here's what you should do instead:
class HomePage < SitePrism::Page
set_url '/index.html'
elements :color_cells, "div[id^='colour-cell-']"
def click_cell(color)
cell = color_cells.find_by(id: "colour-cell-#{color}") # just an example, I don't know how to do element queries in site-prism
cell.click
end
end
Or if you must have them as individual elements:
class HomePage < SitePrism::Page
set_url '/index.html'
COLORS = %i[red green blue]
COLORS.each do |color|
element :"#{color}_colour_cell", "div[id='colour-cell-#{color}']"
end
def cell(color:) # every other usage should call this method instead
#cells ||= COLORS.index_with do |color|
send("#{color}_colour_cell") # do the dynamic `send` in only ONE place
end
#cells.fetch(color)
end
end
home_page.cell(color: :red).click

Dynamically creating objects in Ruby

I have a class whose initialize method defines a few instance variables and does some calculations. I need to create about 60 objects of that class. Each object has an ID number at the end. E.g.:
object1 = Dynamic.new(x, y)
object2 = Dynamic.new(x, y)
object3 = Dynamic.new(x, y)
...
I could just define them all by hand, but that would be quite inefficient. Is there any way to dynamically create each object?
You can always make a loop and push all the objects into an array. An array position might also be needed for knowing which object is each. This isn't quite what you wanted (atleast I don't think so), but it should suffice.
class Dynamic
##instances_of_class = 0
def initialize(x,y)
#...
#array_position = ##instances_of_class
##instances_of_class += 1
end
end
ary = []
50.times do
ary << Dynamic.new(x,y)
end
Edit: This solution, as said in the comments, can cause bugs if you change the array, so here's an alternate solution.
require 'File.rb'
i = 1
varFile = File.open("File.rb","a+")
50.times do
varFile.puts "variable#{i} = Object.new"
i += 1
end
Inside of File.rb will be 50 uniquely named variables that you can use.
I would be curious to know why you need this. It's an unusual requirement, and often that means that you can avoid the problem instead of solving it. I think TheLuigi's solution would work, but if you use a class variable then these Id's will be shared across multiple classes. You can instead use an instance variable, with something like the following:
class A
def self.next_id
#id ||= 0 ; #id += 1
end
def initialize
#id = A.next_id
end
end
A.new
# => #<A:0x007fd6d414c640 #id=1>
A.new
# => #<A:0x007fd6d41454a8 #id=2>
If you just want sixty objects accessible from a variable, you should have them in an array referred to by a single variable.
objects = Array.new(60){Dynamic.new(x, y)}
Your object1, object2, ... will correspond to objects[0], objects[1], ... respectively.

How to make a Ruby method to pass output parameters (change the value of referenced arguments)?

I'm trying to make a method with output arguments in ruby.
I read differents posts here and here about the discussion of wether ruby pass its arguments by-value or by-reference and
I undersand that on a strict sens, Ruby always pass-by-value, but the value passed is actually a reference. Reason why there is so much debate on this.
I find out that there are several ways to change the value of the referenced variable.
For instance with the replace method when its an Array, a Hash or a String, or merge! when it's a hash.
I found out that with integer, I can change and pass the value outside my method without any special method use.
My question is about other objects.
For instance I want to retrieve the 'id' attribute of an object, and the object reference itself :
class RestaurantController < ApplicationController
def pizza_to_deliver(pizza_name, id_of_the_order, pizza)
# pizza to eat
pizza = Pizza.where(:name => pizza_name).first
# unknown pizza
return false if pizza.nil?
# first customer order about this pizza
id_of_the_order = Orders.where(:pizza_id => pizza.id).first
true
end
end
my_pizza_name = 'margerita'
My_order_id = nil
my_pizza = nil
my_restaurant = RestaurantController.new
if my_restauant.pizza_to_deliver(my_pizza_name, My_order_id, my_pizza) then
puts "Pizza to deliver : #{my_order_id}"
rex_dog.eat(my_pizza)
end
How to make this works ? (order_id and my_pizza remains with nil)
Ruby has only pass by value, just like Python and Java. Also like Python and Java, objects are not values directly, and are manipulated through references.
It seems you already understand how it works -- assigning to a local variable never has any effect on a caller scope. And to "share" information with the caller scope other than returning, you must use some method on the object to "mutate" the object (if such a method exists; i.e. if the object is mutable) that is pointed to by the passed reference. However, this simply modifies the same object rather than giving a reference to a new object, which you want.
If you are not willing to return the value, you can pass a mutable container (like an array of one element) that the called function can then mutate and put whatever in there and have it be seen in the caller scope.
Another option is to have the function take a block. The function would give the block the new value of pizza, and the block (which is given by the caller) can then decide what to do with it. The caller can pass a block that simply sets the pizza in its own scope.
For the most part, out parameters are a workaround for languages that don't have multiple-value return. In Ruby, I'd just return an Array containing all the output values of the function. Or make the mutable values instance variables in an object and the function a method on that object.
Thanks for both answers.
It seems I came out with an equivalent solution at last : the mutable container.
I created a new class 'OutputParameter' that contains (as attr_accessors) the parameters that I want to output from my method. Then I passed an instance of this class to my method.
class OutputParameters
attr_accessor :order_id, pizza
end
class RestaurantController < ApplicationController
def pizza_to_deliver(pizza_name, output_parameters)
# pizza to eat
pizza = Pizza.where(:name => pizza_name).first
# unknown pizza
return false if pizza.nil?
# first customer order about this pizza
id_of_the_order = Orders.where(:pizza_id => pizza.id).first
# Output values returned
output_parameters.pizza = pizza
output_parameters.order_id = id_of_the_order
true
end
end
my_pizza_name = 'margerita'
my_output = OutputParameters.new
my_restaurant = RestaurantController.new
if my_restaurant.pizza_to_deliver(my_pizza_name, my_output) then
puts "Pizza to deliver : #{my_output.order_id}"
rex_dog.eat(my_output.pizza)
end
The hash or array you suggested seems even a better idea as it is more adaptative : I wouldn't have to declare a class.
I would just use the merge! method
class RestaurantController < ApplicationController
def pizza_to_deliver(pizza_name, output_hash)
# pizza to eat
pizza = Pizza.where(:name => pizza_name).first
# unknown pizza
return false if pizza.nil?
# first customer order about this pizza
id_of_the_order = Orders.where(:pizza_id => pizza.id).first
# Output values returned
output_hash.merge!({:pizza => pizza})
output_hash.merge!({:id_of_the_order => id_of_the_order})
true
end
end
my_pizza_name = 'margerita'
my_output_hash = {}
my_restaurant = RestaurantController.new
if my_restaurant.pizza_to_deliver(my_pizza_name, my_output_hash) then
puts "Pizza to deliver : #{my_output_hash[:id_of_the_order]}"
rex_dog.eat(my_output_hash[:pizza])
end
You could use multiple return values like this:
def maybe_get_something
...
return nil, "sorry" if bad_condition
...
something, nil
end
...
something, err = maybe_get_something
if !err.nil?
handle(err)
return
end
do_something_with(something)
Very similar to what people do when using Go:
f, err := os.Open("filename.ext")
if err != nil {
log.Fatal(err)
}
// do something with the open *File f

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

Ruby call constructor with random parameters count

I have some classes like
class Demo1 < Struct.new(:text, :text2)
end
class Demo2 < Struct.new(:text, :text2, :text3)
end
How can I call constructor of each class if I only have name and hash of parameters
I need to write method like this,
but this is wrong becasue after send(:new,args) Struct will contain :text which equal to args
def call_demo_object(demo_name, args={})
demo_name.to_s.constantize.send(:new,args)
end
The mian problem is calling constructor with random parameters from hash
variant one:
def call_demo_object(demo_name, args={})
z = [':new']
args.keys.each do |key|
z.push "args[:"+key.to_s+"]"
end
eval('demo_name.to_s.constantize.send(' + z.join(', ') +')' )
end
variant two:
def call_demo_object(demo_name, args={})
a = demo_name.to_s.constantize.send(:new)
args.each do |key, value|
a[key] = value if a.members.include?(key)
end
a
end
One possible variant:
def call_demo_object(demo_name, args={})
obj = demo_name.new
obj.members.each do |member|
obj[member] = args[member]
end
obj
end
It's pros:
args can be in any order
only availible structure members will be assigned
I see a couple of things wrong:
Not sure if your classes really look like that, but you'll need end at the end of them, otherwise you'll get syntax errors.
Also, constantize is not a method on strings in Ruby, it's something Rails defines. So you'll need to use
Kernel.const_get(demo_name.to_s)
to get the same functionality.
As pointed out in the comments I neglected to mention how to expand the parameters.
To do that you'll need to use what's called the "splat operator"
Kernel.const_get(demo_name.to_s).send(:new,*args) #notice the * in front of args
That will expand args out.
However, when args is a hash, say {:text=>"hello", :text2=>"hello2"}, it will expand it out to an array with 2 elements where each element is an array with they key in the first position and key in the second position.
Instead, if you pass an array in as args with the objects in order, you will get what you're looking for.
I think if you're going for what amounts to named parameters, you might have to try another route, but I don't know that for sure.
To go with optional or named parameters, you might look at how Rails does it: use a hash for the parameter, then pass in a hash with the keys. You can then keep a valid list of keys and check the passed-in hash and either reject them or raise an error.

Resources