DRY code by creating ruby objects/variables dynamically - ruby

I am fairly new to Ruby. I have few lines of code I want to DRY them.
foo_attributes = params[:foo]
params.delete(:foo)
bar_attributes = params[:bar]
params.delete(:bar)
I am trying to do something like this
["foo", "bar"].each do |par|
par_attribute = params[:par]
params.delete(:par)
end
Later in my method, I need to call other methods passing the objects foo_attribute, bar_attribute.
Like:
call_foo_method(foo_attribute)
How can I do this in ruby?

Why don't you just keep the params var? Then you don't have to assign any other variable at all?
You can also DRY the first (and second example) by combinig two lines:
foo_attributes = params.delete(:foo)
bar_attributes = params.delete(:bar)

Related

How to go through a loop and create a new object with each iteration Ruby?

I saw several variations of this question but did not really find a solid answer.
So I have an array of URLS. I want to loop through that array and for each individual URL, I would create an instance of class WebPages.
So if array URLS has 5 urls in it, then I would create 5 objects of WebPages. I tried to use eval() to do this but quickly learned that the instances made by eval have a very local scope and I cannot use those WebPage objects after.
string_to_eval = #urls.map{|x| "webpage#{urls.index(x)} = WebPage.new('# {x}')"}.join(';')
puts string_to_eval
eval(string_to_eval)
String_to_eval prints out:
webpage0 = WebPage.new('http://www.google.com');
webpage1 = WebPage.new('http://www.yahoo.com');
webpage2 = WebPage.new('http://www.amazon.com');
webpage3 = WebPage.new('http://www.ebay.com')
How else can I make an object with each iteration of the loop in Ruby? Is there a way around this?
Why not just this?
webpages = #urls.map { |url| WebPage.new(url) }
It is generally a bad idea to have webpage0, webpage1... when you can have webpages[0], webpages[1]... (Also, the array way does not require the Evil of eval.)
In this situation I would forgo unique variable names and instead simply leave the resulting objects in an array. In that case the code would look like this:
>> #urls.map{|url| WebPage.new(url)}
=> [WebPage('http://www.google.com'), WebPage('http://www.yahoo.com'), WebPage('http://www.amazon.com'), WebPage('http://www.ebay.com') ]

ruby - calling a method on a dynamically named object

I have an array of strings, that represent existing object names.
JoesDev = Dev.new
MarksDev = Dev.new
SamsDev = Dev.new
devices=['JoesDev', 'MarksDev', 'SamsDev' ]
i'd like to iterate over the devices array, while calling a method on the objects that each item in the array is named after.
i.e;
JoesDev.method_name
MarksDev.method_name
SamsDev.method_name
how can i do this? thx.
devices.each{|name| self.class.const_get(name).method_name}
You can use the const_get method from Module to have Ruby return the constant with the given name. In your case, it will return the Dev instance for whatever device name you give it.
Using .each to iterate the items, your code could look like
devices.each do |device_name|
device = self.class.const_get(device_name)
device.method_name
end
# Which can be shortened to
devices.each{ |dev| self.class.const_get(dev).method_name }
However, there are better ways to implement this type of thing. The most common way is using a Hash. In your example, the list of devices could look something like
devices = {
joe: Dev.new,
mark: Dev.new,
sam: Dev.new
}
Then, iterating over the devices is as simple as
devices.each do |dev|
dev.method_name
end
# Or
devices.each{ |dev| dev.method_name }
Extra: If you want to get a little fancy, you can use the block version of Hash::new to make adding new devices extremely simple.
# Create the hash
devices = Hash.new{ |hash, key| hash[key] = Dev.new }
# Add the devices
devices['joe']
devices['mark']
devices['sam']
This kind of hash works exactly the same as the one shown above, but will create a new entry if the given key cannot be found in the hash. A potential problem with this design, then, is that you can accidentally add new devices if you make a typo. For example
devices['jon'] # This would make a new Dev instance, which may be undesirable.
Well one way is surely to use eval, a method that allows you to execute arbitrary strings as if they were code.
So, in your example:
var_names.each{ |var_name| eval("#{var_name}.some_method") }
Needless to say, it is very dangerous to let unfiltered strings to be used as code, very bad thingsā„¢ may happen!

Ruby: How to generate lines of code inside a program?

