Forum: Ruby Rails Web Service: Convert Parameter to Class Method Call?

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
D4a6cf1447a473d5fb2bfbe0f7247463?d=identicon&s=25 craigjordanlaw (Guest)
on 2005-11-28 02:29
(Received via mailing list)
I'm trying to write a method for a web service that will find objects
from a user specified table, i.e. find all Widgets added in last 7
days, or find all Whatsits whose name starts with Q.

I could right a  separate method for each table calling i.e.
Widget.find() or Whatsit.find(), but what I'd like to do is have a
single interface that lets me call something like
user_specified_table.find() where user_specified_table is a string
equal to either Widget or Whatsit.

How can I do this? Is there an entirely different way of doing this
that I should be looking at?
Bc6d88907ce09158581fbb9b469a35a3?d=identicon&s=25 james_b (Guest)
on 2005-11-28 03:09
(Received via mailing list)
Craig wrote:
> How can I do this? Is there an entirely different way of doing this
> that I should be looking at?
>

method_missing is your best friend.
Oh, and 'send', too.

Fast attempt at an example:
#!/usr/local/bin/ruby
class Finder

   def method_missing( sym,  *args )
    if sym.to_s =~ /(\w+)_find$/
      klass = $1.capitalize
      "#{klass}.find"
    elsif sym.to_s =~ /(\w+)_find_by_([a-z_]+)/
      klass = $1.capitalize
      params = $2
      "#{klass}.find_by_#{params}"
    else
      super
    end
   end
end

if __FILE__ == $0
   require 'test/unit'
   class TC_FOO < Test::Unit::TestCase

     def setup
      @f = Finder.new
    end

    def test_00
      res = @f.whatsit_find
      assert_equal( "Whatsit.find" , res )
    end

    def test_01
      res = @f.widget_find_by_name
      assert_equal( "Widget.find_by_name" , res )
    end


    def test_02
      assert_raise( NoMethodError ){
      @f.widget_fetch_me_coffee
      }
    end
   end
end




James

--

http://www.ruby-doc.org       - Ruby Help & Documentation
http://www.artima.com/rubycs/ - Ruby Code & Style: Writers wanted
http://www.rubystuff.com      - The Ruby Store for Ruby Stuff
http://www.jamesbritt.com     - Playing with Better Toys
http://www.30secondrule.com   - Building Better Tools
D4a6cf1447a473d5fb2bfbe0f7247463?d=identicon&s=25 craigjordanlaw (Guest)
on 2005-11-28 06:15
(Received via mailing list)
Thanks for the quick reply. I'm just starting with both Ruby and Rails,
so this was very helpful. How would you execute the result of the
method_missing call? What should I do to make sure this is secure?
Bc6d88907ce09158581fbb9b469a35a3?d=identicon&s=25 james_b (Guest)
on 2005-11-29 07:43
(Received via mailing list)
Craig wrote:
> Thanks for the quick reply. I'm just starting with both Ruby and Rails,
> so this was very helpful. How would you execute the result of the
> method_missing call? What should I do to make sure this is secure?
>

Once method_missing has teased apart the request, you have  few options.

The example just created a string of code, so you could just pass that
to eval().  Not recommend expect perhaps to test some code to see that
things are perhaps working up to that point.


Since you decompose the name of the missing method into a class name and
a request, you can get a reference to the class then try to invoke
request as a class method.



class Finder

   def method_missing( sym,  *args )
    if sym.to_s =~ /(\w+)_find$/
      klass = $1.capitalize
      cls = Object.const_get( klass )
      return cls.find
    elsif sym.to_s =~ /(\w+)_find_by_([a-z_]+)/
      klass = $1.capitalize
      cls = Object.const_get( klass )
      params = $2
      cls.send( "find_by_#{params}", args )
    else
      super
    end
   end
end



If you prefer to create object instances, then first call 'new' on the
class reference:

   def method_missing( sym,  *args )
    if sym.to_s =~ /(\w+)_find$/
      klass = $1.capitalize
      cls = Object.const_get( klass )
      obj = cls.new
      return obj.find
    elsif sym.to_s =~ /(\w+)_find_by_([a-z_]+)/
      klass = $1.capitalize
      cls = Object.const_get( klass )
      params = $2
      obj = cls.new
      obj.send( "find_by_#{params}", args )
    else
      super
    end
   end


Note that all sorts of error handling has been omitted here.

You may want to take more precautions in what classes are created and
what methods get invoked.   For example you could first check that the
extracted class name is contained in an 'allowed objects' list before
instantiation anything.



Hope this helps,


James Britt
--

http://www.ruby-doc.org       - Ruby Help & Documentation
http://www.artima.com/rubycs/ - Ruby Code & Style: Writers wanted
http://www.rubystuff.com      - The Ruby Store for Ruby Stuff
http://www.jamesbritt.com     - Playing with Better Toys
http://www.30secondrule.com   - Building Better Tools
D4a6cf1447a473d5fb2bfbe0f7247463?d=identicon&s=25 craigjordanlaw (Guest)
on 2005-12-02 05:36
(Received via mailing list)
This is very helpful. Thank you very much. Not only have you helped me
with this particular problem, you've sent me scurrying after several
new concepts that have me thinking about other problems in new ways.
This topic is locked and can not be replied to.