开发者

Allow a block to reference classes/modules not currently in scope, but will be in scope when invoked?

Is it possible to do something like this in Ruby (1.9.2-p290)?

class SomeClass
  include SomeModuleThatProvidesLotOfConstants

  def build(&block)
    singleton_class.instance_eval(&block)
  end
end

obj = SomeClass.new
obj.build d开发者_高级运维o
  some_class_method SomeConstant, :an => :option
  ...
end

Where some_class_method is a method that is available to SomeClass (not to instances of it) and SomeConstant is a class/module that is in scope inside of SomeClass, but would have to be references as SomeClass::SomeConstant from outside.

I can get this working if I always pass fully-qualified class names inside my block, but I'm trying to effectively "re-scope" the block when it is invoked. Is this possible? I'm pretty sure RSpec and other such tools that make heavy use of blocks achieve something like this :)

Note that while I'm calling class methods from inside the block, I only want the changes to affect this individual singleton class, rather than propogate to all instances.

EDIT | Ok, here's the non-pseudo version of what I'm trying to achieve. I'm trying to add some DataMapper properties at runtime, but only to singleton classes... I don't want them to suddenly appear across all instances of the model.

class Post
  include DataMapper::Resource

  property :id,         Serial
  property :title,      String
  property :created_at, DateTime
  ... etc ...

  def virtualize(&block)
    singleton_class.instance_eval(&block)
    self
  end
end

def suspend_post
  @post = Post.get!(1).virtualize do
    property :delete_comments, Boolean
  end
end

I know there are other ways to do virtual attributes (I'm currently using a couple of different approaches, depending on the complexity), but I'm just experimenting with a few ideas to avoid cluttering my model definitions with transient methods that are only used for transporting form data in one specific part of the site and don't mean anything when you're reading the source code of the model by itself. One or two virtual attributes are ok, but as they start to mount up on commonly used models I start to explore things like this ;)

In the above, the resource would have all of the standard properties defined in the concrete class, plus any that are added in the #virtualize method. It's the reference to Boolean without the DataMapper::Property:: prefix that's throwing it off.


You've already got what you want with respect to methods. If you define some_class_method like this:

def Foo.some_class_method(name)
  define_method name do
    puts("this is the method #{name}")
  end
end

and do

f = Foo.new
f.build { some_class_method "new_method" }
f.singleton_methods # => [:new_method]

You've defined behavior on just that one instance.

However I don't think you can get what you're looking for with respect to constants. One option would be to use methods instead of constants for those arguments. Another would be to have the client code mix in whatever module defines the constants.

Do keep in mind this is pretty dense metaprogramming, so the complexity may not be justified.


What's wrong with this:

class SomeClass
  SOME_CONSTANT = 42

  class << self
    def some_class_method
      'foo'
    end
  end

  def build &block
    self.class.instance_eval(&block)
  end
end

SomeClass.new.build do
  puts "#{some_class_method} #{SOME_CONSTANT}"
end
#=>foo 42
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