String to Class?


#1

Hi,

I’m getting a class name as string via params[:class_name]. How can I
access the class?

E.g.

params[:class_name] => “Post”

and then something like

params[:class_name].to_class.find(:first)

Why I want to to this? I want to create a generic commenting component
with model controller and view (partials) that I can use to comment any
other instance.

For now each class that can be commented (here: Article besides many
others) needs rather the same action code:

class ArticleController < ApplicationController

def comment
@commentable = Article.find(params[:id]) # commentable
@comment = Comment.new
@comment.user = session[‘user’]
@commentable.comments << @comment
if request.post?
@comment = Comment.new(params[:comment])
if @comment.save
render :partial => “comment/comment_list”
else
render :inline => “<%= user_message_helper ‘comment’ %>”,
:status => 500
end
end
end
end

I would like to have this action only once in the CommentController,
somehow like this:

class CommentController < ApplicationController

def comment
class_name = params[:class_name]
@commentable = class_name.to_class.find(params[:id]) # commentable
@comment = Comment.new
@comment.user = session[‘user’]
@commentable.comments << @comment
if request.post?
@comment = Comment.new(params[:comment])
if @comment.save
render :partial => “comment/comment_list”
else
render :inline => “<%= user_message_helper ‘comment’ %>”,
:status => 500
end
end
end
end

I’m sure there is an elegant way to handle this…


#2

starburger wrote:

Hi,

I’m getting a class name as string via params[:class_name]. How can I
access the class?

I think what you want is eval

eval(“Ball”).find(:all)

for example will find all balls with a model class Ball. Not that a
class name is case sensitive!!!


#3

Rob N. wrote:

Not that a class name is case sensitive!!!

Should be:

Note that a class name is case sensitive!!!


#4

On 2/1/07, starburger removed_email_address@domain.invalid wrote:

and then something like

params[:class_name].to_class.find(:first)

Try String#constantize:

params[:class_name].constantize.find(:first)

Why I want to to this? I want to create a generic commenting component


#5

Thanks a lot for your help - you have solved my problem!

Starburger


#6

Michael G. wrote:

On 2/1/07, starburger removed_email_address@domain.invalid wrote:

and then something like

params[:class_name].to_class.find(:first)

Try String#constantize:

params[:class_name].constantize.find(:first)

Why I want to to this? I want to create a generic commenting component

how bout eval(params[:class_name].capitalize + “.find(:first)”) ?


#7

Stay away from eval.

Use String#constantize or Object#get_const

Jason


#8

On 1-Feb-07, at 2:13 PM, Jason R. wrote:

Starburger

Jason, Why stay away from eval?

I’m currently using eval to dynamically build a restful url_helper
reference. Is this also a bad case of using eval fro the same reason/?

ala: eval(“change_state_#{the_model.class.name.downcase}_url(#
{the_model.id})”)

(sorry to hijack - hopefully this adds to the discussion!)

Cheers,
Jodi
General Partner
The nNovation Group inc.
www.nnovation.ca/blog



#9

On Feb 1, 2007, at 11:28 AM, Jodi S. wrote:

{the_model.id})")

Jodi-

The reason to stay away from eval is because you want to avoid
evaling anything user generated. The OP is trying to make a class
from an item in the params object. DANGER!! look here:

how bout eval(params[:class_name].capitalize + “.find(:first)”) ?

Now anyone can try to enter all kinds of things in the params
[:class_name] form field. And it will get evaled without any checking
on the server. Bad!

If you can avoid eval please do so at all costs. class_Eval or
instance_eval with blocks instead of strings are better. But in your
example:

eval(“change_state_#{the_model.class.name.downcase}_url(#
{the_model.id})”)

should be written like this:

send(“change_state_#{the_model.class.name.downcase}_url”, “#
{the_model.id}”)

Cheers-

– Ezra Z.
– Lead Rails Evangelist
– removed_email_address@domain.invalid
– Engine Y., Serious Rails Hosting
– (866) 518-YARD (9273)


#10

On Thursday 01 February 2007 20:28, Jodi S. wrote:

Why stay away from eval?

class SomethingsController < ApplicationController
def tricky

eval(params[:my_param])

end
end

Then consider what a request like this might do to your server

http://example.com/trickies/tricky?my_param=system("rm%20-rf%20%2F")

Michael


Michael S.
mailto:removed_email_address@domain.invalid
http://www.schuerig.de/michael/


#11

eval(“change_state_#{the_model.class.name.downcase}_url(#
{the_model.id})”)

should be written like this:

send(“change_state_#{the_model.class.name.downcase}_url”, “#
{the_model.id}”)

Hey Ezra -

the send didn’t succeed as :
undefined method `change_state_project_url(:id => 85, :event =>
‘change_project_info’)’ for #<#Class:0x345af20:0x345aef8>

note: this code is running inside application_helper - not sure what
self is inside a helper, but it doesn’t know about rest url helpers.

Do you know what object ‘owns’ these methods? I gather they are Class
methods.

thanx.
Jodi


#12

On 1-Feb-07, at 3:48 PM, Ezra Z. wrote:

Jason
I’m currently using eval to dynamically build a restful url_helper
General Partner
evaling anything user generated. The OP is trying to make a class
instance_eval with blocks instead of strings are better. But in your
Cheers-

– Ezra Z.
– Lead Rails Evangelist
– removed_email_address@domain.invalid
– Engine Y., Serious Rails Hosting
– (866) 518-YARD (9273)

Thanx Ezra and Michael.

I understood that Send limitted the scope(object calls) of damage
that can be cause by eval - and a object.send("#{params[:command]) is
dangerous enough. Whereby Eval’s scope isn’t limited.

In my particular case the_model is instantiate by the controller
action - so eval would likely be as ‘safe’ as send (?).

My questions was questioning what additional protection that send
provides - is there any protection provided above and beyond the
localization of the class executing the arbritary code?

I think my learning here is that regardless of means(send, eval,
etc), that additionally checking should be done on the methods being
called - limiting the scope of the methods that can be called (I’ve
recently used acts_as_state_machine to limit sends based upon model
state and other guards - this provides funnel that sends must qualify
before running - and still give me the benefit of a measure of dynamism.

ala:
instance.send("#{params[:command]}") if
Class.legitimate_commands.include?(params[:command])

thanks for teaching this ruby-nuby.
Jodi


#13

As everybody has said, avoid eval - use const_get to retrieve a
constant. If you want a class from a string, here’s a simple
implementation:

class String
def to_class
Object.const_get(self)
end
end