Class: FlexColumns::Definition::FieldDefinition

Inherits:
Object
  • Object
show all
Defined in:
lib/flex_columns/definition/field_definition.rb

Overview

A FieldDefinition represents, well, the definition of a field. One of these objects is created for each field you declare in a flex column. It keeps track of (at minimum) the name of the field; it also is responsible for implementing our “shorthand types” system (where declaring your field as :integer adds a validation that requires it to be an integer, for example).

Perhaps most significantly, a FieldDefinition object is responsible for creating the appropriate methods on the flex-column class and on the model class, and also for adding methods to classes that have invoked IncludeFlexColumns#include_flex_columns_from.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(flex_column_class, field_name, additional_arguments, options) ⇒ FieldDefinition

Creates a new instance. flex_column_class is the Class we created for this flex column – i.e., a class that inherits from FlexColumns::Contents::FlexColumnContentsBase. field_name is the name of the field. additional_arguments is an Array containing any additional arguments that were passed – right now, that can only be the type of the field (e.g., :integer, etc.). options is any options that were passed; this can contain:

:visibility, :null, :enum, :limit, :json

:visibility

Can be set to :public or :private; will override the default visibility for fields specified on the flex-column class itself.

:null

If present and set to false, a validation requiring data in this field will be added.

:enum

If present, must be mapped to an Array; a validation requiring the data to be one of the elements of the array will be added.

:limit

If present, must be mapped to an integer; a validation requiring the length of the data to be at most this value will be added.

:json

If present, must be mapped to a String or Symbol; this specifies that the field should be stored under the given key in the JSON, rather than its field name.



44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/flex_columns/definition/field_definition.rb', line 44

def initialize(flex_column_class, field_name, additional_arguments, options)
  unless flex_column_class.respond_to?(:is_flex_column_class?) && flex_column_class.is_flex_column_class?
    raise ArgumentError, "You can't define a flex-column field against #{flex_column_class.inspect}; that isn't a flex-column class."
  end

  validate_options(options)
  @flex_column_class = flex_column_class
  @field_name = self.class.normalize_name(field_name)
  @options = options
  @field_type = nil

  apply_additional_arguments(additional_arguments)
  apply_validations!
end

Instance Attribute Details

#field_nameObject (readonly)

Returns the value of attribute field_name.



26
27
28
# File 'lib/flex_columns/definition/field_definition.rb', line 26

def field_name
  @field_name
end

Class Method Details

.normalize_name(name) ⇒ Object

Given the name of a field, returns a normalized version of that name – so we can compare using == without worrying about String vs. Symbol and so on.



15
16
17
18
19
20
21
22
23
# File 'lib/flex_columns/definition/field_definition.rb', line 15

def normalize_name(name)
  case name
  when Symbol then name
  when String then
    raise "You must supply a non-empty String, not: #{name.inspect}" if name.strip.length == 0
    name.strip.downcase.to_sym
  else raise ArgumentError, "You must supply a name, not: #{name.inspect}"
  end
end

Instance Method Details

#add_methods_to_flex_column_class!(dynamic_methods_module) ⇒ Object

Defines appropriate accessor methods for this field on the given DynamicMethodsModule, which should be included in the flex-column class (not the model class). These are quite simple; they always exist (and should overwrite any existing methods, since we’re last-definition-wins). We just need to make them work, and make them private, if needed.



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/flex_columns/definition/field_definition.rb', line 68

def add_methods_to_flex_column_class!(dynamic_methods_module)
  fn = field_name

  dynamic_methods_module.define_method(fn) do
    self[fn]
  end

  dynamic_methods_module.define_method("#{fn}=") do |x|
    self[fn] = x
  end

  if private?
    dynamic_methods_module.private(fn)
    dynamic_methods_module.private("#{fn}=")
  end
end

#add_methods_to_included_class!(dynamic_methods_module, association_name, target_class, options) ⇒ Object

Defines appropriate accessor methods for this field on the given DynamicMethodsModule, which should be included in some target model class that has said include_flex_columns_from on the clsas containing this field. association_name is the name of the association method name that, when called on the class that includes the DynamicMethodsModule, will return an instance of the model class in which this field lives. target_class is the target class we’re defining methods on, so that we can check if we’re going to conflict with some method there that we should not clobber.

options can contain:

:visibility

If :private, then methods will be defined as private.

:prefix

If specified, then methods will be prefixed with the given prefix. This will override the prefix specified on the flex-column class, if any.



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
# File 'lib/flex_columns/definition/field_definition.rb', line 129

def add_methods_to_included_class!(dynamic_methods_module, association_name, target_class, options)
  return if (! flex_column_class.delegation_type)

  prefix = if options.has_key?(:prefix) then options[:prefix] else flex_column_class.delegation_prefix end
  is_private = private? || (flex_column_class.delegation_type == :private) || (options[:visibility] == :private)

  if is_private && options[:visibility] == :public
    raise ArgumentError, %{You asked for public visibility for methods included from association #{association_name.inspect},
  but the flex column #{flex_column_class.model_class.name}.#{flex_column_class.column_name} has its methods
  defined with private visibility (either in the flex column itself, or at the model level).

  You can't have methods be 'more public' in the included class than they are in the class
  they're being included from.}
  end

  mn = field_name
  mn = "#{prefix}_#{mn}".to_sym if prefix

  fcc = flex_column_class
  fn = field_name

  if target_class._flex_columns_safe_to_define_method?(mn)
    dynamic_methods_module.define_method(mn) do
      associated_object = send(association_name) || send("build_#{association_name}")
      flex_column_object = associated_object.send(fcc.column_name)
      flex_column_object.send(fn)
    end

    dynamic_methods_module.define_method("#{mn}=") do |x|
      associated_object = send(association_name) || send("build_#{association_name}")
      flex_column_object = associated_object.send(fcc.column_name)
      flex_column_object.send("#{fn}=", x)
    end

    if is_private
      dynamic_methods_module.private(mn)
      dynamic_methods_module.private("#{mn}=")
    end
  end
end

#add_methods_to_model_class!(dynamic_methods_module, model_class) ⇒ Object

Defines appropriate accessor methods for this field on the given DynamicMethodsModule, which should be included in the model class. We also pass model_class so that we can check to see if we’re going to conflict with one of its columns first, or other methods we shouldn’t clobber.

We need to respect visibility (public or private) of methods, and the delegation prefix assigned at the flex-column level.



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
# File 'lib/flex_columns/definition/field_definition.rb', line 91

def add_methods_to_model_class!(dynamic_methods_module, model_class)
  return if (! flex_column_class.delegation_type) # :delegate => false on the flex column means don't delegate from the model at all

  mn = field_name
  mn = "#{flex_column_class.delegation_prefix}_#{mn}".to_sym if flex_column_class.delegation_prefix

  if model_class._flex_columns_safe_to_define_method?(mn)
    fcc = flex_column_class
    fn = field_name

    should_be_private = (private? || flex_column_class.delegation_type == :private)

    dynamic_methods_module.define_method(mn) do
      flex_instance = fcc.object_for(self)
      flex_instance[fn]
    end
    dynamic_methods_module.private(mn) if should_be_private

    dynamic_methods_module.define_method("#{mn}=") do |x|
      flex_instance = fcc.object_for(self)
      flex_instance[fn] = x
    end
    dynamic_methods_module.private("#{mn}=") if should_be_private
  end
end

#json_storage_nameObject

Returns the key under which the field’s value should be stored in the JSON.



60
61
62
# File 'lib/flex_columns/definition/field_definition.rb', line 60

def json_storage_name
  (options[:json] || field_name).to_s.strip.downcase.to_sym
end