deco_lite 0.2.3 → 0.2.4

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: f9cc4e271606535b0bb5daf708487f63f68c193709e4a2e2efa4e7c7fee60d49
4
- data.tar.gz: d34b33b97c7b1554c35dc064e2a52908d9e007ee90608a876cbfac944d6d79e8
3
+ metadata.gz: 2d360b0607d72711264a19f807ad4367237f0e5e69efb385728ccbda66e72e05
4
+ data.tar.gz: f90e61477cc9dfa1ba8d19070c1490fcccfce413533436ed93b532d4933fad08
5
5
  SHA512:
6
- metadata.gz: 785614c89432bb0dd43ec4fa24a3760205c606d5a25a16e7392d569a57b2a8509c307adbe53f622a80a509cbe88b17535bc0bb484071415f51252704a096782b
7
- data.tar.gz: 28c95fb72bc380e4a568680375cbdaa03105aa0d7c05cd7f910c61bb910f0aa9a6a5056a5ed3254e11df4755b6c88a7ac6bfe932f3bfded7ebcd4003c5513749
6
+ metadata.gz: 87d78e241cd8aada8c09c8adbcb20cdb5997e96ff4f17cc8dd3a494e973fd10f13fe73927edb604e2125209119bd881522c79995753768fc0f64d049632d1751
7
+ data.tar.gz: d4ae03338d636d82d6a67996cea9e2143a07b0319a9ad6f3a1f1e4a508b2f9a4262ec5cb7e68c8cb2b7d29a8f0e046413ad7f9470f4a20c6fa470c20d9e3c907
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ### 0.2.4
2
+ * Changes
3
+ * Change DecoLite::Model#load to #load! as it alters the object, give deprecation warning when calling #load.
4
+ * 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.
5
+ * 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).
6
+ * bug fixes
7
+ * 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.
8
+
1
9
  ### 0.2.3
2
10
  * Fix bug that added duplcate field names to Model#field_names.
3
11
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- deco_lite (0.2.3)
4
+ deco_lite (0.2.4)
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.
@@ -9,15 +9,36 @@ module DecoLite
9
9
  include FieldsOptionable
10
10
 
11
11
  def validate_field_conflicts!(field_name:, options:)
12
- return unless options.strict? && field_conflict?(field_name: field_name)
12
+ return unless field_conflict?(field_name: field_name, options: options)
13
13
 
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} }."
14
+ raise "Field :#{field_name} conflicts with existing method(s) " \
15
+ ":#{field_name} and/or :#{field_name}=; " \
16
+ 'this will raise an error when loading using strict mode ' \
17
+ "(i.e. options: { #{OPTION_FIELDS}: :#{OPTION_FIELDS_STRICT} }) " \
18
+ 'or if the method(s) are native to the object (e.g :to_s, :==, etc.).'
17
19
  end
18
20
 
19
- def field_conflict?(field_name:)
20
- respond_to? field_name
21
+ # This method returns true
22
+ def field_conflict?(field_name:, options:)
23
+ # If field_name was already added using Model#load, there is only a
24
+ # conflict if options.strict? is true.
25
+ if field_names_include?(field_name: field_name)
26
+ return options.strict?
27
+ end
28
+
29
+ # If we get here, we know that :field_name does not exist as an
30
+ # attribute on the model. If the attribute already exists on the
31
+ # model, this is a conflict because we cannot override an attribute
32
+ # that already exists on the model
33
+ attr_accessor_exist?(field_name: field_name)
34
+ end
35
+
36
+ def field_names_include?(field_name:)
37
+ field_names.include? field_name
38
+ end
39
+
40
+ def attr_accessor_exist?(field_name:)
41
+ respond_to?(field_name) || respond_to?(:"#{field_name}=")
21
42
  end
22
43
  end
23
44
  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,28 @@ 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
+ def create_field_getter(field_name:, options:)
34
+ define_singleton_method(field_name) do
35
+ instance_variable_get "@#{field_name}"
36
+ end
37
+ end
38
+
39
+ def create_field_setter(field_name:, options:)
40
+ define_singleton_method("#{field_name}=") do |value|
41
+ instance_variable_set "@#{field_name}", value
42
+ end
22
43
  end
23
44
  end
24
45
  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 = /\A(?:[a-z_]\w*[?!=]?|\[\]=?|<<|>>|\*\*|[!~+\*\/%&^|-]|[<>]=?|<=>|={2,3}|![=~]|=~)\z/i.freeze
7
+
8
+ module_function
9
+
10
+ def validate_field_name!(field_name:, options: nil)
11
+ unless field_name =~ FIELD_NAME_REGEX
12
+ raise "field_name '#{field_name}' is not a valid field name."
13
+ end
14
+ end
15
+ end
16
+ 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
@@ -24,6 +26,7 @@ module DecoLite
24
26
 
25
27
  def initialize(options: {})
26
28
  @field_names = []
29
+
27
30
  # Accept whatever options are sent, but make sure
28
31
  # we have defaults set up. #options_with_defaults
29
32
  # will merge options into OptionsDefaultable::DEFAULT_OPTIONS
@@ -32,7 +35,7 @@ module DecoLite
32
35
  self.options = Options.with_defaults options
33
36
  end
34
37
 
35
- def load(hash:, options: {})
38
+ def load!(hash:, options: {})
36
39
  # Merge options into the default options passed through the
37
40
  # constructor; these will override any options passed in when
38
41
  # this object was created, allowing us to retain any defaut
@@ -45,10 +48,11 @@ module DecoLite
45
48
  self
46
49
  end
47
50
 
48
- attr_reader :field_names
49
-
50
- private
51
+ def load(hash:, options: {})
52
+ puts 'WARNING: DecoLite::Model#load will be deprecated in a future release;' \
53
+ ' use DecoLite::Model#load! instead!'
51
54
 
52
- attr_writer :field_names
55
+ load!(hash: hash, options: options)
56
+ end
53
57
  end
54
58
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  # Defines the version of this gem.
4
4
  module DecoLite
5
- VERSION = '0.2.3'
5
+ VERSION = '0.2.4'
6
6
  end
data/lib/deco_lite.rb CHANGED
@@ -3,6 +3,7 @@
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_names_persistable'
6
7
  require_relative 'deco_lite/field_requireable'
7
8
  require_relative 'deco_lite/field_retrievable'
8
9
  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.3
4
+ version: 0.2.4
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-18 00:00:00.000000000 Z
11
+ date: 2022-08-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -261,8 +261,10 @@ 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_names_persistable.rb
264
265
  - lib/deco_lite/field_requireable.rb
265
266
  - lib/deco_lite/field_retrievable.rb
267
+ - lib/deco_lite/field_validatable.rb
266
268
  - lib/deco_lite/fields_optionable.rb
267
269
  - lib/deco_lite/hash_loadable.rb
268
270
  - lib/deco_lite/hashable.rb