ruby,dynamic,methods,eval , Dynamically Generating Code

## Question:

Tag: ruby,dynamic,methods,eval

I have a whole bunch of similarly-structured methods, each of which look something like this:

def my_method_1
if params[:user_id]
#code that stays the same across my_method_1, 2, 3, 4, etc.
#code that varies across my_method_1, 2, 3, 4, etc.
elsif params[:tag_id]
#code that stays the same across my_method_1, 2, 3, 4, etc.
#code that varies across my_method_1, 2, 3, 4, etc.
else
#code that stays the same across my_method_1, 2, 3, 4, etc.
#code that varies across my_method_1, 2, 3, 4, etc.
end
end


I have my_method_2, 3, 4, etc. What I'm trying to do is avoid typing out all of this for every method I have, since most of the code is repetitive. I only want to type out the bit of code that actually varies across the methods 1, 2, 3, 4, etc.

My attempt at this uses eval(), which works, and certainly dries up all my individual methods, but makes me uncomfortable. Basically, I have a helper method that takes in key-value pairs, the key being the "context", and the value being a "statement" specified as a string:

def helper_method
hash.each do |context, statement|
if params[eval(":#{context}_id")]
#code that stays the same
eval(statement)
return
end
end
eval(hash[:none])
end


Now my individual methods can be super dry, just calling the helper methods and passing in the strings of code:

def my_method_1
helper_method(
user:   '#code that varies',
tag:    '#code that varies',
none:   '#code that varies'
)
end


Again, typing out chunks of code inside strings makes me uncomfortable. Any help in going about this another way would be much appreciated!

The repetitive branches in your code tell me your class could use a little refactoring to remove the need for multiple if-statements. It sounds like your class needs to delegate to another class for specific functionality. While I don't know that your class looks like, or it's intent, the following is an example that you could apply to your code so that you don't need to generate dynamic methods at all.

## The hypothetical Order class with repetitive if-statements

Consider this Order class with multiple, similar looking if-statements:

class Order
attr_accessor :order_type

def discount_amount
if order_type == 1
.2
elsif order_type == 2
.5
else
0
end
end

def discount_end_date
if order_type == 1
DateTime.new(2014, 12, 31)
elsif order_type == 2
DateTime.new(2014, 3, 31)
else
# Always expires 100 years from now
DateTime.new(DateTime.now.year + 100, 1, 1)
end
end
end


We have three discounts: 20% off that expires end of 2014; 50% which expires end of March, 2014. Finally the default discount of 0% always expires 100 years in the future. Let's clean this up to remove the if-statments, and instead delegate to a Discount class for these calculations.

## Refactor the Order class to utilize delegate methods

First up, let's clean up the Order class, then we will implement a Discount class:

class Order
attr_accessor :order_type

def discount
@discount ||=
if order_type == 1
Discount.twenty_percent_off
elsif order_type == 2
Discount.half_off
else
Discount.default_discount
end
end

def discount_amount
discount.amount
end

def discount_end_date
discount.end_date
end
end


Nice and clean. An Order object needs a Discount object to get the discount amount and end date. The Order class is now virtually infinitely extensible since the logic for calculating discounts is off-loaded to another class entirely. The Order#order_type value determines the discount. Now, let's define our Discount class.

## Implementing the Discount class

According to our (fake) business rules, only three discounts exist:

1. 20%-off, expiring end of 2014
2. 50%-off, expiring end of March, 2014
3. 0%-off (no discount) which always expires 100 years from today, essentially meaning it never expires

We don't want people to create arbitrary discounts, so let's limit our Discount instances to only those that we define by using a private constructor, then declaring static methods for each kind of discount:

class Discount
private_class_method :new

def self.default_discount
@@default_discount ||= new(0)
end

def self.half_off
@@half_off_discount ||= new(.5, DateTime.new(2014, 3, 31))
end

def self.twenty_percent_off
@@twenty_percent_off ||= new(.2, DateTime.new(2014, 12, 31))
end

def initialize(amount, end_date = nil)
@amount = amount
@end_date = end_date
end

def amount
@amount
end

def end_date
@end_date ||= DateTime.new(DateTime.now.year + 100, 1, 1)
end
end


Trying to run Discount.new(...) should throw an error. We only have three Discount instances available:

Discount.half_off
Discount.twenty_percent_off
Discount.default_discount


Given that the Order#order_type is used to determine the discount, we emulate this with Order#discount returning the proper Discount instance based on the Order#order_type. Furthermore, we prevent people from gaming the system by defining their own discounts and the logic for all the discounts is in one class.

order = Order.new
order.order_type = 1
puts order.discount_amount # -> .2

order = Order.new
order.order_type = 2
puts order.discount_amount # -> .5


You can use sub classing to create even more specific business logic, for instance a "random" discount:

class Discount
protected_class_method :new

...

def self.random
@random_discount ||= RandomDiscount.new(nil)
end

class RandomDiscount < Discount
def amount
rand / 2
end
end
end


Now Discount.random.amount outputs a different discount every time. The possibilities become endless.

## How this applies to your question

The existence of repetitive if-statements means your class is doing too much. It should be delegating to another class that specializes in one of those branches of code. You shouldn't have to manipulate methods in Ruby at runtime to achieve this. It's too much "magic" and is confusing to new developers. With the approach I outlined above, you get a strongly typed definition of what these discounts are, and you keep each class focused on one task (and no, "strongly typed" is not a four letter word in Ruby when properly used). You get well defined relationships between objects, code that is easier to test and you get strong enforcement of business rules. All with no "magic."

# Related:

## Trying to dynamically create divs that can also be closed

javascript,html,dynamic
I'm trying to create a simple note taking application. Right now I am trying figure out how to dynamically create and delete DIV elements. I can create DIVs, but each time I add a new div it adds a "Close" "X" to each previous div. So my question is how...

## Loop until i get correct user

ruby,redis
I have users stored in Redis and want to be able to call only certain subsets from a set, if i don't get the correct user back i want to put it back in the set and then try again until i get one of the desired users @redis =...

## Get X days out of an Array

