Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"Lazy" require in ruby

I have a ruby script (pretty big one) that update a couple of dbs using SQL. Most of the sql queries (and even table names), are constant, and look like:

MONTH = 8
SOME_TABLE = "the_table_name_" + MONTH + SOME_SUFFIX
SOME_QUERY = "CREATE TEMPORARY TABLE #{SOME_TABLE} SELECT yada, yada2 FROM #{SOME_OTHER_TABLE} WHERE yada=1 AND MONTH=#{MONTH};"

There are hundreds of queries and tables like that, and they all use each other. I made a recent change, to get the "month" as a parameter from the command line instead a constant in the command line, so run this script in a cron management system.

My first problem was that i couldn't set MONTH as a const. At first place, i worked around it with something like:

eval("MONTH=%d" % options[:month])

later on i changed MONTH to be @month (global) and that fixed that.

the second and main problem was than my main.rb has that line

require("queries.rb")

at the beginning, therefore ruby tries to expand the var MONTH (or @month) when requiring, before my main even ran! I worked around that problem using that code:

def run_lazy_loaded_work
    require './queries.rb'
    run_work
end

and removing the require in the start of the file of course. that solution works perfectly, since the expansion in queries.rb happens when requiring, after @month has already been set.

However, It doesn't smell like the correct solution, and feels like a nasty patch, which might not be bullet proof when other developer will take ownership of that code.

I tried to initialize all the queries in a config class, which initializes all the queries in the instance's initialization, and has accessors to that, but since that's hundreds of lines, and also all the lines that are using them, it becomes even nastier.

I would love to hear if anyone can think of a more elegance solution.

Thanks! :)

* UPDATE ** I've edited it a bit more, and currently it looks like that:

main.rb:

require './work.rb'
def main
    run_work(options)
end

work.rb:

def run_work(options)
    @month=options[:month]
    load 'queries.rb'
    run_query(SOME_QUERY, SOME_TABLE, SOME_INDEX)
end

queries.rb:

SOME_TABLE = "table_name_#{@month}"
SOME_QUERY = "SELECT * FROM #{SOME_TABLE} WHERE month=#{month}"
like image 523
Dekel Avatar asked May 04 '26 16:05

Dekel


2 Answers

One possible solution is to change queries.rb so that the expensive properties are actually methods instead of "fake" constants. Then requiring queries.rb would not be expensive, and you'd change your code to something like:

require queries.rb

#...

def run_lazy_loaded_work
    run_work(Queries.some_query())
end

I know that changing a file with "hundreds" of properties might seem a bit scary, but something which looks like a constant but actually isn't seems even scarier to me.

like image 55
Craig Stuntz Avatar answered May 06 '26 06:05

Craig Stuntz


I'll try to give my 2 cents.

Instead of using constants and instance variables, you could use methods.

The first time the method is called, then it defines the value of an internal helper instance variable. So, the month is expanded only when needed.

Ex.:

def month
  # set @month at first time; or return it in further calls
  @month ||= $options[:month]     # here I'm using a global $options
end

(...)

# will get the month (will define it at first call)
SOME_TABLE = "the_table_name_" + month + SOME_SUFFIX
like image 29
Sony Santos Avatar answered May 06 '26 05:05

Sony Santos