Module: ExpressTemplates::Components::Capabilities::Configurable

Included in:
ExpressTemplates::Component, ExpressTemplates::Components::Configurable, ExpressTemplates::Components::Container
Defined in:
lib/express_templates/components/capabilities/configurable.rb

Class Method Summary collapse

Class Method Details

.included(base) ⇒ Object



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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/express_templates/components/capabilities/configurable.rb', line 7

def self.included(base)
  base.class_eval do

    class_attribute :supported_options
    self.supported_options = {}

    class_attribute :supported_arguments
    self.supported_arguments = {}


    def self.emits(*args, &block)
      warn ".emits is deprecrated"
      self.contains(*args, &block)
    end

    def build(*args, &block)
      _process_builder_args!(args)
      super(*args, &block)
    end

    def config
      @config ||= {}
    end

    def self.has_option(name, description, type: :string, required: nil, default: nil, attribute: nil, values: nil)
      raise "name must be a symbol" unless name.kind_of?(Symbol)
      option_definition = {description: description}
      option_definition.merge!(type: type, required: required, default: default, attribute: attribute, values: values)
      self.supported_options =
        self.supported_options.merge(name => option_definition)
    end

    def required_options
      supported_options.select {|k,v| v[:required] unless v[:default] }
    end

    def self.has_argument(name, description, as: nil, type: :string, default: nil, optional: false)
      raise "name must be a symbol" unless name.kind_of?(Symbol)
      argument_definition = {description: description, as: as, type: type, default: default, optional: optional}
      self.supported_arguments =
        self.supported_arguments.merge(name => argument_definition)
    end

    has_argument :id, "The id of the component.", type: :symbol, optional: true

    protected

      def _default_options
        supported_options.select {|k,v| v[:default] }
      end

      def _check_required_options(supplied)
        missing = required_options.keys - supplied.keys
        if missing.any?
          raise "#{self.class} missing required option(s): #{missing}"
        end
      end

      def _set_defaults
        _default_options.each do |key, value|
          default_value = value[:default].respond_to?(:call) ? value[:default].call : value[:default]
          if !!value[:attribute]
            set_attribute key, default_value
          else
            if config[key].nil?
              config[key] = default_value
            end
          end
        end
      end

      def _valid_types(definition)
        valid_type_names = if definition[:type].kind_of?(Symbol)
            [definition[:type]]
          elsif definition[:type].respond_to?(:keys)
            definition[:type].keys
          else
            definition[:type] || []
          end
        valid_type_names.map do |type_name|
          if type_name.eql?(:boolean)
            type_name
          else
            type_name.to_s.classify.constantize
          end
        end
      end

      def _is_valid?(value, definition)
        valid_types = _valid_types(definition)
        if valid_types.empty? && value.kind_of?(String)
          true
        elsif valid_types.include?(value.class)
          true
        elsif valid_types.include?(:boolean) &&
              [1, 0, true, false].include?(value)
          true
        else
          false
        end
      end

      def _optional_argument?(definition)
        definition[:default] || definition[:optional]
      end

      def _required_argument?(definition)
        !_optional_argument?(definition)
      end

      def _extract_supported_arguments!(args)
        supported_arguments.each do |key, definition|
          value = args.shift
          if value.nil? && _required_argument?(definition)
            raise "argument for #{key} not supplied"
          end
          unless _is_valid?(value, definition)
            if _required_argument?(definition)
              raise "argument for #{key} invalid (#{value.class}) '#{value.to_s}'; Allowable: #{_valid_types(definition).inspect}"
            else
              args.unshift value
              next
            end
          end
          config_key = definition[:as] || key
          config[config_key] = value || definition[:default]
        end
      end

      def _set_id_attribute
        attributes[:id] = config[:id]
      end

      def _extract_supported_options!(builder_options)
        builder_options.each do |key, value|
          if supported_options.keys.include?(key)
            unless supported_options[key][:attribute]
              config[key] = builder_options.delete(key)
            end
          end
        end
      end

      # TODO: this method should probably save the unprocessed builder options
      #       for passing to builder/helpers like in FormComponent
      def _process_builder_args!(args)
        _extract_supported_arguments!(args)
        builder_options = args.last.try(:kind_of?, Hash) ? args.last : {}
        _check_required_options(builder_options)
        _extract_supported_options!(builder_options)
        _set_defaults
        _set_id_attribute
      end
  end
end