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


126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'app/models/attributable/attribute.rb', line 126

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)

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

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

#configure(model) ⇒ Object

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


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

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?
        required.validates name, numericality: { only_integer: integer?, greater_than_or_equal_to: minimum }
      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
# File 'app/models/attributable/attribute.rb', line 28

def default_from(origin = nil)
  return nil if origin.nil?
  return origin.validator_for(name).default if validator?
end

#display_nameObject


141
142
143
# File 'app/models/attributable/attribute.rb', line 141

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

153
154
155
# File 'app/models/attributable/attribute.rb', line 153

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

#fixed_selection?Boolean

Returns:

  • (Boolean)

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

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

#float?Boolean

Returns:

  • (Boolean)

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

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)

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

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

#kindObject


157
158
159
160
161
162
163
# File 'app/models/attributable/attribute.rb', line 157

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

  FieldInfo::TEXT
end

#minimumObject


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

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

#optional?Boolean

Returns:

  • (Boolean)

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

def optional?
  !required?
end

#required?Boolean

Returns:

  • (Boolean)

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

def required?
  @required
end

#selection?Boolean

Returns:

  • (Boolean)

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

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

#selection_from_metadata(validator_source) ⇒ Object


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

def (validator_source)
  return nil if validator_source.blank?
  return validator_source.validator_for(name).valid_options.to_a if validator?
end

#selection_options(validator_source) ⇒ Object


170
171
172
# File 'app/models/attributable/attribute.rb', line 170

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

#selection_valuesObject


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

def selection_values
  @options[:in]
end

#to_field_info(validator_source = nil) ⇒ Object


174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'app/models/attributable/attribute.rb', line 174

def to_field_info(validator_source = nil)
  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.update(step: 1, min: minimum) if integer?
  options.update(step: 0.1, min: 0) if float?
  FieldInfo.new(options)
end

#valid_formatObject


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

def valid_format
  @options[:with]
end

#valid_format?Boolean

Returns:

  • (Boolean)

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

def valid_format?
  valid_format
end

#validator?Boolean

Returns:

  • (Boolean)

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

def validator?
  @validator
end