如何从接受要在实例上下文中执行的块的类方法内部添加实例方法

时间:2014-01-19 01:58:29

标签: ruby-on-rails ruby metaprogramming memoization

我完全重写了这个问题,因为我觉得这更准确地反映了我第一次以较少的迂回方式提出的要求。

在实例化FormObject之后,对动态定义的方法的调用不会在我正在尝试的上下文中评估它们的块参数。例如:

@registration = RegistrationForm.new
@registration.user
# undefined local variable or method `user_params' for RegistrationForm:Class

RegistrationForm调用一个类方法exposing(:user) { User.new(user_params) },我想要定义一个如下所示的新方法:

def user
  @user ||= User.new(user_params)
end

我的实现不使用@ivar ||=来缓存值(因为falsey值会导致重新评估该方法)。我从rspec的memoized_helpers中借用了这个想法,我想'我明白它是如何工作的。我不明白的是我应该在class_eval中替换lib/form_object/memoized_helpers.rb

谢谢

LIB / form_object / base.rb

class FormObject::Base
  include ActiveModel::Model
  include FormObject::MemoizedHelpers

  attr_reader :params, :errors

  def initialize(params = {})
    @params = ActionController::Parameters.new(params)
    @errors = ActiveModel::Errors.new(self)
  end

  def save
    valid? && persist
  end
end

LIB / form_object / memoized_helpers.rb

module FormObject
  module MemoizedHelpers
    private
    def __memoized
      @__memoized ||= {}
    end

    def self.included(mod)
      mod.extend(ClassMethods)
    end

    module ClassMethods
      def exposing(name, &block)
        raise "#exposing called without a block" unless block_given?

        class_eval do
          define_method(name) { __memoized.fetch(name) { |k| __memoized[k] = block.call } }
        end
      end
    end
  end
end

应用/形式/ registration_form.rb

class RegistrationForm < FormObject::Base
  exposing(:user)   { User.new(user_params) { |u| u.is_admin = true } }
  exposing(:tenant) { user.build_tenant(tenant_params) }

  validate do
    tenant.errors.each do |key, value|
      errors.add("#{tenant.class.name.underscore}_#{key}", value)
    end unless tenant.valid?
  end

  validate do
    user.errors.each do |key, value|
      errors.add("#{user.class.name.underscore}_#{key}", value)
    end unless user.valid?
  end

  private

  def persist
    user.save
  end

  def user_params
    params.fetch(:user, {}).permit(:first_name, :last_name, :email, :password, :password_confirmation)
  end

  def tenant_params
    params.fetch(:tenant, {}).permit(:name)
  end
end

1 个答案:

答案 0 :(得分:1)

所以,我可能过多地简化了这个例子,但我认为这就是你想要的:

module Exposing
  def exposing(name, &block)
    instance_eval do
      define_method(name, block)
    end
  end
end

class Form
  extend Exposing

  exposing(:user) { user_params }

  def user_params
    {:hi => 'ho'}
  end
end

Form.new.user

你可以在这里摆弄:http://repl.it/OCa