aboutsummaryrefslogtreecommitdiffstats
path: root/lib/af83/decorator.rb
blob: f990555fe5c7f09268f504312fd343f0fcf4beae (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
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