Class: Attributable::Attribute

Inherits:
Object
  • Object
show all
Defined in:
app/models/attributable/attribute.rb

Overview

Summarises the validations for an attribute In addition to the basis rails validation also provides: 1) Information to assist with automatically generating form elements 2) Tools to assist with validating eg. submissions prior to the creation of the requests themselves 3) Wiping out some fields on the condition of others

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(owner, name, options = {}) ⇒ Attribute

Returns a new instance of Attribute.



15
16
17
18
19
20
21
22
# File 'app/models/attributable/attribute.rb', line 15

def initialize(owner, name, options = {})
  @owner = owner
  @name = name.to_sym
  @options = options
  @default = options.delete(:default)
  @required = options.delete(:required).present?
  @validator = options.delete(:validator).present?
end

Instance Attribute Details

#defaultObject (readonly)

rubocop:todo Metrics/ClassLength



11
12
13
# File 'app/models/attributable/attribute.rb', line 11

def default
  @default
end

#nameObject (readonly) Also known as: assignable_attribute_name

rubocop:todo Metrics/ClassLength



11
12
13
# File 'app/models/attributable/attribute.rb', line 11

def name
  @name
end

Class Method Details

.find_display_name(klass, name) ⇒ Object

rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity



133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'app/models/attributable/attribute.rb', line 133

def self.find_display_name(klass, name)
  translation = I18n.t("metadata.#{klass.name.underscore.tr('/', '.')}.#{name}")

  return translation[:label] if translation.is_a?(Hash) # translation found, we return the label

  superclass = klass.superclass
  if superclass == ActiveRecord::Base
    # We've reached the top and have no translation
    translation # shoulb be an error message, so that's ok
  else
    # We still have a parent class
    find_display_name(superclass, name) # Walk up the class hierarchy and try again
  end
end

Instance Method Details

#boolean?Boolean

Returns:

  • (Boolean)


54
55
56
# File 'app/models/attributable/attribute.rb', line 54

def boolean?
  @options.key?(:boolean)
end

#configure(model) ⇒ Object

rubocop:todo Metrics/PerceivedComplexity, Metrics/MethodLength, Metrics/AbcSize



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
# File 'app/models/attributable/attribute.rb', line 87

def configure(model) # rubocop:todo Metrics/CyclomaticComplexity
  conditions = @options.slice(:if, :on)
  save_blank_value = @options.delete(:save_blank)
  allow_blank = save_blank_value
  model.with_options(conditions) do |object|
    # false.blank? == true, so we exclude booleans here, they handle themselves further down.
    object.validates_presence_of(name) if required? && !boolean?
    object.with_options(allow_nil: optional?, allow_blank: allow_blank) do |required|
      required.validates_inclusion_of(name, in: [true, false]) if boolean?
      if integer? || float?
        numericality_options = { only_integer: integer?, greater_than_or_equal_to: minimum }
        numericality_options[:less_than_or_equal_to] = maximum if maximum.present?
        required.validates name, numericality: numericality_options
      end
      required.validates_inclusion_of(name, in: selection_values, allow_false: true) if fixed_selection?
      required.validates_format_of(name, with: valid_format) if valid_format?

      # Custom validators should handle nil explicitly.
      required.validates name, custom: true, allow_nil: false if validator?
    end
  end

  unless save_blank_value
    model.class_eval(
      "
      before_validation do |record|
        value = record.#{name}
        record.#{name}= nil if value and value.blank?
      end
    "
    )
  end

  return if conditions[:if].nil?

  model.class_eval(
    "
    before_validation do |record|
      record.#{name}= nil unless record.#{conditions[:if]}
    end
  "
  )
end

#default_from(origin = nil) ⇒ Object



28
29
30
31
32
# File 'app/models/attributable/attribute.rb', line 28

def default_from(origin = nil)
  return nil if origin.nil?

  origin.validator_for(name).default if validator?
end

#display_nameObject



148
149
150
# File 'app/models/attributable/attribute.rb', line 148

def display_name
  Attribute.find_display_name(@owner, name)
end

#find_default(validator_source = nil) ⇒ type

