Skip to content

Instantly share code, notes, and snippets.

@saturnflyer
Last active December 18, 2015 13:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save saturnflyer/5788041 to your computer and use it in GitHub Desktop.
Save saturnflyer/5788041 to your computer and use it in GitHub Desktop.
This is a class methods that allows you to simplify the setup of arguments, objects, and methods.
class Something
setup(:first, :second)
module Second
end
end
# This is the equivalent of what the code above does...
class Something
def initialize(first, second)
@first, @second = first, second.extend(Second)
end
attr_reader :first, :second
private: :first, second
module Second
end
end
# implementation of the setup method
class Something
def setup(*setup_args)
attr_reader(*setup_args)
private(*setup_args)
define_method(:initialize){ |*args|
Hash[setup_args.zip(args)].each{ |role, object|
role_module_name = role.classify # from active support
klass = self.class
if mod = klass.const_defined?(role_module_name) && !mod.is_a?(Class)
object = object.extend(klass.const_get(role_module_name))
end
instance_variable_set("@#{role}", object)
}
}
end
end
@cupakromer
Copy link

The Hash[] on line 29 is sort of pointless. I understand you're trying to show that you're mapping the role name to the object, but it does nothing for your code. You never use it and since this is the initialize method, it will never get returned:

setup_args = [:first, :second]
args = [1, 2]
Hash[setup_args.zip(args)].each{ |a, b| puts "#{a}\t#{b}" }
# first 1
# second    2

setup_args.zip(args).each{ |a, b| puts "#{a}\t#{b}" }
# first 1
# second    2

@cupakromer
Copy link

Not completely happy, but it's easier for me to understand at a glance. Also, the new class methods can be easily re-used.

class Something
  private_attr_reader :roles

  def self.setup(*roles)
    @roles = roles.dup.freeze
    private_attr_reader *roles

    define_method(:initialize) do |*args|
      raise ArgumentError.new 'roles size differs' if args.size > roles.size

      roles.zip(args).each do |role, object|
        object.extend(get_role_module role) if has_role_module? role
        instance_variable_set("@#{role}", object)
      end
    end
  end

  def self.private_attr_reader(*attributes)
    attr_reader(*attributes)
    private(*attributes)
  end

  def self.has_role_module?(role)
    roles.include?(role) && self.class.const_defined?(role.classify) # from active support
  end

  def self.get_role_module(role)
    self.class.const_get role.classify
  end
end

@cupakromer
Copy link

Original line 34 (see next line) attempted to check if the module existed:

if mod = klass.const_defined?(role_module_name) && !mod.is_a?(Class)

However, mod would only ever be a boolean based on const_defined? being a predicate. So !mod.is_a?(Class) would always be true, as mod would either of been true or false. In my version, I didn't do the check in either has_role_module? or get_role_module.

Mostly because I was being lazy; partly because I wanted to emphasize the refactor, and not bullet proof the code.

@saturnflyer
Copy link
Author

Ah. Good catch with the is_a? call on a false object.

I'm thinking about this stuff and reworking things.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment