deco_lite 0.2.2 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6e8cab024f00d573f418f6da7931159d211392b7e67994b6e5d736fa87e6858d
4
- data.tar.gz: 75d52762639bda61821152dd7ee071c0c29571de02835d9ca2309dc3f645a380
3
+ metadata.gz: 74286df21784817e45f4d615081bfeac44d90f13d8200bc8f066107e8d0f773f
4
+ data.tar.gz: 57dcc52147494eb3caf59159cbb21bc23bb6788d0b6315ebc1ddd8cf1868c779
5
5
  SHA512:
6
- metadata.gz: 073e6f539cae726483f065c873ab6b21c11a0f29cb497998c66ad85c9f1775c4c973f675c4138c07d2b7158455cf819555ca3b550ea38c7e6b3811dfb2bf9780
7
- data.tar.gz: f69bc6b7d1052172c9603c1b2a0b49cd13ee863217c2303b49dbafe86dd68e0b5def92184948b80efd8760b6735878ff0ddad2ecdbb5b01135c7cbc3241b4ee5
6
+ metadata.gz: b79af6a65df899227821e4a02bf17587f1f423042a4280a54525c8469c1c3344f3dd2c3b16bd0de477c711d27ed04527295ca37e811c1c829037379509971ac6
7
+ data.tar.gz: 7d1b5dea80d0af672713d18675166207e2d62ab51aff84045411d02344529a32de513df76d394e3769dfb14a69d0742f18e850398dc9b4cc6bac10eb5c72ecec
data/CHANGELOG.md CHANGED
@@ -1,13 +1,32 @@
1
+ ### 0.2.5
2
+ * Changes
3
+ * Remove init of `@field_names = []` in `Model#initialize` as unnecessary - FieldNamesPersistable takes care of this.
4
+ * Bug fixes
5
+ * Fix but that does not take into account option :namespace when determining whether or not a field name conflicts with an existing attribute (already exists).
6
+
7
+ ### 0.2.4
8
+ * Changes
9
+ * Change DecoLite::Model#load to #load! as it alters the object, give deprecation warning when calling #load.
10
+ * FieldConflictable now expliticly prohibits loading fields that conflict with attributes that are native to the receiver. In other words, you cannot load fields with names like :to_s, :tap, :hash, etc.
11
+ * FieldCreatable now creates attr_accessors on the instance using #define_singleton_method, not at the class level (i.e. self.class.attr_accessor) (see bug fixes).
12
+ * Bug fixes
13
+ * Fix bug that used self.class.attr_accessor in DecoLite::FieldCreatable to create attributes, which forced every object of that class subsequently created have the accessors created which caused field name conflicts across DecoLite::Model objects.
14
+
15
+ ### 0.2.3
16
+ * Bug fixes
17
+ * Fix bug that added duplcate field names to Model#field_names.
18
+
1
19
  ### 0.2.2
2
- * Fix bug requiring support codez in lib/deco_lite.rb.
20
+ * Bug fixes
21
+ * Fix bug requiring support codez in lib/deco_lite.rb.
3
22
 
4
23
  ### 0.2.1
5
- * changes
24
+ * Changes
6
25
  * Add mad_flatter gem runtime dependency.
7
26
  * Refactor to let mad_flatter handle the Hash flattening.
8
27
 
9
28
  ### 0.1.1
10
- * changes
29
+ * Changes
11
30
  * Update gems and especially rake gem version to squash CVE-2020-8130, see https://github.com/advisories/GHSA-jppv-gw3r-w3q8.
12
31
  * Fix rubocop violations.
13
32
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- deco_lite (0.2.2)
4
+ deco_lite (0.2.5)
5
5
  activemodel (~> 7.0, >= 7.0.3.1)
6
6
  activesupport (~> 7.0, >= 7.0.3.1)
7
7
  immutable_struct_ex (~> 0.2.0)
data/README.md CHANGED
@@ -170,6 +170,29 @@ model.wife_info_age #=> 20
170
170
  model.wife_info_address #=> 1 street, boonton, nj 07005
171
171
  ```
172
172
 
173
+ ### Manually Defining Attributes
174
+
175
+ Manually defining attributes on your subclass is possible; however, you
176
+ must add your attr_reader name to the `DecoLite::Model@field_names` array, or an error will be reaised _if_ there are any conflicting field names being loaded
177
+ using `DecoLite::Model#load!`, regardless of setting the `{ fields: :merge }`
178
+ option. This is because DecoLite assumes any existing attributes not added to
179
+ the model via `load!`to be native to the object created, and therefore will not
180
+ allow you to create attr_accessors for existing attributes, as this can potentially be dangerous.
181
+
182
+ To avoid errors when manually defining attributes on the model that could potentially be in conflict with fields loaded using `DecoLite::Model#load!`, do the following:
183
+
184
+ ```ruby
185
+ class JustBecauseYouCanDoesntMeanYouShould < DecoLite::Model
186
+ attr_accessor :existing_field
187
+
188
+ def initialize(options: {})
189
+ super
190
+
191
+ @field_names = %i(existing_field)
192
+ end
193
+ end
194
+ ```
195
+
173
196
  ## Development
174
197
 
175
198
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -1,23 +1,50 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'field_name_namespaceable'
3
4
  require_relative 'fields_optionable'
4
5
 
5
6
  module DecoLite
6
7
  # Defines methods to to manage fields that conflict with
7
8
  # existing model attributes.
8
9
  module FieldConflictable
10
+ include FieldNameNamespaceable
9
11
  include FieldsOptionable
10
12
 
11
13
  def validate_field_conflicts!(field_name:, options:)
12
- return unless options.strict? && field_conflict?(field_name: field_name)
14
+ return unless field_conflict?(field_name: field_name, options: options)
13
15
 
14
- raise "Field '#{field_name}' conflicts with existing attribute; " \
15
- 'this will raise an error when running in strict mode: ' \
16
- "options: { #{OPTION_FIELDS}: :#{OPTION_FIELDS_STRICT} }."
16
+ field_name = field_name_or_field_name_with_namespace field_name: field_name, options: options
17
+
18
+ raise "Field :#{field_name} conflicts with existing method(s) " \
19
+ ":#{field_name} and/or :#{field_name}=; " \
20
+ 'this will raise an error when loading using strict mode ' \
21
+ "(i.e. options: { #{OPTION_FIELDS}: :#{OPTION_FIELDS_STRICT} }) " \
22
+ 'or if the method(s) are native to the object (e.g :to_s, :==, etc.).'
23
+ end
24
+
25
+ # This method returns true
26
+ def field_conflict?(field_name:, options:)
27
+ # If field_name was already added using Model#load, there is only a
28
+ # conflict if options.strict? is true.
29
+ return options.strict? if field_names_include?(field_name: field_name, options: options)
30
+
31
+ # If we get here, we know that :field_name does not exist as an
32
+ # attribute on the model. If the attribute already exists on the
33
+ # model, this is a conflict because we cannot override an attribute
34
+ # that already exists on the model
35
+ attr_accessor_exist?(field_name: field_name, options: options)
17
36
  end
18
37
 
19
- def field_conflict?(field_name:)
20
- respond_to? field_name
38
+ def field_names_include?(field_name:, options:)
39
+ field_name = field_name_or_field_name_with_namespace field_name: field_name, options: options
40
+
41
+ field_names.include? field_name
42
+ end
43
+
44
+ def attr_accessor_exist?(field_name:, options:)
45
+ field_name = field_name_or_field_name_with_namespace field_name: field_name, options: options
46
+
47
+ respond_to?(field_name) || respond_to?(:"#{field_name}=")
21
48
  end
22
49
  end
23
50
  end
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'field_conflictable'
4
+ require_relative 'field_validatable'
4
5
 
5
6
  module DecoLite
6
7
  # Takes an array of symbols and creates attr_accessors.
