deco_lite 0.2.2 → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
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