Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # Messing Around with Composing Concerns
- Recently, I wished to refactor a module (hosting common code: including `class` and `instance` methods) into submodules, splitting custom (say "_submodule_") logic from the shared (say "_template_") logic.
- **Problem:** the submodules need **both** `class` and `instance` methods from the shared template module.
- Yet, they'll define their own custom `class` and `instance` methods. All of which should be mixed in
- when a particular submodule (_concern_) is included on a concrete class (say "_controller_").
- ## My Solution
- Can't guarantee best practice on this, but here's what I've come up with (`ActiveSupport` is optional).
- First, our template module:
- ```ruby
- module TemplateConcernModule
- COMMON_CONSTANT = "something_generic"
- def self.common_class_method
- puts "I print '#{COMMON_CONSTANT}': a common module constant."
- end
- protected
- def protected_instance_method
- puts "I print '#{@wait_for_it}': instantiated by some concrete class."
- end
- end
- ```
- We can imagine that the above module is `abstract` in some sense.
- Presumably, it hosts only a _partial_ solution.
- While possible, including it on classes `as is` would provide little benefit.
- Instead, we complete our template module concretely:
- ```ruby
- module AngryConcreteConcern
- extend ActiveSupport::Concern
- include TemplateConcernModule
- ANGRY_CONSTANT = "fuck"
- class_methods do
- # inherit all template class methods
- TemplateConcernModule.singleton_methods.each do |m|
- define_method m, TemplateConcernModule.method(m).to_proc
- end
- def angry_class_method
- puts "I print '#{ANGRY_CONSTANT}': common for angry concerns."
- end
- end
- def angry_instance_method
- puts "I print '#{@angry_ivar}': instantiated by some angry class."
- protected_instance_method
- end
- end
- ```
- All static class methods are now available on this module, and its instance method leverages part of the template.
- Let's put this concern to use:
- ```ruby
- class ExampleController
- include AngryConcreteConcern
- def initialize
- @wait_for_it = "here it is"
- @angry_ivar = "#{ANGRY_CONSTANT}!!!"
- end
- end
- ```
- and illustrate some behaviour:
- ```ruby
- puts "Constants: " +
- (ExampleController.constants(true) - Object.constants(true)).inspect
- # > Constants: [:ANGRY_CONSTANT, :ClassMethods, :COMMON_CONSTANT]
- puts "Class methods: " + (ExampleController.methods - Object.methods).inspect
- # > Class methods: [:common_class_method, :angry_class_method]
- puts "Instance methods: " +
- (ExampleController.instance_methods - Object.instance_methods).inspect
- # > Instance methods: [:angry_instance_method, :protected_instance_method]
- ExampleController.common_class_method
- # > I print 'something_generic': a common module constant.
- ExampleController.angry_class_method
- # > I print 'fuck': common for angry concerns.
- controller_instance = ExampleController.new
- controller_instance.angry_instance_method
- # > I print 'fuck!!!': instantiated by angry classes.
- # > I print 'here it is': instantiated by some concrete class.
- # in contrast, controller_instance.protected_instance_method would fail
- ```
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement