How to initialise an instance variable dynamically in ruby? - ruby

Using interpolated strings is it possible to call multiple #{} within each other?
For example I want to create a variable and add a number onto the end. I also want to attach a result from a column in site, but also increment the number as well. What the best method of doing the below code?
#site.each do |site|
1.upto(4) do |i|
eval("#var#{i} = #{site.site_#{i}_location}", b)
end
end
So #var1 = site.site_1_location, #var2 = site.site_2_location, etc.

Mocking #sites data:
#sites = [OpenStruct.new(
site_1_location: 'Japan',
site_2_location: 'India',
site_3_location: 'Chile',
site_4_location: 'Singapore'
)]
You can use instance_variable_set to set the instance variable
#sites.each do |site|
1.upto(4) do |i|
instance_variable_set("#var#{i}", site.send("site_#{i}_location"))
end
end
Now you can access the variables:
#var1 # returns "Japan"
#var2 # returns "India"
#var3 # returns "Chile"
#var4 # returns "Singapore"

Using #send might help
code = "#var#{i} = site.send(:site_#{i}_location)"
eval(code)

Yes of cause, nesting of #{} is possible.
Stupid example, but it shows the possibilties of nesting:
x = 1
y = 2
"abc_#{"#{x}_#{y if y > 1}"}"
# => 'abc_1_2'
Never the less, the solution for your code, suggested by Imram Ahmad (https://stackoverflow.com/a/66002619/14485176) is the better aproach to solve your problem!

Related

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 method to begin method from begining?

Example(Rails):
def blabla
#ads = ["1", "2"] if #ads.nil?
#reklame = Reklamer.find(#koder.sample)
#ads[0] = #reklame.id
if #ads[0] == #ads[1]
begin from start method from start
end
end
Is there a method to restart the method/action so it begins from the top?
Try the keyword retry, this keyword will restart the iteration from the beginning so consider to rebuild your method to use iteration, for example like this:
def blabla
#ads = ["1", "2"] if #ads.nil?
#ads.each_slice do |prev_,next_|
#reklame = Reklamer.find(#koder.sample)
prev_ = #reklame.id
retry if prev_ == next_
end
end
Also the keyword redo will repeat current iteration.
You could use ruby-goto but I would definitely recommend against it.
For more information on why not to use goto statements check out this question. It is C# based but I think it covers the point nicely.
Not sure what you want to accomplish, but it sounds like you need a loop of some sort. Would something like this do what you need?
def blabla
#ads ||= ["1", "1"]
# careful with comparing a string ("2") to a numeric id
while #ads[0] == #ads[1] do
#reklame = Reklamer.find(#koder.sample)
#ads[0] = #reklame.id
end
end
Basically you keep reseting #ads[0] until it is different from #ads[1].

Set Attribute Dynamically of Ruby Object

How can I set an object attribute dynamically in Ruby e.g.
def set_property(obj, prop_name, prop_value)
#need to do something like > obj.prop_name = prop_value
#we can use eval but I'll prefer a faster/cleaner alternative:
eval "obj.#{prop_name} = #{prop_value}"
end
Use send:
def set_property(obj, prop_name, prop_value)
obj.send("#{prop_name}=",prop_value)
end
Object#instance_variable_set() is what you are looking for, and is the cleaner version of what you wanted.
Example:
your_object = Object.new
your_object.instance_variable_set(:#attribute, 'value')
your_object
# => <Object:0x007fabda110408 #attribute="value">
Ruby documentation about Object#instance_variable_set
If circumstances allow for an instance method, the following is not overly offensive:
class P00t
attr_reader :w00t
def set_property(name, value)
prop_name = "##{name}".to_sym # you need the property name, prefixed with a '#', as a symbol
self.instance_variable_set(prop_name, value)
end
end
Usage:
p = P00t.new
p.set_property('w00t', 'jeremy')
This answer (https://stackoverflow.com/a/7466385/6094965) worked for me:
Object.send(attribute + '=', value)
attribute has to be a String. So if you are iterating trough an array of Symbols (like me), you can use to_s.
Object.send(attribute.to_s + '=', value)

Can I reject objects which do not meet my criteria as they are entered into an array?

I know there are a number of ways to create new elements in an existing ruby array.
e.g.
myArray = []
myArray + other_array
myArray << obj
myArray[index] = obj
I'm also pretty sure I could use .collect, .map, .concat, .fill, .replace, .insert, .join, .pack and .push as well to add to or otherwise modify the contents of myArray.
However, I want to ensure that myArray only ever includes valid HTTP/HTTPS URLs.
Can anyone explain how I can enforce that kind of behaviour?
I would create a module that allows you to specify an acceptance block for an array, and then override all the methods you mention (and more, like concat) to pre-filter the argument before calling super. For example:
module LimitedAcceptance
def only_allow(&block)
#only_allow = block
end
def <<( other )
super if #only_allow[ other ]
end
def +( other_array )
super( other_array.select(&#only_allow) )
end
end
require 'uri'
my_array = []
my_array.extend LimitedAcceptance
my_array.only_allow do |item|
uri = item.is_a?(String) && URI.parse(item) rescue nil
uri.class <= URI::HTTP
end
my_array << "http://phrogz.net/"
my_array << "ftp://no.way"
my_array += %w[ ssh://bar http://ruby-lang.org http:// ]
puts my_array
#=> http://phrogz.net/
#=> http://ruby-lang.org
Create a class to encapsulate behavior you want. Then you can create your << method doing the verifications you want.
Put all logic that handle this data in methods in this domain class. Probably you will discover code floating around the use of this data to move to the new class.
My 2 cents.
Use this to insert. (untested).
def insert_to_array(first_array, second_array)
second_array.each do |i| {
if URI.parse(i).class == URI::HTTP
first_array.insert(i)
end
}
first_array
end

'pass parameter by reference' in Ruby?

In Ruby, is it possible to pass by reference a parameter with value-type semantics (e.g. a Fixnum)?
I'm looking for something similar to C#'s 'ref' keyword.
Example:
def func(x)
x += 1
end
a = 5
func(a) #this should be something like func(ref a)
puts a #should read '6'
Btw. I know I could just use:
a = func(a)
You can accomplish this by explicitly passing in the current binding:
def func(x, bdg)
eval "#{x} += 1", bdg
end
a = 5
func(:a, binding)
puts a # => 6
Ruby doesn't support "pass by reference" at all. Everything is an object and the references to those objects are always passed by value. Actually, in your example you are passing a copy of the reference to the Fixnum Object by value.
The problem with the your code is, that x += 1 doesn't modify the passed Fixnum Object but instead creates a completely new and independent object.
I think, Java programmers would call Fixnum objects immutable.
In Ruby you can't pass parameters by reference. For your example, you would have to return the new value and assign it to the variable a or create a new class that contains the value and pass an instance of this class around. Example:
class Container
attr_accessor :value
def initialize value
#value = value
end
end
def func(x)
x.value += 1
end
a = Container.new(5)
func(a)
puts a.value
You can try following trick:
def func(x)
x[0] += 1
end
a = [5]
func(a) #this should be something like func(ref a)
puts a[0] #should read '6'
http://ruby-doc.org/core-2.1.5/Fixnum.html
Fixnum objects have immediate value. This means that when they are assigned or
passed as parameters, the actual object is passed, rather than a reference to
that object.
Also Ruby is pass by value.
However, it seems that composite objects, like hashes, are passed by reference:
fp = {}
def changeit(par)
par[:abc] = 'cde'
end
changeit(fp)
p fp
gives
{:abc=>"cde"}

Resources