Find the default value for the attribute. Validator source needs to respond to #validator_for such as metadata or a request type. such as those on request types, you can pass in the validators here.

Parameters:

  • validator_source (#validator_for) (defaults to: nil)

    In cases where defaults are dynamic

Returns:

  • (type)

    [description]



160
161
162
# File 'app/models/attributable/attribute.rb', line 160

def find_default(validator_source = nil)
  default_from(validator_source) || default
end

#fixed_selection?Boolean

Returns:

  • (Boolean)


58
59
60
# File 'app/models/attributable/attribute.rb', line 58

def fixed_selection?
  @options.key?(:in)
end

#float?Boolean

Returns:

  • (Boolean)


50
51
52
# File 'app/models/attributable/attribute.rb', line 50

def float?
  @options.fetch(:positive_float, false)
end

#from(record) ⇒ Object



24
25
26
# File 'app/models/attributable/attribute.rb', line 24

def from(record)
  record[name]
end

#integer?Boolean

Returns:

  • (Boolean)


46
47
48
# File 'app/models/attributable/attribute.rb', line 46

def integer?
  @options.fetch(:integer, false)
end

#kindObject



164
165
166
167
168
169
170
# File 'app/models/attributable/attribute.rb', line 164

def kind
  return FieldInfo::SELECTION if selection?
  return FieldInfo::BOOLEAN if boolean?
  return FieldInfo::NUMERIC if integer? || float?

  FieldInfo::TEXT
end

#maximumObject



70
71
72
# File 'app/models/attributable/attribute.rb', line 70

def maximum
  @options.fetch(:maximum, nil)
end

#minimumObject



66
67
68
# File 'app/models/attributable/attribute.rb', line 66

def minimum
  @options.fetch(:minimum, 0)
end

#optional?Boolean

Returns:

  • (Boolean)


42
43
44
# File 'app/models/attributable/attribute.rb', line 42

def optional?
  !required?
end

#required?Boolean

Returns:

  • (Boolean)


38
39
40
# File 'app/models/attributable/attribute.rb', line 38

def required?
  @required
end

#selection?Boolean

Returns:

  • (Boolean)


62
63
64
# File 'app/models/attributable/attribute.rb', line 62

def selection?
  fixed_selection? || @options.key?(:selection)
end

#selection_from_metadata(validator_source) ⇒ Object



172
173
174
175
176
# File 'app/models/attributable/attribute.rb', line 172

def (validator_source)
  return nil if validator_source.blank?

  validator_source.validator_for(name).valid_options.to_a if validator?
end

#selection_options(validator_source) ⇒ Object



178
179
180
# File 'app/models/attributable/attribute.rb', line 178

def selection_options(validator_source)
  selection_values || (validator_source) || []
end

#selection_valuesObject



74
75
76
# File 'app/models/attributable/attribute.rb', line 74

def selection_values
  @options[:in]
end

#to_field_info(validator_source = nil) ⇒ Object

rubocop:disable Metrics/AbcSize



182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'app/models/attributable/attribute.rb', line 182

def to_field_info(validator_source = nil) # rubocop:disable Metrics/AbcSize
  options = {
    # TODO[xxx]: currently only working for metadata, the only place attributes are used
    display_name: display_name,
    key: assignable_attribute_name,
    default_value: find_default(validator_source),
    kind: kind,
    required: required?
  }

  options.update(selection: selection_options(validator_source)) if selection?
  options_hash = { min: minimum }

  options_hash.update(max: maximum)
  options_hash.update(step: 1) if integer?
  options_hash.update(step: 0.1) if float?

  options.update(**options_hash) if options_hash.present?
  FieldInfo.new(options)
end

#valid_formatObject



78
79
80
# File 'app/models/attributable/attribute.rb', line 78

def valid_format
  @options[:with]
end

#valid_format?Boolean

Returns:

  • (Boolean)


82
83
84
# File 'app/models/attributable/attribute.rb', line 82

def valid_format?
  valid_format
end

#validator?Boolean

Returns:

  • (Boolean)


34
35
36
# File 'app/models/attributable/attribute.rb', line 34

def validator?
  @validator
end