7
8
  module FieldCreatable
8
9
  include FieldConflictable
10
+ include FieldValidatable
9
11
 
10
12
  def create_field_accessors(field_names:, options:)
11
13
  return if field_names.blank?
@@ -16,9 +18,30 @@ module DecoLite
16
18
  end
17
19
 
18
20
  def create_field_accessor(field_name:, options:)
21
+ validate_field_name!(field_name: field_name, options: options)
19
22
  validate_field_conflicts!(field_name: field_name, options: options)
20
23
 
21
- self.class.attr_accessor(field_name) if field_name.present?
24
+ # If we want to set a class-level attr_accessor
25
+ # self.class.attr_accessor(field_name) if field_name.present?
26
+
27
+ create_field_getter field_name: field_name, options: options
28
+ create_field_setter field_name: field_name, options: options
29
+ end
30
+
31
+ private
32
+
33
+ # rubocop:disable Lint/UnusedMethodArgument
34
+ def create_field_getter(field_name:, options:)
35
+ define_singleton_method(field_name) do
36
+ instance_variable_get "@#{field_name}"
37
+ end
38
+ end
39
+
40
+ def create_field_setter(field_name:, options:)
41
+ define_singleton_method("#{field_name}=") do |value|
42
+ instance_variable_set "@#{field_name}", value
43
+ end
22
44
  end
45
+ # rubocop:enable Lint/UnusedMethodArgument
23
46
  end
24
47
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DecoLite
4
+ # Defines methods to transform a field name into a field name
5
+ # with a namespace.
6
+ module FieldNameNamespaceable
7
+ def field_name_or_field_name_with_namespace(field_name:, options:)
8
+ return field_name unless options.namespace?
9
+
10
+ field_name_with_namespace(field_name: field_name, namespace: options.namespace)
11
+ end
12
+
13
+ def field_name_with_namespace(field_name:, namespace:)
14
+ "#{namespace}_#{field_name}"
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DecoLite
4
+ # Takes an array of symbols and creates attr_accessors.
5
+ module FieldNamesPersistable
6
+ def field_names
7
+ @field_names ||= instance_variable_get(:@field_names) || []
8
+ end
9
+
10
+ private
11
+
12
+ attr_writer :field_names
13
+ end
14
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DecoLite
4
+ # Defines methods validate field (attribute) names.
5
+ module FieldValidatable
6
+ FIELD_NAME_REGEX = %r{\A(?:[a-z_]\w*[?!=]?|\[\]=?|<<|>>|\*\*|[!~+*/%&^|-]|[<>]=?|<=>|={2,3}|![=~]|=~)\z}i
7
+
8
+ module_function
9
+
10
+ # rubocop:disable Lint/UnusedMethodArgument
11
+ def validate_field_name!(field_name:, options: nil)
12
+ raise "field_name '#{field_name}' is not a valid field name." unless FIELD_NAME_REGEX.match?(field_name)
13
+ end
14
+ # rubocop:enable Lint/UnusedMethodArgument
15
+ end
16
+ end
@@ -19,7 +19,7 @@ module DecoLite
19
19
  load_service.execute(hash: hash, options: load_service_options).tap do |h|
20
20
  h.each_pair do |field_name, value|
21
21
  create_field_accessor field_name: field_name, options: deco_lite_options
22
- field_names << field_name
22
+ field_names << field_name unless field_names.include? field_name
23
23
  set_field_value(field_name: field_name, value: value, options: deco_lite_options)
24
24
  end
25
25
  end
@@ -3,6 +3,7 @@
3
3
  require 'active_model'
4
4
  require_relative 'field_creatable'
5
5
  require_relative 'field_requireable'
6
+ require_relative 'field_names_persistable'
6
7
  require_relative 'hash_loadable'
7
8
  require_relative 'hashable'
8
9
  require_relative 'model_nameable'
@@ -14,6 +15,7 @@ module DecoLite
14
15
  class Model
