Created
October 24, 2017 16:18
-
-
Save vasilakisfil/47b30c155e31df2cd8b5cd127861d6ab to your computer and use it in GitHub Desktop.
A module for creating UI controllers in Rails
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module NestedControllers | |
CALLBACKS_OPTS = [:filter, :if, :unless, :kind].freeze | |
#adds the relative paths to controller so you can do `render 'subcontroller/something'` | |
#instead of `render 'parent_controller/subcontroller/something'` | |
#(solves 2) | |
def self.extended(base) | |
base.prepend_view_path("app/views/#{base.controller_path}/") | |
end | |
#creates a nested controller `{self}::{Name}Controller` that inherits from | |
#the controller that `self` inherits | |
def controller(name, options = {}, &block) | |
#save the code to an anonymized module | |
extended_m = Module.new | |
#create a new class that inherits parent and extends current module to support recursiveness | |
extended_superklass = Class.new(self.superclass).send(:extend, NestedControllers) | |
#create a new class that inherits the previously created class and sets that as a constant under parent controller | |
#ONLY THEN do we apply the developer's code in order to give | |
#the option to the developer to override any method defined by us or the parent controller | |
klass = self.const_set( | |
"#{name.to_s.camelize}Controller", | |
Class.new(extended_superklass, &block).send(:extend, extended_m) | |
) | |
#figure out the controller path | |
begin | |
name_path = self.controller_name | |
rescue NoMethodError | |
#if we get NoMethodError, this means that Rails hasn't set the class constant yet (like `ProfilesController::SubscriptionsController`) | |
#it happens when we have > 2 leves of nesting and we need to help Rails by passing a `controller_path` in options | |
name_path = nil | |
if options[:controller_path].nil? | |
raise 'You need to set a `controller_path` option in when nesting more than once' | |
end | |
end | |
#set the controller path (makes it easier to work with forms) | |
klass.send(:define_singleton_method, :controller_path) do | |
"#{(name_path || options[:controller_path])}/#{name.to_s}" | |
end | |
#set the views path (solves 1) | |
klass.prepend_view_path("app/views/#{(name_path || options[:controller_path])}/") | |
#add the parent's filters (solves 4) | |
unless options[:exclude_filters] | |
_inject_callbacks(klass) | |
end | |
end | |
#adds the {before|after}_filters defined in `self`, in `klass` | |
#(but not the filters from `self`'s ancestors since these run anyway because | |
#`klass` also inherits the same ancestors | |
#internally all callbacks in Rails are saved as `before` of `after` | |
def _inject_callbacks(klass) | |
callbacks_array = _process_action_callbacks.to_a.map{|c| | |
if superclass._process_action_callbacks.to_a.map{|s_c| | |
s_c.send(:instance_variable_get, "@filter") | |
}.include?(c.send(:instance_variable_get, "@filter")) | |
next | |
end | |
CALLBACKS_OPTS.inject({}){|memo, k| | |
memo[k] = c.send(:instance_variable_get, "@#{k}") | |
memo | |
} | |
}.compact | |
callbacks_array.each do |callback| | |
if callback[:filter].is_a? Symbol | |
klass.send("#{callback[:kind]}_action", callback[:filter], { | |
if: callback[:if], unless: callback[:unless] | |
}) | |
else | |
klass.send("#{callback[:kind]}_action", callback[:filter], { | |
if: callback[:if], unless: callback[:unless] | |
}) do | |
klass.instance_exec(&callback[:filter]) | |
end | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment