Skip to content

Instantly share code, notes, and snippets.

@tinbka
Created January 19, 2016 23:34
Show Gist options
  • Save tinbka/9a7eb814ea8aad07d8b7 to your computer and use it in GitHub Desktop.
Save tinbka/9a7eb814ea8aad07d8b7 to your computer and use it in GitHub Desktop.
before_render class method for Rails controllers
# The MIT License (MIT)
#
# Copyright (c) 2015 <tinbka@gmail.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# Rails usage:
#
# # app/controllers/application_controller.rb
# include BeforeRender
#
# # app/controllers/my_controller.rb
# before_render do
# @collection &&= @collection.decorate
# @resource &&= @resource.decorate
# end
#
# Then, @resource or @collection will become decorated exactly before call to an explicit or implicit #render.
# Implicit #render is called just after the last line of a definition of MyController's current action.
#
# Supports :only and :except options in a manner equivalent to before_action method.
module BeforeRender
module ClassHelper
# Filters will be performed in order of definition in a controller.
# Filter inheritance works in accordance with class_attribute concept.
# The value returned by a filter does not affect workflow of subsequent filters.
def before_render(action=nil, **options, &block)
options = fetch_only_and_except_filter_options(options)
self.__before_render_filters += [[action ? action.to_sym : block, options]]
end
def skip_before_render(action, **options)
options = fetch_only_and_except_filter_options(options)
action = action.to_sym
self.__before_render_filters = self.__before_render_filters.map {|filter, filter_options|
if filter == action
if filter_options[:only]
if options[:only]
[filter, {only: filter_options[:only] - options[:only]}]
elsif options[:except]
[filter, {only: filter_options[:only] & options[:except]}]
else
next
end
elsif filter_options[:except]
if options[:only]
[filter, {only: filter_options[:except] & options[:only]}]
elsif options[:except]
[filter, {only: filter_options[:except] - options[:except]}]
else
next
end
else
if options[:only]
[filter, {except: options[:only]}]
elsif options[:except]
[filter, {only: options[:except]}]
else
next
end
end
end
[filter, filter_options]
}.compact
end
private
def fetch_only_and_except_filter_options(options)
options[:only] &&= Array.wrap(options[:only]).map {|name| name.to_sym}
options[:except] &&= Array.wrap(options[:except]).map {|name| name.to_sym}
if options[:only] and options[:except]
raise ArgumentError, "only one of the options :only and :except can be applied to filter"
end
options
end
end
def self.included(controller)
controller.class_eval do
extend BeforeRender::ClassHelper
class_attribute :__before_render_filters, instance_accessor: false
self.__before_render_filters = []
attr_accessor :render_args
# at this point _normalize_args has allready been called
# render_options is here a modifyable hash
# and is passed to a filter
define_method :render_to_body do |render_options|
self.class.__before_render_filters.each {|filter, options|
if execute_before_render?(filter, options)
filter.is_a?(Proc) ? instance_exec(render_options, &filter) : __send__(filter, render_options)
end
}
super render_options
end
end
end
private
def execute_before_render?(filter, options)
if options[:only]
options[:only].include?(params[:action].to_sym)
elsif options[:except]
!options[:except].include?(params[:action].to_sym)
else
true
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment