I was working on a feature for my Rails app, which allows merchant to set fulfillment times on each day like this :
The main feature is that once an order is placed, the customer can’t cancel the placed order after the fulfillment time starts on that day (or on tomorrow).
The fulfillment start times are saved in different columns like this :
create_table "fulfillments", force: :cascade do |t|
t.time "monday_start_time", default: "2000-01-01 00:00:00", null: false
t.time "tuesday_start_time", default: "2000-01-01 00:00:00", null: false
t.time "wednesday_start_time", default: "2000-01-01 00:00:00", null: false
t.time "thursday_start_time", default: "2000-01-01 00:00:00", null: false
t.time "friday_start_time", default: "2000-01-01 00:00:00", null: false
t.time "saturday_start_time", default: "2000-01-01 00:00:00", null: false
t.time "sunday_start_time", default: "2000-01-01 00:00:00", null: false
end
To check if the fulfillment time has started, my app will get the current day of week using DateTime.current.strftime('%A').downcase
. If today is wednesday, then it will return wednesday
.
The question now is that how do I access value of different column based on different day of week? Say I want to get the value of thursday_start_time
if today is thursday ; or the value of sunday_start_time
is today is sunday .
Fortunately, Ruby’s metaprogramming feature allows us to call methods dynamically by just passing the method name into public_send(method_name) or send(method_name) .
Say we have a class like this :
class Duck
def make_noise
puts "quack"
end
private
def jump
puts "can't jump"
end
end
We can call the make_noise method by calling Duck.new.public_send("make_noise")
, this is equivalent to calling Duck.new.make_noise .
For the jump method, we would need to use send instead, as it is a private method, Duck.new.send("jump")
For the fulfillment start time, we can then pass in the day dynamically like this :
# this will return 'monday' if today is monday, or 'tuesday' if today is tuesday etc..
day_of_week = DateTime.current.strftime('%A').downcase
# if today is monday, it will call 'monday_start_time', etc..
start_time = fulfillment.public_send("#{day_of_week}_start_time")
Sweet!
Alternatively, you can also pass in symbol instead of string : public_send(:make_noise) .
If you would like to send parameters to the method, you can pass them after the method name :
class Animal
def make_noise(noise, times)
1..times.each do
puts noise
end
end
end
Animal.new.public_send('make_noise', 'quack', 3)
# quack
# quack
# quack
Ruby Doc’s public_send documentation
Ruby Doc’s send documentation