File: base.rb

package info (click to toggle)
ruby-rails-admin 0.8.1%2Bdfsg-3
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 2,492 kB
  • ctags: 1,292
  • sloc: ruby: 5,341; makefile: 3
file content (348 lines) | stat: -rw-r--r-- 11,461 bytes parent folder | download
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
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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
require 'rails_admin/config/proxyable'
require 'rails_admin/config/configurable'
require 'rails_admin/config/hideable'
require 'rails_admin/config/groupable'

module RailsAdmin
  module Config
    module Fields
      class Base # rubocop:disable ClassLength
        include RailsAdmin::Config::Proxyable
        include RailsAdmin::Config::Configurable
        include RailsAdmin::Config::Hideable
        include RailsAdmin::Config::Groupable

        attr_reader :name, :properties, :abstract_model
        attr_accessor :defined, :order, :section
        attr_reader :parent, :root

        def initialize(parent, name, properties)
          @parent = parent
          @root = parent.root

          @abstract_model = parent.abstract_model
          @defined = false
          @name = name.to_sym
          @order = 0
          @properties = properties
          @section = parent
        end

        register_instance_option :css_class do
          "#{self.name}_field"
        end

        def type_css_class
          "#{type}_type"
        end

        def virtual?
          properties.blank?
        end

        register_instance_option :column_width do
          nil
        end

        register_instance_option :sortable do
          !virtual? || children_fields.first || false
        end

        register_instance_option :searchable do
          !virtual? || children_fields.first || false
        end

        register_instance_option :queryable? do
          !virtual?
        end

        register_instance_option :filterable? do
          !!searchable # rubocop:disable DoubleNegation
        end

        register_instance_option :search_operator do
          @search_operator ||= RailsAdmin::Config.default_search_operator
        end

        # serials and dates are reversed in list, which is more natural (last modified items first).
        register_instance_option :sort_reverse? do
          false
        end

        # list of columns I should search for that field [{ column: 'table_name.column', type: field.type }, {..}]
        register_instance_option :searchable_columns do
          @searchable_columns ||= begin
            case searchable
            when true
              [{column: "#{abstract_model.table_name}.#{name}", type: type}]
            when false
              []
            when :all # valid only for associations
              table_name = associated_model_config.abstract_model.table_name
              associated_model_config.list.fields.collect { |f| {column: "#{table_name}.#{f.name}", type: f.type} }
            else
              [searchable].flatten.collect do |f|
                if f.is_a?(String) && f.include?('.')                            #  table_name.column
                  table_name, column = f.split '.'
                  type = nil
                elsif f.is_a?(Hash)                                              #  <Model|table_name> => <attribute|column>
                  am = f.keys.first.is_a?(Class) && AbstractModel.new(f.keys.first)
                  table_name = am && am.table_name || f.keys.first
                  column = f.values.first
                  property = am && am.properties.detect { |p| p.name == f.values.first.to_sym }
                  type = property && property.type
                else                                                             #  <attribute|column>
                  am = (self.association? ? associated_model_config.abstract_model : abstract_model)
                  table_name = am.table_name
                  column = f
                  property = am.properties.detect { |p| p.name == f.to_sym }
                  type = property && property.type
                end
                {column: "#{table_name}.#{column}", type: (type || :string)}
              end
            end
          end
        end

        register_instance_option :formatted_value do
          value
        end

        # output for pretty printing (show, list)
        register_instance_option :pretty_value do
          formatted_value.presence || ' - '
        end

        # output for printing in export view (developers beware: no bindings[:view] and no data!)
        register_instance_option :export_value do
          pretty_value
        end

        # Accessor for field's help text displayed below input field.
        register_instance_option :help do
          (@help ||= {})[::I18n.locale] ||= generic_field_help
        end

        register_instance_option :html_attributes do
          {
            required: required?,
          }
        end

        register_instance_option :default_value do
          nil
        end

        # Accessor for field's label.
        #
        # @see RailsAdmin::AbstractModel.properties
        register_instance_option :label do
          (@label ||= {})[::I18n.locale] ||= abstract_model.model.human_attribute_name name
        end

        register_instance_option :hint do
          (@hint ||= '')
        end

        # Accessor for field's maximum length per database.
        #
        # @see RailsAdmin::AbstractModel.properties
        register_instance_option :length do
          @length ||= properties && properties.length
        end

        # Accessor for field's length restrictions per validations
        #
        register_instance_option :valid_length do
          @valid_length ||= abstract_model.model.validators_on(name).detect { |v| v.kind == :length }.try(&:options) || {}
        end

        register_instance_option :partial do
          :form_field
        end

        # Accessor for whether this is field is mandatory.
        #
        # @see RailsAdmin::AbstractModel.properties
        register_instance_option :required? do
          context = begin
            if bindings && bindings[:object]
              bindings[:object].persisted? ? :update : :create
            else
              :nil
            end
          end
          (@required ||= {})[context] ||= !!([name] + children_fields).uniq.detect do |column_name| # rubocop:disable DoubleNegation
            abstract_model.model.validators_on(column_name).detect do |v|
              !(v.options[:allow_nil] || v.options[:allow_blank]) &&
              [:presence, :numericality, :attachment_presence].include?(v.kind) &&
              (v.options[:on] == context || v.options[:on].blank?) &&
              (v.options[:if].blank? && v.options[:unless].blank?)
            end
          end
        end

        # Accessor for whether this is a serial field (aka. primary key, identifier).
        #
        # @see RailsAdmin::AbstractModel.properties
        register_instance_option :serial? do
          properties && properties.serial?
        end

        register_instance_option :view_helper do
          :text_field
        end

        register_instance_option :read_only? do
          !editable?
        end

        # init status in the view
        register_instance_option :active? do
          false
        end

        register_instance_option :visible? do
          returned = true
          (RailsAdmin.config.default_hidden_fields || {}).each do |section, fields|
            next unless self.section.is_a?("RailsAdmin::Config::Sections::#{section.to_s.camelize}".constantize)
            returned = false if fields.include?(name)
          end
          returned
        end

        # columns mapped (belongs_to, paperclip, etc.). First one is used for searching/sorting by default
        register_instance_option :children_fields do
          []
        end

        register_instance_option :render do
          bindings[:view].render partial: "rails_admin/main/#{partial}", locals: {field: self, form: bindings[:form]}
        end

        def editable?
          !(@properties && @properties.read_only?)
        end

        # Is this an association
        def association?
          is_a?(RailsAdmin::Config::Fields::Association)
        end

        # Reader for validation errors of the bound object
        def errors
          ([name] + children_fields).uniq.collect do |column_name|
            bindings[:object].errors[column_name]
          end.uniq.flatten
        end

        # Reader whether field is optional.
        #
        # @see RailsAdmin::Config::Fields::Base.register_instance_option :required?
        def optional?
          !required?
        end

        # Inverse accessor whether this field is required.
        #
        # @see RailsAdmin::Config::Fields::Base.register_instance_option :required?
        def optional(state = nil, &block)
          if !state.nil? || block # rubocop:disable NonNilCheck
            required state.nil? ? proc { false == (instance_eval(&block)) } : false == state
          else
            optional?
          end
        end

        # Writer to make field optional.
        #
        # @see RailsAdmin::Config::Fields::Base.optional
        def optional=(state)
          optional(state)
        end

        # Reader for field's type
        def type
          @type ||= self.class.name.to_s.demodulize.underscore.to_sym
        end

        # Reader for field's value
        def value
          bindings[:object].safe_send(name)
        rescue NoMethodError => e
          raise e.exception <<-EOM.gsub(/^\s{10}/, '')
          #{e.message}
          If you want to use a RailsAdmin virtual field(= a field without corresponding instance method),
          you should declare 'formatted_value' in the field definition.
            field :#{name} do
              formatted_value{ bindings[:object].call_some_method }
            end
          EOM
        end

        # Reader for nested attributes
        register_instance_option :nested_form do
          false
        end

        # Allowed methods for the field in forms
        register_instance_option :allowed_methods do
          [method_name]
        end

        def generic_help
          (required? ? I18n.translate('admin.form.required') : I18n.translate('admin.form.optional')) + '. '
        end

        def generic_field_help
          model = abstract_model.model_name.underscore
          model_lookup = "admin.help.#{model}.#{name}".to_sym
          translated = I18n.translate(model_lookup, help: generic_help, default: [generic_help])
          (translated.is_a?(Hash) ? translated.to_a.first[1] : translated).html_safe
        end

        def parse_value(value)
          value
        end

        def parse_input(_params)
          # overriden
        end

        def inverse_of
          nil
        end

        def method_name
          name
        end

        def form_default_value
          (default_value if bindings[:object].new_record? && value.nil?)
        end

        def form_value
          form_default_value.nil? ? formatted_value : form_default_value
        end

        def inspect
          "#<#{self.class.name}[#{name}] #{
            instance_variables.collect do |v|
              value = instance_variable_get(v)
              if [:@parent, :@root, :@section, :@children_fields_registered,
                  :@associated_model_config, :@group, :@bindings].include? v
                if value.respond_to? :name
                  "#{v}=#{value.name.inspect}"
                else
                  "#{v}=#{value.class.name}"
                end
              else
                "#{v}=#{value.inspect}"
              end
            end.join(', ')
          }>"
        end
      end
    end
  end
end