15
16
  include ActiveModel::Model
16
17
  include FieldCreatable
18
+ include FieldNamesPersistable
17
19
  include FieldRequireable
18
20
  include HashLoadable
19
21
  include Hashable
@@ -23,7 +25,6 @@ module DecoLite
23
25
  validate :validate_required_fields
24
26
 
25
27
  def initialize(options: {})
26
- @field_names = []
27
28
  # Accept whatever options are sent, but make sure
28
29
  # we have defaults set up. #options_with_defaults
29
30
  # will merge options into OptionsDefaultable::DEFAULT_OPTIONS
@@ -32,7 +33,7 @@ module DecoLite
32
33
  self.options = Options.with_defaults options
33
34
  end
34
35
 
35
- def load(hash:, options: {})
36
+ def load!(hash:, options: {})
36
37
  # Merge options into the default options passed through the
37
38
  # constructor; these will override any options passed in when
38
39
  # this object was created, allowing us to retain any defaut
@@ -45,10 +46,11 @@ module DecoLite
45
46
  self
46
47
  end
47
48
 
48
- attr_reader :field_names
49
-
50
- private
49
+ def load(hash:, options: {})
50
+ puts 'WARNING: DecoLite::Model#load will be deprecated in a future release; ' \
51
+ 'use DecoLite::Model#load! instead!'
51
52
 
52
- attr_writer :field_names
53
+ load!(hash: hash, options: options)
54
+ end
53
55
  end
54
56
  end
@@ -35,7 +35,7 @@ module DecoLite
35
35
 
36
36
  raise ArgumentError,
37
37
  "option :fields value or type is invalid. #{OPTION_FIELDS_VALUES} (Symbol) " \
38
- "was expected, but '#{fields}' (#{fields.class}) was received."
38
+ "was expected, but '#{fields}' (#{fields.class}) was received."
39
39
  end
40
40
 
41
41
  def validate_option_namespace!(namespace:)
@@ -43,7 +43,7 @@ module DecoLite
43
43
  return if namespace.blank? || namespace.is_a?(Symbol)
44
44
 
45
45
  raise ArgumentError, 'option :namespace value or type is invalid. A Symbol was expected, ' \
46
- "but '#{namespace}' (#{namespace.class}) was received."
46
+ "but '#{namespace}' (#{namespace.class}) was received."
47
47
  end
48
48
  end
49
49
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  # Defines the version of this gem.
4
4
  module DecoLite
5
- VERSION = '0.2.2'
5
+ VERSION = '0.2.5'
6
6
  end
data/lib/deco_lite.rb CHANGED
@@ -3,6 +3,8 @@
3
3
  require_relative 'deco_lite/field_assignable'
4
4
  require_relative 'deco_lite/field_conflictable'
5
5
  require_relative 'deco_lite/field_creatable'
6
+ require_relative 'deco_lite/field_name_namespaceable'
7
+ require_relative 'deco_lite/field_names_persistable'
6
8
  require_relative 'deco_lite/field_requireable'
7
9
  require_relative 'deco_lite/field_retrievable'
8
10
  require_relative 'deco_lite/fields_optionable'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: deco_lite
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gene M. Angelo, Jr.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-08-17 00:00:00.000000000 Z
11
+ date: 2022-08-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -261,8 +261,11 @@ files:
261
261
  - lib/deco_lite/field_assignable.rb
262
262
  - lib/deco_lite/field_conflictable.rb
263
263
  - lib/deco_lite/field_creatable.rb
264
+ - lib/deco_lite/field_name_namespaceable.rb
265
+ - lib/deco_lite/field_names_persistable.rb
264
266
  - lib/deco_lite/field_requireable.rb
265
267
  - lib/deco_lite/field_retrievable.rb
268
+ - lib/deco_lite/field_validatable.rb
266
269
  - lib/deco_lite/fields_optionable.rb
267
270
  - lib/deco_lite/hash_loadable.rb
268
271
  - lib/deco_lite/hashable.rb