ruby,ruby-on-rails-4
I have an array filled with Datetime objects: [Mon, 22 Jun 2015, Tue, 23 Jun 2015, Wed, 24 Jun 2015, Thu, 25 Jun 2015, Fri, 26 Jun 2015, Sat, 27 Jun 2015, Sun, 28 Jun 2015] I know how to select what I want from the array ex: week.select{|x|x.monday? ||...

## Call method to generate arguments in ruby works in 1.8.7 but not 1.9.3

ruby-on-rails,ruby,ruby-1.9.3
This is something that I had working in ruby 1.8.7, but no longer works in 1.9.3, and I am not sure what changes make this fail. Previously, I had something like this myFunction(submitArgs()) where submitArgs was a helper method that could be called with some options def submitArgs(args={}) #Some logic/manipulations...

## What is Rack::Utils.multipart_part_limit within Rails and what function does it perform?

ruby-on-rails,ruby,rack,multipart
Rack::Utils.multipart_part_limit is set to 128 by default. What purpose does the value have and what effect does it have within the Rails system?...

## Rails Association Guidance [on hold]

ruby-on-rails,ruby,ruby-on-rails-4,ruby-on-rails-3.2
I am new to rails 4. I have gone through lots of tutorials and trying to solve below scenario. But still no success. Can anybody point me in the right direction. How to handle associations for below scenario. Scenario: 1. Patient can have many surgeries. 2. Surgery has two types...

ruby-on-rails,ruby,rest,activerecord,one-to-many
I'm creating a rails application that is a backend for a mobile application. The backend is implemented with a RESTful web API. Currently I am trying to add gamification to the platform through the use of badges that can be earned by the user. Right now the badges are tied...

## How to handle backslash “\” escape characters in q string and heredocument

ruby
Ruby Newbie here. I do not understand why Ruby looks inside %q and escapes the \. I am using Ruby to generate Latex code. I need to generate \\\hline which is used in Latex for table making. I found \\\hline as input generated \hline even though the string was inside...

## Heroku RAM not increasing with upgraded dynos

ruby-on-rails,ruby,ruby-on-rails-3,memory,heroku
I have a massive function i have been calling manually through the heroku rails console. I have been receiving the error rapid fire in my logs: 2015-06-22T14:56:42.940517+00:00 heroku[run.9877]: Process running mem=575M(112.4%) 2015-06-22T14:56:42.940517+00:00 heroku[run.9877]: Error R14 (Memory quota exceeded) A 1X dyno is suppose to have 512 MB of RAM. I...

## Ruby- get a xml node value

ruby,xml
can someone help me in extracting the node value for the element "Name". Type 1: I am able to extract the "name" value for the below xml by using the below code <Element> <Details> <ID>20367</ID> <Name>Ram</Name> <Name>Sam</Name> </Details> </Element> doc = Nokogiri::XML(response.body) values = doc.xpath('//Name').map{ |node| node.text}.join ',' puts values...

## Appending an element to a page in VoltRb

html,ruby,opalrb,voltrb

## Rails less url path change

ruby-on-rails,ruby,url,path,less
Developing a Rails application with the less-rails gem I found something unusual : // app/assets/common/css/desktop/typo.less @font-face{ font-family:'SomeFont'; src:url("fonts/db92e416-da16-4ae2-a4c9-378dc24b7952.eot?#iefix"); // ... } The requested font is app/assets/common/css/fonts/db92e416-da16-4ae2-a4c9-378dc24b7952.eot This font is compiled with less and the results is : @font-face { font-family: 'SomeFont'; src: url("desktop/fonts/db92e416-da16-4ae2-a4c9-378dc24b7952.eot?#iefix"); //... } Do you know why is...

## regex to pull in number with decimal or comma

ruby,regex
This is my line of code: col_value = line_item[column].scan(/\d+./).join().to_i When I enter 30,000 into the textfield, col_value is 30. I want it to bring in any number: 30,000 30.5 30.55 30000 Any of these are valid... Is there a problem with the scan and or join which would cause it...

## Ruby access words in string

ruby
I don't understand the best method to access a certain word by it's number in a string. I tried using [] to access a word but instead it returns letter. puts s # => I went for a walk puts s[3] # => w ...

## Using Ruby Pathname to access relative directory

ruby,path,pathname
Given I have a relative path pointing to a directory how can I use it with Ruby's Pathname or File library to get the directory itself? p = Pathname.new('dir/') p.dirname => . p.directory? => false I have tried './dir/', 'dir/', 'dir'. What I want is p.dirname to return 'dir'. I...

## Allowing some enabled and disabled option on collection_select

ruby-on-rails,ruby
I am trying to populate a dropdown box on a view that has all the states. This works just fine: <%= f.collection_select :state_id, @states, :id, :name %> Now, I need to make the following: Some states are going to be disabled for choosing, but they still have to appear on...

## Rails basic auth not working properly

ruby-on-rails,ruby,authentication
I am building a small API that uses basic authentication. What I have done, is that a user can generate a username and password, that could be used to authenticate to the API. However I have discovered that it is not working 100% as intended. It appears that a request...

## Rails - link_to path based on object's name + refactoring multiple custom actions

ruby-on-rails,ruby,refactoring
I'm looking to simplify the link_to path based on thr object's name and also am looking into refactoring multiple custom actions. I've managed to get this working below. <% ServiceMenu.all.each do |menu| %> <tr class=" <%= cycle('odd', 'even') %>"> <td><%= link_to menu.name, ("tech/""#{menu.name.parameterize}") %></td> </tr> <% end %> I feel...