aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorcedricnjanga2018-01-30 06:37:52 -0800
committercedricnjanga2018-01-30 06:37:52 -0800
commit7ec19d8dc153e1491cb1a693b11b3b0a351f316c (patch)
tree6da6a8bbcf1ae789a460fece5cf7e961562c5222 /lib
parentf32d869cc3f34a939764cc3fa4d612a5d6544d08 (diff)
parent617a54cf63d7f11111b168534d49cdc04a5865d4 (diff)
downloadchouette-core-7ec19d8dc153e1491cb1a693b11b3b0a351f316c.tar.bz2
Merge branch 'master' into 5683-add_workgroup_id_to_calendars
Diffstat (limited to 'lib')
-rw-r--r--lib/af83/decorator.rb122
-rw-r--r--lib/af83/decorator/enhanced_decorator.rb145
-rw-r--r--lib/af83/decorator/link.rb153
3 files changed, 420 insertions, 0 deletions
diff --git a/lib/af83/decorator.rb b/lib/af83/decorator.rb
new file mode 100644
index 000000000..f990555fe
--- /dev/null
+++ b/lib/af83/decorator.rb
@@ -0,0 +1,122 @@
+class AF83::Decorator < ModelDecorator
+ include AF83::Decorator::EnhancedDecorator
+ extend AF83::Decorator::EnhancedDecorator::ClassMethods
+
+ def self.decorates klass
+ instance_decorator.decorates klass
+ end
+
+ def self.instance_decorator
+ @instance_decorator ||= begin
+ klass = Class.new(AF83::Decorator::InstanceDecorator)
+ klass.delegate_all
+ klass
+ end
+ end
+
+ def self.with_instance_decorator
+ @_with_instance_decorator = true
+ yield instance_decorator
+ @_with_instance_decorator = false
+ end
+
+ def self.decorate object, options = {}
+ if object.is_a?(ActiveRecord::Base)
+ return instance_decorator.decorate object, options
+ else
+ self.new object, options.update(with: instance_decorator)
+ end
+ end
+
+ def self.define_instance_method method_name, &block
+ instance_decorator.send(:define_method, method_name, &block)
+ end
+
+ # Defines a class method on the decorated object's class. These
+ # can be called like `object.class.my_method`.
+ def self.define_instance_class_method method_name, &block
+ instance_decorator.send(:define_singleton_method, method_name, &block)
+ end
+
+ class ActionLinks
+ attr_reader :options
+
+ delegate :each, :map, :size, :first, :last, :any?, :select, to: :resolve
+
+ def initialize opts
+ @options = opts.deep_dup
+ end
+
+ def for_group group
+ returning_a_copy do
+ @options[:groups] = [group] if group.present?
+ end
+ end
+
+ def for_groups groups
+ returning_a_copy do
+ @options[:groups] = groups if groups.present?
+ end
+ end
+
+ def primary
+ for_group :primary
+ end
+
+ def secondary
+ for_group :secondary
+ end
+
+ def resolve
+ out = @options[:links].map{|l| l.bind_to_context(@options[:context], @options[:action])}.select{|l| l.enabled?}
+ if @options[:groups].present?
+ out = out.select do |l|
+ @options[:groups].any? do |g|
+ l.in_group_for_action?(g)
+ end
+ end
+ end
+ out
+ end
+ alias_method :to_ary, :resolve
+
+ def grouped_by *groups
+ add_footer = groups.include?(:footer)
+ groups -= [:footer]
+ out = HashWithIndifferentAccess[*groups.map{|g| [g, []]}.flatten(1)]
+ out[:other] = []
+ if add_footer
+ out[:footer] = []
+ groups << :footer
+ end
+
+ each do |l|
+ found = false
+ groups.each do |g|
+ if l.in_group_for_action?(g)
+ out[g] << l
+ found = true
+ next
+ end
+ end
+ out[:other] << l unless found
+ end
+ out
+ end
+
+ private
+ def returning_a_copy &block
+ out = ActionLinks.new options
+ out.instance_eval &block
+ out
+ end
+ end
+
+ class IncompleteLinkDefinition < RuntimeError
+ end
+
+ class InstanceDecorator < Draper::Decorator
+ include AF83::Decorator::EnhancedDecorator
+ extend AF83::Decorator::EnhancedDecorator::ClassMethods
+ end
+end
diff --git a/lib/af83/decorator/enhanced_decorator.rb b/lib/af83/decorator/enhanced_decorator.rb
new file mode 100644
index 000000000..904d1b2da
--- /dev/null
+++ b/lib/af83/decorator/enhanced_decorator.rb
@@ -0,0 +1,145 @@
+module AF83::Decorator::EnhancedDecorator
+ module ClassMethods
+ def action_link args={}
+ raise "You are using `action_link` inside a with_instance_decorator block, but not on the instance decorator itself.\n Use `instance_decorator.action_link` or move outside of the block, as this may lead to an unforeseen behaviour." if @_with_instance_decorator
+ args[:if] = @_condition if args[:if].nil?
+
+ options, link_options = parse_options args
+
+ link = AF83::Decorator::Link.new(link_options)
+ instance_exec(link, &options[:before_block]) if options[:before_block]
+ yield link if block_given?
+ raise AF83::Decorator::IncompleteLinkDefinition.new(link.errors) unless link.complete?
+
+ weight = options[:weight] || 1
+ @_action_links ||= []
+ @_action_links[weight] ||= []
+ @_action_links[weight] << link
+ end
+
+ ### Here we define some shortcuts that match dthe default behaviours
+ def create_action_link args={}, &block
+ opts = {
+ on: :index,
+ primary: :index,
+ policy: :create,
+ before_block: -> (l){
+ l.content { h.t('actions.add') }
+ l.href { [:new, object.klass.name.underscore.singularize] }
+ }
+ }
+ action_link opts.update(args), &block
+ end
+
+ def show_action_link args={}, &block
+ opts = {
+ on: :index,
+ primary: :index,
+ before_block: -> (l){
+ l.content { h.t('actions.show') }
+ l.href { [object] }
+ }
+ }
+ action_link opts.update(args), &block
+ end
+
+ def edit_action_link args={}, &block
+ opts = {
+ primary: %i(show index),
+ policy: :edit,
+ before_block: -> (l){
+ l.content { h.t('actions.edit') }
+ l.href { [:edit, object] }
+ }
+ }
+ action_link opts.update(args), &block
+ end
+
+ def destroy_action_link args={}, &block
+ opts = {
+ policy: :destroy,
+ footer: true,
+ secondary: :show,
+ before_block: -> (l){
+ l.content { h.destroy_link_content }
+ l.href { [object] }
+ l.method :delete
+ l.data {{ confirm: h.t('actions.destroy_confirm') }}
+ }
+ }
+ action_link opts.update(args), &block
+ end
+
+ def t key
+ eval "-> (l){ h.t('#{key}') }"
+ end
+
+ def with_condition condition, &block
+ @_condition = condition
+ instance_eval &block
+ @_condition = nil
+ end
+
+ def action_links action
+ (@_action_links || []).flatten.compact.select{|l| l.for_action?(action)}
+ end
+
+ def parse_options args
+ options = {}
+ %i(weight primary secondary footer on action actions policy feature if groups group before_block).each do |k|
+ options[k] = args.delete(k) if args.has_key?(k)
+ end
+ link_options = args.dup
+
+ actions = options.delete :actions
+ actions ||= options.delete :on
+ actions ||= [options.delete(:action)]
+ actions = [actions] unless actions.is_a?(Array)
+ link_options[:_actions] = actions.compact
+
+ link_options[:_groups] = options.delete(:groups)
+ link_options[:_groups] ||= {}
+ if single_group = options.delete(:group)
+ if(single_group.is_a?(Symbol) || single_group.is_a?(String))
+ link_options[:_groups][single_group] = true
+ else
+ link_options[:_groups].update single_group
+ end
+ end
+ link_options[:_groups][:primary] ||= options.delete :primary
+ link_options[:_groups][:secondary] ||= options.delete :secondary
+ link_options[:_groups][:footer] ||= options.delete :footer
+
+ link_options[:_if] = options.delete(:if)
+ link_options[:_policy] = options.delete(:policy)
+ link_options[:_feature] = options.delete(:feature)
+ [options, link_options]
+ end
+ end
+
+ def action_links action=:index, opts={}
+ @action = action&.to_sym
+ links = AF83::Decorator::ActionLinks.new links: self.class.action_links(action), context: self, action: action
+ group = opts[:group]
+ links = links.for_group opts[:group]
+ links
+ end
+
+ def primary_links action=:index
+ action_links(action, group: :primary)
+ end
+
+ def secondary_links action=:index
+ action_links(action, group: :secondary)
+ end
+
+ def check_policy policy
+ _object = policy.to_s == "create" ? object.klass : object
+ method = "#{policy}?"
+ h.policy(_object).send(method)
+ end
+
+ def check_feature feature
+ h.has_feature? feature
+ end
+end
diff --git a/lib/af83/decorator/link.rb b/lib/af83/decorator/link.rb
new file mode 100644
index 000000000..7d2896e6a
--- /dev/null
+++ b/lib/af83/decorator/link.rb
@@ -0,0 +1,153 @@
+class AF83::Decorator::Link
+ REQUIRED_ATTRIBUTES = %i(href content)
+
+ attr_reader :context
+ attr_reader :action
+
+ def initialize options={}
+ @options = {}
+ options.each do |k, v|
+ send "#{k}", v
+ end
+ end
+
+ def bind_to_context context, action
+ @context = context
+ @action = action
+ self
+ end
+
+ def method *args
+ link_method *args
+ end
+
+ def class *args
+ link_class args
+ end
+
+ def method_missing name, *args, &block
+ if block_given?
+ @options[name] = block
+ elsif args.size == 0
+ out = @options[name]
+ out = context.instance_exec(self, &out) if out.is_a?(Proc)
+ out
+ else
+ # we can use l.foo("bar") or l.foo = "bar"
+ if name.to_s =~ /\=$/
+ _name = name.to_s.gsub(/=$/, '')
+ return send(_name, *args, &block)
+ end
+ @options[name] = args.first
+ end
+ end
+
+ def options
+ @options.symbolize_keys
+ end
+
+ def complete?
+ @missing_attributes = REQUIRED_ATTRIBUTES.select{|a| !@options[a].present?}
+ @missing_attributes.empty?
+ end
+
+ def enabled_actions
+ @options[:_actions].map(&:to_s) || []
+ end
+
+ def for_action? action=nil
+ action ||= @action
+ enabled_actions.empty? || enabled_actions.include?(action.to_s)
+ end
+
+ def actions_for_group group
+ val = @options[:_groups][group]
+ val.is_a?(Array) ? val.map(&:to_s) : val
+ end
+
+ def in_group_for_action? group
+ vals = actions_for_group(group)
+ if vals.is_a?(Array)
+ return vals.include?(@action.to_s)
+ elsif vals.is_a?(String) || vals.is_a?(Symbol)
+ vals.to_s == @action.to_s
+ else
+ !!vals
+ end
+ end
+
+ def primary?
+ in_group_for_action? :primary
+ end
+
+ def secondary?
+ in_group_for_action? :secondary
+ end
+
+ def enabled?
+ enabled = false
+ if @options[:_if].nil?
+ enabled = true
+ elsif @options[:_if].is_a?(Proc)
+ enabled = context.instance_exec(&@options[:_if])
+ else
+ enabled = !!@options[:_if]
+ end
+
+ enabled = enabled && check_policy(@options[:_policy]) if @options[:_policy].present?
+ enabled = enabled && check_feature(@options[:_feature]) if @options[:_feature].present?
+ enabled
+ end
+
+ def check_policy(policy)
+ @context.check_policy policy
+ end
+
+ def check_feature(feature)
+ @context.check_feature feature
+ end
+
+ def errors
+ "Missing attributes: #{@missing_attributes.to_sentence}"
+ end
+
+ def add_class val
+ @options[:link_class] ||= []
+ @options[:link_class] << val
+ @options[:link_class].flatten!
+ end
+
+ def extra_class
+ (options[:link_class] || []).join(' ')
+ end
+
+ def html_options
+ out = {}
+ options.each do |k, v|
+ out[k] = self.send(k) unless k == :content || k == :href || k.to_s =~ /^_/
+ end
+ out[:method] = link_method
+ out[:class] = extra_class
+ out.delete(:link_class)
+ out[:class] += " disabled" if disabled
+ out[:disabled] = !!disabled
+ out
+ end
+
+ def to_html
+ if block_given?
+ link = AF83::Decorator::Link.new(@options).bind_to_context(context, @action)
+ yield link
+ return link.to_html
+ end
+ if type&.to_sym == :button
+ HTMLElement.new(
+ :button,
+ content,
+ html_options
+ ).to_html
+ else
+ context.h.link_to content, href, html_options
+ end
+ end
+end