Advertisement
Guest User

Untitled

a guest
Jun 18th, 2019
100
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 3.16 KB | None | 0 0
  1. # Messing Around with Composing Concerns
  2.  
  3. 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.
  4.  
  5. **Problem:** the submodules need **both** `class` and `instance` methods from the shared template module.
  6. Yet, they'll define their own custom `class` and `instance` methods. All of which should be mixed in
  7. when a particular submodule (_concern_) is included on a concrete class (say "_controller_").
  8.  
  9. ## My Solution
  10.  
  11. Can't guarantee best practice on this, but here's what I've come up with (`ActiveSupport` is optional).
  12.  
  13. First, our template module:
  14.  
  15. ```ruby
  16. module TemplateConcernModule
  17.  
  18. COMMON_CONSTANT = "something_generic"
  19.  
  20. def self.common_class_method
  21. puts "I print '#{COMMON_CONSTANT}': a common module constant."
  22. end
  23.  
  24. protected
  25.  
  26. def protected_instance_method
  27. puts "I print '#{@wait_for_it}': instantiated by some concrete class."
  28. end
  29. end
  30. ```
  31.  
  32. We can imagine that the above module is `abstract` in some sense.
  33. Presumably, it hosts only a _partial_ solution.
  34. While possible, including it on classes `as is` would provide little benefit.
  35.  
  36. Instead, we complete our template module concretely:
  37.  
  38. ```ruby
  39. module AngryConcreteConcern
  40. extend ActiveSupport::Concern
  41.  
  42. include TemplateConcernModule
  43.  
  44. ANGRY_CONSTANT = "fuck"
  45.  
  46. class_methods do
  47. # inherit all template class methods
  48. TemplateConcernModule.singleton_methods.each do |m|
  49. define_method m, TemplateConcernModule.method(m).to_proc
  50. end
  51. def angry_class_method
  52. puts "I print '#{ANGRY_CONSTANT}': common for angry concerns."
  53. end
  54. end
  55.  
  56. def angry_instance_method
  57. puts "I print '#{@angry_ivar}': instantiated by some angry class."
  58. protected_instance_method
  59. end
  60.  
  61. end
  62. ```
  63.  
  64. All static class methods are now available on this module, and its instance method leverages part of the template.
  65.  
  66. Let's put this concern to use:
  67. ```ruby
  68. class ExampleController
  69. include AngryConcreteConcern
  70. def initialize
  71. @wait_for_it = "here it is"
  72. @angry_ivar = "#{ANGRY_CONSTANT}!!!"
  73. end
  74. end
  75. ```
  76.  
  77. and illustrate some behaviour:
  78. ```ruby
  79. puts "Constants: " +
  80. (ExampleController.constants(true) - Object.constants(true)).inspect
  81.  
  82. # > Constants: [:ANGRY_CONSTANT, :ClassMethods, :COMMON_CONSTANT]
  83.  
  84. puts "Class methods: " + (ExampleController.methods - Object.methods).inspect
  85.  
  86. # > Class methods: [:common_class_method, :angry_class_method]
  87.  
  88. puts "Instance methods: " +
  89. (ExampleController.instance_methods - Object.instance_methods).inspect
  90.  
  91. # > Instance methods: [:angry_instance_method, :protected_instance_method]
  92.  
  93. ExampleController.common_class_method
  94.  
  95. # > I print 'something_generic': a common module constant.
  96.  
  97. ExampleController.angry_class_method
  98.  
  99. # > I print 'fuck': common for angry concerns.
  100.  
  101. controller_instance = ExampleController.new
  102. controller_instance.angry_instance_method
  103.  
  104. # > I print 'fuck!!!': instantiated by angry classes.
  105. # > I print 'here it is': instantiated by some concrete class.
  106.  
  107. # in contrast, controller_instance.protected_instance_method would fail
  108. ```
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement