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

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? :slight_smile: 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?

On Tue, Jan 25, 2011 at 4:10 AM, Tony A. [email protected]
wrote:

subject = subject_class.find(id)

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? :slight_smile: 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 = Struct.new :subject_class, :action do
def perform(id)
subject = subject_class.find id
action[subject]
end
end

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

It does not even need metaprogramming…

Kind regards

robert

On Tue, Jan 25, 2011 at 7:19 AM, Robert K.
[email protected]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)

On Tue, Jan 25, 2011 at 6:33 PM, Tony A. [email protected]
wrote:

Because I’m building these classes for another API, namely Resque. I’m
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)

Then it seems this would do

def awesome_mcjobify(state, options = {}, &action)
cl = 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 GitHub - defunkt/resque: Moved to resque/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