Surely there's a better way to do this... (implementing a DSL)

Discussion in 'Ruby' started by Tony Arcieri, Jan 25, 2011.

  1. Tony Arcieri

    Tony Arcieri Guest

    [Note: parts of this message were removed to make it a legal post.]

    I'm trying to write a method that builds a class and takes arguments from
    the outer scope when doing so:

    def awesome_mcjobify(state, options = {}, &action)
    subject_class = @subject_class

    job = Class.new do
    metaclass = class << self; self; end

    metaclass.send :define_method, :perform do |id|
    subject = subject_class.find(id)
    action subject
    end

    metaclass.send :define_method, :action, &action
    end

    subject_class.const_set "#{state.to_s.camelize}Job", job
    end

    This is trying to do a lot of things at once and feels icky. It's building a
    class that responds to a couple of methods, encapsulates a bit of state
    (into the class itself, I guess?), then sticks that class namespaced
    underneath another given class.

    Refactor me? :) I know there's supposed to be define_singleton_method in
    1.9, but using define_*method at all (not to mention send :define_method)
    seems a lot messier than it could potentially be.

    Any suggestions?

    --
    Tony Arcieri
    Medioh! Kudelski
     
    Tony Arcieri, Jan 25, 2011
    #1
    1. Advertising

  2. On Tue, Jan 25, 2011 at 4:10 AM, Tony Arcieri <> wro=
    te:
    > I'm trying to write a method that builds a class and takes arguments from
    > the outer scope when doing so:
    >
    > =A0 =A0def awesome_mcjobify(state, options =3D {}, &action)
    > =A0 =A0 =A0subject_class =3D @subject_class
    >
    > =A0 =A0 =A0job =3D Class.new do
    > =A0 =A0 =A0 =A0metaclass =3D class << self; self; end
    >
    > =A0 =A0 =A0 =A0metaclass.send :define_method, :perform do |id|
    > =A0 =A0 =A0 =A0 =A0subject =3D subject_class.find(id)
    > =A0 =A0 =A0 =A0 =A0action subject
    > =A0 =A0 =A0 =A0end
    >
    > =A0 =A0 =A0 =A0metaclass.send :define_method, :action, &action
    > =A0 =A0 =A0end
    >
    > =A0 =A0 =A0subject_class.const_set "#{state.to_s.camelize}Job", job
    > =A0 =A0end
    >
    > This is trying to do a lot of things at once and feels icky. It's buildin=

    g a
    > class that responds to a couple of methods, encapsulates a bit of state
    > (into the class itself, I guess?), then sticks that class namespaced
    > underneath another given class.
    >
    > Refactor me? :) I know there's supposed to be define_singleton_method in
    > 1.9, but using define_*method at all (not to mention send :define_method)
    > seems a lot messier than it could potentially be.
    >
    > Any suggestions?


    First thing that strikes me odd is that you define methods on the
    singleton class, i.e. instance methods of the newly created class.
    What do you need a class for then - especially since your new class
    inherits Object (which means, it does not inherit particular instance
    functionality)? From what I see you need something that responds to
    "perform" by doing two things

    1. find a subject via the subject class based on the id method parameter
    2. invoke the block passed with the subject found

    Wouldn't this do what you need?

    Proxy =3D Struct.new :subject_class, :action do
    def perform(id)
    subject =3D subject_class.find id
    action[subject]
    end
    end

    def awesome_mcjobify(state, options =3D {}, &action)
    Proxy[@subject_class, action]
    end

    It does not even need metaprogramming...

    Kind regards

    robert

    --=20
    remember.guy do |as, often| as.you_can - without end
    http://blog.rubybestpractices.com/
     
    Robert Klemme, Jan 25, 2011
    #2
    1. Advertising

  3. Tony Arcieri

    Tony Arcieri Guest

    [Note: parts of this message were removed to make it a legal post.]

    On Tue, Jan 25, 2011 at 7:19 AM, Robert Klemme
    <>wrote:

    > First thing that strikes me odd is that you define methods on the
    > singleton class, i.e. instance methods of the newly created class.
    > What do you need a class for then - especially since your new class
    > inherits Object (which means, it does not inherit particular instance
    > functionality)?



    Because I'm building these classes for another API, namely Resque. I'm
    creating a DSL for building Resque jobs, which the documentation defines
    thusly:

    Resque jobs are Ruby classes (or modules) which respond to
    the perform method. Here's an example:

    class Archive
    @queue = :file_serve

    def self.perform(repo_id, branch = 'master')
    repo = Repository.find(repo_id)
    repo.create_archive(branch)
    end
    end


    So unfortunately I can't change the form of the classes I'm trying to
    generate (with the possible exception of changing them into modules)

    --
    Tony Arcieri
    Medioh! Kudelski
     
    Tony Arcieri, Jan 25, 2011
    #3
  4. On Tue, Jan 25, 2011 at 6:33 PM, Tony Arcieri <> wro=
    te:
    > On Tue, Jan 25, 2011 at 7:19 AM, Robert Klemme
    > <>wrote:
    >
    >> First thing that strikes me odd is that you define methods on the
    >> singleton class, i.e. instance methods of the newly created class.
    >> What do you need a class for then - especially since your new class
    >> inherits Object (which means, it does not inherit particular instance
    >> functionality)?

    >
    >
    > Because I'm building these classes for another API, namely Resque. I'm
    > creating a DSL for building Resque jobs, which the documentation defines
    > thusly:
    >
    > Resque jobs are Ruby classes (or modules) which respond to
    > the perform method. Here's an example:
    >
    > class Archive
    > =A0@queue =3D :file_serve
    >
    > =A0def self.perform(repo_id, branch =3D 'master')
    > =A0 =A0repo =3D Repository.find(repo_id)
    > =A0 =A0repo.create_archive(branch)
    > =A0end
    > end
    >
    >
    > So unfortunately I can't change the form of the classes I'm trying to
    > generate (with the possible exception of changing them into modules)


    Then it seems this would do

    def awesome_mcjobify(state, options =3D {}, &action)
    cl =3D Class.new do
    def self.perform(id)
    @action[@cl.find(id)]
    end
    end

    cl.instance_variable_set "@action", action
    cl.instance_variable_set "@cl", @subject_class

    cl
    end

    However, it seems that https://github.com/defunkt/resque uses classes
    in order to be able to distribute them to different machines. In that
    case you would need to make sure the class has a name and is known at
    all nodes. In that case it might be wise to also add a String
    argument to the method call.

    Btw, if my assumption is correct you might get away with simple
    instances that implement important methods (#name for example) and are
    assigned to constants which reflect their name.

    Side note: code distribution is the Achilles heel of distributed job
    systems. You either have to distribute code along with the request
    which bloats queues and introduces security risks. Or you distribute
    it via some other channel and then you can get typical versioning
    issues...

    Kind regards

    robert

    --=20
    remember.guy do |as, often| as.you_can - without end
    http://blog.rubybestpractices.com/
     
    Robert Klemme, Jan 26, 2011
    #4
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. GG
    Replies:
    3
    Views:
    5,590
    mail2saion
    May 14, 2009
  2. Andoni
    Replies:
    0
    Views:
    359
    Andoni
    Apr 26, 2004
  3. Groove
    Replies:
    1
    Views:
    655
    Nathan Sokalski
    Jun 1, 2006
  4. Richard Shea
    Replies:
    3
    Views:
    1,758
    Peter Hansen
    May 2, 2004
  5. Greg
    Replies:
    10
    Views:
    638
    Kai-Uwe Bux
    Oct 11, 2006
Loading...

Share This Page