I’m trying to implement a simple parser and quite likely going about it
the wrong way.
I would like to be able to include a bit of ruby code in a string, which
is saved to a field in a database. When the string is retrieve from the
database at a later stage, I want to parse it for code and evaluate the
code, substituting the result value into the string.
I’ve been trying to do something like this:
module Substitutable @other_word = “other word”
proc = Proc.new{}
String.class_eval do
define_method :to_s do
self.gsub(/(.?)</code>/) do |match|
code = match.scan(/(.)</code>/).flatten.first
eval(code, proc.binding)
end
end
end
end
If I then create a class and include Substitutable, I get the
following output
class Tester
include Substitutable
def say @word = “hello”
‘@other_word’.to_s # => “other word”
‘@word’.to_s # => “”
‘“hard coded word”’.to_s # => “hard coded word”
end
end
Tester.new.say
So I know that conceptually passing the a proc to the eval method does
enable me to set an instance variable out of the scope of the new to_s
method. But I cannot work out how to get the scope of the class within
which Substitutable has been "include"d. I’ve tried various things
like…
a) creating an “proc” method within the class that returns a proc object
b) creating a “@proc” instance variable within the class
I’m trying to implement a simple parser and quite likely going about it
the wrong way.
I would like to be able to include a bit of ruby code in a string, which
is saved to a field in a database. When the string is retrieve from the
database at a later stage, I want to parse it for code and evaluate the
code, substituting the result value into the string.
You are aware of the serious security problems with this, right?
[…]
But I cannot work out how to get the scope of the class within
which Substitutable has been "include"d.
Perhaps you should use the Module#included callback to register it.
You are aware of the serious security problems with this, right?
Yes, this isn’t a public-facing site, and there will be no front-end
access to the database that will generate and store the code.
Then you shouldn’t need to do this in the first place. If there’s no
frontend access to the DB, presumably that means that the app itself is
generating the code. If so, then it can keep it as a method in a live
object rather than writing it to the database and opening up a security
hole.
To give you a bit of background to this - I’ve got a “macro” that is
generating a bunch of reports. The reports contain lines like “the
number of people in this group are X and they have spent a total of Y on
your product this year”. The problem is, I can’t substitute X and Y for
their real values at the time the macro runs, because I don’t know what
filters the user will apply when viewing the reports. So I need a way
to do the calculations and substitute in the values when the user views
the report, and to recalculate these each time the user changes the
filter (e.g. show me only people older than 30).
So unless I’m missing something, I can’t keep a live object.
It’s a rails app, and the controller will be pulling together a
“@results” variable, but will not necessarily know what kinds of stats
need to be pulled from it. That’s why i’d like to be able to do things
like “the number of people in this group are @results.size
and they spent @results.collect(&:spend).sum on your
product this year”.
Doees this make sense?
[…]
But I cannot work out how to get the scope of the class within
which Substitutable has been "include"d.
Perhaps you should use the Module#included callback to register it.
To give you a bit of background to this - I’ve got a “macro” that is
generating a bunch of reports. The reports contain lines like “the
number of people in this group are X and they have spent a total of Y on
your product this year”. The problem is, I can’t substitute X and Y for
their real values at the time the macro runs, because I don’t know what
filters the user will apply when viewing the reports. So I need a way
to do the calculations and substitute in the values when the user views
the report, and to recalculate these each time the user changes the
filter (e.g. show me only people older than 30).
Check with a rails group, but I’m pretty sure you’ll find that you can,
in fact, do this natively in rails without the security hole.
You are aware of the serious security problems with this, right?
Yes, this isn’t a public-facing site, and there will be no front-end
access to the database that will generate and store the code.
Then you shouldn’t need to do this in the first place. If there’s no
frontend access to the DB, presumably that means that the app itself is
generating the code. If so, then it can keep it as a method in a live
object rather than writing it to the database and opening up a security
hole.
[…]
But I cannot work out how to get the scope of the class within
which Substitutable has been "include"d.
Perhaps you should use the Module#included callback to register it.
So unless I’m missing something, I can’t keep a live object.
It’s a rails app, and the controller will be pulling together a
“@results” variable, but will not necessarily know what kinds of stats
need to be pulled from it. That’s why i’d like to be able to do things
like “the number of people in this group are @results.size
and they spent @results.collect(&:spend).sum on your
product this year”.
I don’t really see the benefit of storing the template in the database,
but
here is what I came up with.
db = SQLite3::Database.new( ‘store_and_execute_strings.db’ )
db.execute <<-__________________________________________________
CREATE TABLE IF NOT EXISTS reports (
id INTEGER PRIMARY KEY ASC AUTOINCREMENT,
title Text,
template TEXT
);
__________________________________________________
db.execute <<-__________________________________________________
CREATE TABLE IF NOT EXISTS groups (
id INTEGER PRIMARY KEY ASC AUTOINCREMENT,
name STRING
);
__________________________________________________
db.execute <<-__________________________________________________
CREATE TABLE IF NOT EXISTS people (
id INTEGER PRIMARY KEY ASC AUTOINCREMENT,
group_id INTEGER,
money_spent DOUBLE,
age INTEGER
);
__________________________________________________
documentation at
for other (better, depending on your needs) documentation, see
class Report < ActiveRecord::Base
def generate( &init )
dup.instance_eval {
instance_eval &init if init
ERB.new( template , 0 , “%<>” ).result( binding )
}
end
end
class Group < ActiveRecord::Base
has_many :people
end
class Person < ActiveRecord::Base
belongs_to :group
end
here is an example, as I understand your criteria
text_for_report = "the number of people in this group are <%= @people.length
%> and they have spent "
"a total of $<%= @people.inject(0) { |sum,person|
sum+person.money_spent.to_f } %> "
“on your product this year”