Ruby: Automatically set instance variable as method argument?

前端 未结 3 1665
时光说笑
时光说笑 2021-01-12 02:10

Are there any plans to implement ruby behavior similar to the CoffeeScript feature of specifying an instance variable name in a method argument list? Like

cl         


        
相关标签:
3条回答
  • 2021-01-12 02:20

    I think you answered your own question, it does not fit the ruby simplicity philosophy. It would add additional complexity for how parameters are handled in methods and moves the logic for managing variables up into the method parameters. I can see the argument that it makes the code less readable a toss up, but it does strike me as not very verbose.

    Some scenarios the @ param would have to contend with:

    def initialize( first, last, @scope, @opts = {} )
    
    def search( @query, condition )
    
    def ratchet( @*arg  ) 
    

    Should all of these scenarios be valid? Just the initialize? The @*arg seems particularly dicey in my mind. All these rules and exclusions make the Ruby language more complicated. For the benefit of auto instance variables, I do not think it would be worth it.

    0 讨论(0)
  • 2021-01-12 02:26

    After some pondering, I wondered if it's possible to actually get the argument names from a ruby method. If so, I could use a special argument prefix like "iv_" to indicate which args should be set as instance variables.

    And it is possible: How to get argument names using reflection.

    Yes! So I can maybe write a module to handle this for me. Then I got stuck because if I call the module's helper method, it doesn't know the values of the arguments because they're local to the caller. Ah, but ruby has Binding objects.

    Here's the module (ruby 1.9 only):

    module InstanceVarsFromArgsSlurper
      # arg_prefix must be a valid local variable name, and I strongly suggest
      # ending it with an underscore for readability of the slurped args.
      def self.enable_for(mod, arg_prefix)
        raise ArgumentError, "invalid prefix name" if arg_prefix =~ /[^a-z0-9_]/i
        mod.send(:include, self)
        mod.instance_variable_set(:@instance_vars_from_args_slurper_prefix, arg_prefix.to_s)
      end
    
      def slurp_args(binding)
        defined_prefix = self.class.instance_variable_get(:@instance_vars_from_args_slurper_prefix)
        method_name = caller[0][/`.*?'/][1..-2]
        param_names = method(method_name).parameters.map{|p| p.last.to_s }
        param_names.each do |pname|
          # starts with and longer than prefix
          if pname.start_with?(defined_prefix) and (pname <=> defined_prefix) == 1
            ivar_name = pname[defined_prefix.size .. -1]
            eval "@#{ivar_name} = #{pname}", binding
          end
        end
        nil
      end
    end
    

    And here's the usage:

    class User
      InstanceVarsFromArgsSlurper.enable_for(self, 'iv_')
    
      def initialize(iv_name, age)
        slurp_args(binding)  # this line does all the heavy lifting
        p [:iv_name, iv_name]
        p [:age, age]
        p [:@name, @name]
        p [:@age, @age]
      end
    end
    
    user = User.new("Methuselah", 969)
    p user
    

    Output:

    [:iv_name, "Methuselah"]
    [:age, 969]
    [:@name, "Methuselah"]
    [:@age, nil]
    #<User:0x00000101089448 @name="Methuselah">
    

    It doesn't let you have an empty method body, but it is DRY. I'm sure it can be enhanced further by merely specifying which methods should have this behavior (implemented via alias_method), rather than calling slurp_args in each method - the specification would have to be after all the methods are defined though.

    Note that the module and helper method name could probably be improved. I just used the first thing that came to mind.

    0 讨论(0)
  • 2021-01-12 02:38

    Well, actually...

    class User
      define_method(:initialize) { |@name| }
    end
    
    User.new(:name).instance_variable_get :@name
    # => :name
    

    Works in 1.8.7, but not in 1.9.3. Now, just where did I learn about this...

    0 讨论(0)
提交回复
热议问题