I am developing a parser in Ruby using the parslet library.
The language I am parsing has a lot of keywords that can be merged into a single parsing rule like this:
rule(:keyword) {
str('keyword1') |
str('keyword2') |
str('keyword2') ...
}
Is there a good way to generate this set of lines of code dynamically, by reading a text file with all the keywords?
This would help me keep my parser clean and small, making it easier to add new keywords without modifying the code.
The pseudo-code of what I want to embed inside the rule(:keyword) would be somethings like this:
File.read("keywords.txt").each { |k| write_line " str(\'#{k}\') "}
So far, the workaround I have found is to have a separate ruby program loading the parser code as:
keywords = ["keyword1", "keyword2","keyword3"]
subs = {:keyword_list => keywords .inject("") { |a,k| a << "str('#{k}') | \n"} }
eval( File.read("parser.rb") % subs)
where the parser code has the following lines:
rule(:keywords){
%{keyword_list}
}
Is there a more elegant way to achieve this?
You can try something like this:
rule(:keyword) {
File.readlines("keywords.txt").map { |k| str(k.chomp) }.inject(&:|)
}
In this case, you don't really need to "generate lines of code". As #Uri tried to explain in his answer, there's nothing special about the contents of that rule method; it's just plain Ruby code. Because of this, anything you can do in Ruby you can do inside that rule method as well, including read files, dynamically call methods, and call methods on objects.
Let me break down your existing code, so I can better explain how a dynamic solution to the same problem would work:
rule(:keyword) {
# Stuff here
}
This code right here calls a rule method and passes it :keyword and a block of code. At some point, parslet will call that block and check its return value. Parslet might choose to call the block using instance_exec, which can change the context the block is being executed in to make methods not available outside the block (like str, perhaps) available inside it.
str('keyword1')
Here, inside the context of the rule block, you are calling a method named str with the string "keyword1", and getting the result. Nothing special here, this is just a normal method call.
str('keyword1') | str('keyword2')
Here, the | operator is actually just a method being called on whatever str('keyword1') is returning. This code is equivalent to str('keyword1').send(:'|', str('keyword2')).
str('keyword1') |
str('keyword2') |
str('keyword2')
Same as before, except this time we're calling | on whatever str('keyword1').send(:'|', str('keyword2')) returned. The result of this method call is returned to the rule method when it calls the block.
So now that you know how all this works, you can perform exactly the same operations (calling str with each keyword, and using the | method to "add up" the results) dynamically, based on the contents of a file perhaps:
rule(:keyword) {
File.readlines("keywords.txt").map(&:chomp).map { |k| str(k) }.inject(:|)
}
Breakdown:
rule(:keyword) { # Call the rule method with the `:keyword` argument, and pass
# it this block of code.
File.readlines("keywords.txt"). # Get an array of strings containing all the
# keywords
map(&:chomp). # Remove surrounding whitespace from each keyword in the array,
# by calling `chomp` on them. (The strings returned by
# `File.readlines` include the newline character at the end of
# each string.)
map { |k| str(k) }. # Convert each keyword in the array into whatever is
# returned by calling `str` with that keyword.
inject(:|) # Reduce the returned objects to a single one using the `|`
# method on each object. (Equivalent to obj1 | obj2 | obj3...)
}
And that's it! See? No need to generate any lines of code, just do what the real code is doing, but do it dynamically!

How do I combine map with to_s?

I am using Mongoid and retrieving a bunch of BSON::ObjectId instances. Ideally, I'd like to convert them to strings upon retrieval. What's the correct syntax? It can be done in two lines like this:
foo = Bar.where(:some_id => N).map(&:another_id)
ids_as_strings = foo.map(&:to_s)
What's the proper Ruby way to chain to_s after the map invocation above?
This works fine, but don't do it!
ids_as_string = Bar.where(:some_id => N).map(&:another_id).map(&:to_s)
It looks cool for sure, but think about it, you are doing two maps. A map is for looping over an array, or something else, and will operate in each position, retrieving a new array, or something else, with the results.
So why do two loops if you want to do two operations?
ids_as_string = Bar.where(:some_id => N).map {|v| v.another_id.to_s}
This should be the way to go in this situation, and actually looks nicer.
You can just chain it directly:
ids_as_string = Bar.where(:some_id => N).map(&:another_id).map(&:to_s)
I tried this out with a model and I got what you expected, something like:
["1", "2", ...]

OR operators and Ruby where clause

Probably really easy but im having trouble finding documentation online about this
I have two activerecord queries in Ruby that i want to join together via an OR operator
#pro = Project.where(:manager_user_id => current_user.id )
#proa = Project.where(:account_manager => current_user.id)
im new to ruby but tried this myself using ||
#pro = Project.where(:manager_user_id => current_user.id || :account_manager => current_user.id)
this didnt work, So 1. id like to know how to actually do this in Ruby and 2. if that person can also give me a heads up on the boolean syntax in a ruby statement like this altogether.
e.g. AND,OR,XOR...
You can't use the Hash syntax in this case.
Project.where("manager_user_id = ? OR account_manager = ?", current_user.id, current_user.id)
You should take a look at the API documentation and follow conventions, too. In this case for the code that you might send to the where method.
This should work:
#projects = Project.where("manager_user_id = '#{current_user.id}' or account_manager_id = '#{current_user.id}'")
This should be safe since I'm assuming current_user's id value comes from your own app and not from an external source such as form submissions. If you are using form submitted data that you intent to use in your queries you should use placeholders so that Rails creates properly escaped SQL.
# with placeholders
#projects = Project.where(["manager_user_id = ? or account_manager_id = ?", some_value_from_form1, some_value_from_form_2])
When you pass multiple parameters to the where method (the example with placeholders), the first parameter will be treated by Rails as a template for the SQL. The remaining elements in the array will be replaced at runtime by the number of placeholders (?) you use in the first element, which is the template.
Metawhere can do OR operations, plus a lot of other nifty things.

Resources