acts_as_hashable 1.0.3 → 1.2.0.pre.alpha

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
- SHA1:
3
- metadata.gz: aeda82e04843a1f805a0e056f3a1a988cf3fa624
4
- data.tar.gz: 129e4222c77d0527b9932d36761a354b7e237f3f
2
+ SHA256:
3
+ metadata.gz: efc9586f91e07aee23e59bc2f3fbe3d0d5c1f8e7586ee2a172dcfec3b3c36ca0
4
+ data.tar.gz: 1f1e8850279c8a82cbad3ed60d23415a1e1a012526ec6854113d2dc714e6e781
5
5
  SHA512:
6
- metadata.gz: 70e823f2d22dc978bdc537c6bf3f74163daa796db9035f6dd3afb49d30868974f475626d14d0e046d121428efb2acca4889771f03532bb73354c20391e23feb2
7
- data.tar.gz: c5520831734e879970e1abea57eead97a92794345d8d0ac6c1fc40990ba9e641fef2b983b16aa7e438d1e43fb24bac2751dd8b9d29f4fad7f2f553b29500766e
6
+ metadata.gz: 67c2547b85e3f13b68ad0f6085cf9bbb1e914fe99138a5f324cbd4e1c962dc2c8285f4fc65c2a67d2ac17ab4b0ac92542407311eca33cdf3082e8c457ab59de8
7
+ data.tar.gz: 52e1dfc7fe3ae98932ce7040e79961134ec8d783e7d909e98378f87f3cdf888622d5667a1c468471de03e5d7eaeddd43658aee421b25eeced0b1606f0894a07f
data/.gitignore CHANGED
@@ -1,2 +1,6 @@
1
1
  .DS_Store
2
2
  *.gem
3
+ /tmp
4
+ /coverage
5
+ /pkg
6
+ Gemfile.lock
@@ -1,4 +1,13 @@
1
- inherit_from: .rubocop_todo.yml
2
-
3
1
  Metrics/LineLength:
4
2
  Max: 100
3
+
4
+ Metrics/BlockLength:
5
+ ExcludedMethods: ['it', 'describe', 'context', 'let', 'specify']
6
+ Exclude:
7
+ - acts_as_hashable.gemspec
8
+
9
+ AllCops:
10
+ TargetRubyVersion: 2.5
11
+
12
+ Style/RescueModifier:
13
+ Enabled: false
@@ -1 +1 @@
1
- 2.3.7
1
+ 2.6.6
@@ -1,8 +1,24 @@
1
+ env:
2
+ global:
3
+ - CC_TEST_REPORTER_ID=830db02445429888fcdd7d1406dd82e9b32535e863ac54a538a162c0c4acd4d1
1
4
  language: ruby
2
5
  rvm:
3
- - 2.3.1
4
- - 2.3.7
6
+ # Build on the latest stable of all supported Rubies (https://www.ruby-lang.org/en/downloads/):
7
+ - 2.5.8
8
+ - 2.6.6
9
+ - 2.7.1
5
10
  cache: bundler
11
+ before_script:
12
+ - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
13
+ - chmod +x ./cc-test-reporter
14
+ - ./cc-test-reporter before-build
6
15
  script:
7
16
  - bundle exec rubocop
8
17
  - bundle exec rspec
18
+ after_script:
19
+ - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
20
+ addons:
21
+ # https://docs.travis-ci.com/user/uploading-artifacts/
22
+ artifacts:
23
+ paths:
24
+ - Gemfile.lock
@@ -0,0 +1,23 @@
1
+ # 1.2.0 (une 8th, 2020)
2
+
3
+ * Bumped minimum Ruby version to >= 2.5
4
+ * Do not pass in any constructor arguments unless we have at least one key.
5
+ * Add more detail to construction errors.
6
+
7
+ # 1.1.0 (May 3rd, 2019)
8
+
9
+ * Added acts_as_hashable_factory to dynamically create objects.
10
+
11
+ # 1.0.5 (February 5th, 2019)
12
+
13
+ * Fixed equality bug in array reject block.
14
+
15
+ # 1.0.4 (January 30th, 2019)
16
+
17
+ Maintenance Release
18
+
19
+ * Added bin/console script
20
+ * Added CHANGELOG
21
+ * Updated README with publication steps, additional testing steps
22
+ * Bumped minimum Ruby to 2.3.8
23
+ * Added Guard
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at oss@bluemarblepayroll.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ guard :rspec, cmd: 'DISABLE_SIMPLECOV=true bundle exec rspec --format=documentation' do
4
+ require 'guard/rspec/dsl'
5
+ dsl = Guard::RSpec::Dsl.new(self)
6
+
7
+ # RSpec files
8
+ rspec = dsl.rspec
9
+ watch(rspec.spec_helper) { rspec.spec_dir }
10
+ watch(rspec.spec_support) { rspec.spec_dir }
11
+ watch(rspec.spec_files)
12
+
13
+ # Ruby files
14
+ ruby = dsl.ruby
15
+ dsl.watch_spec_files_for(ruby.lib_files)
16
+ end
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Acts as Hashable
2
2
 
3
- [![Build Status](https://travis-ci.org/bluemarblepayroll/acts_as_hashable.svg?branch=master)](https://travis-ci.org/bluemarblepayroll/acts_as_hashable)
3
+ [![Gem Version](https://badge.fury.io/rb/acts_as_hashable.svg)](https://badge.fury.io/rb/acts_as_hashable) [![Build Status](https://travis-ci.org/bluemarblepayroll/acts_as_hashable.svg?branch=master)](https://travis-ci.org/bluemarblepayroll/acts_as_hashable) [![Maintainability](https://api.codeclimate.com/v1/badges/647dac37b9a8177f3d84/maintainability)](https://codeclimate.com/github/bluemarblepayroll/acts_as_hashable/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/647dac37b9a8177f3d84/test_coverage)](https://codeclimate.com/github/bluemarblepayroll/acts_as_hashable/test_coverage) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
4
 
5
- This is a small library that helps increase the pliability of object constructor signatures.
5
+ This is a small library that helps increase the pliability of object constructor signatures.
6
6
  Instead of instantiating via the constructor, this library can install helper factory
7
7
  class-level methods that you can use with hashes:
8
8
 
@@ -10,7 +10,7 @@ class-level methods that you can use with hashes:
10
10
  * array(): create an array of instances of a class
11
11
 
12
12
  One caveat to this library is that it is not meant to be a complete modeling solution but rather
13
- just a tool that helps model a domain or complete a modeling framework.
13
+ just a tool that helps model a domain or complete a modeling framework.
14
14
  The main missing element to this library is that you still have to manage and implement constructors.
15
15
  The two class-level factory methods are just meant to create a syntactic hash-like interface.
16
16
 
@@ -30,6 +30,8 @@ bundle add acts_as_hashable
30
30
 
31
31
  ## Examples
32
32
 
33
+ ### Utilizing Classes on Hashabled Classes
34
+
33
35
  Consider the following example:
34
36
 
35
37
  ````
@@ -125,6 +127,76 @@ class Pet
125
127
  end
126
128
  ````
127
129
 
130
+ ### Dynamic Factory Building
131
+
132
+ More complex relationships may contain objects with disparate types. In this case we can use the included factory
133
+ pattern to help us build these. Based on our examples above:
134
+
135
+ ```ruby
136
+ class ExampleFactory
137
+ acts_as_hashable_factory
138
+
139
+ register 'Pet', Pet
140
+
141
+ register 'HeadOfHousehold', HeadOfHousehold
142
+ end
143
+ ```
144
+
145
+ Now we can dynamically build these using:
146
+
147
+ ```ruby
148
+ objects = [
149
+ {
150
+ type: 'Pet',
151
+ name: 'Doug the dog',
152
+ toy: { squishy: true }
153
+ },
154
+ {
155
+ type: 'HeadOfHousehold',
156
+ person: {
157
+ name: 'Matt',
158
+ age: 109
159
+ },
160
+ partner: {
161
+ name: 'Katie',
162
+ age: 110
163
+ }
164
+ }
165
+ ]
166
+
167
+ hydrated_objects = ExampleFactory.array(objects)
168
+ ```
169
+
170
+ If the type key does not happen to be `type` then you can explicitly set this as:
171
+
172
+ ```ruby
173
+ class ExampleFactory
174
+ acts_as_hashable_factory
175
+
176
+ type_key 'object_type'
177
+
178
+ register 'Pet', Pet
179
+
180
+ register 'HeadOfHousehold', HeadOfHousehold
181
+ end
182
+ ```
183
+
184
+ You can also choose to pass in a proc/lambda instead of a class constant:
185
+
186
+ ```ruby
187
+ class ExampleFactory
188
+ acts_as_hashable_factory
189
+
190
+ type_key 'object_type'
191
+
192
+ register 'Pet', Pet
193
+
194
+ register 'HeadOfHousehold', ->(_key) { HeadOfHousehold }
195
+ end
196
+ ```
197
+
198
+ In case you need full control of the registry you can also choose to simply override the class-level `registry` method which will simply return a hash of keys (names) and values (class constants).
199
+
128
200
  ## Contributing
129
201
 
130
202
  ### Development Environment Configuration
@@ -142,9 +214,38 @@ Basic steps to take to get this repository compiling:
142
214
  To execute the test suite run:
143
215
 
144
216
  ````
145
- rspec spec --format documentation
217
+ bundle exec rspec spec --format documentation
218
+ ````
219
+
220
+ Alternatively, you can have Guard watch for changes:
221
+
222
+ ````
223
+ bundle exec guard
224
+ ````
225
+
226
+ Also, do not forget to run Rubocop:
227
+
228
+ ````
229
+ bundle exec rubocop
146
230
  ````
147
231
 
232
+ ### Publishing
233
+
234
+ Note: ensure you have proper authorization before trying to publish new versions.
235
+
236
+ After code changes have successfully gone through the Pull Request review process then the following steps should be followed for publishing new versions:
237
+
238
+ 1. Merge Pull Request into master
239
+ 2. Update ```lib/acts_as_hashable/version.rb``` using [semantic versioning](https://semver.org/)
240
+ 3. Install dependencies: ```bundle```
241
+ 4. Update ```CHANGELOG.md``` with release notes
242
+ 5. Commit & push master to remote and ensure CI builds master successfully
243
+ 6. Run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
244
+
245
+ ## Code of Conduct
246
+
247
+ Everyone interacting in this codebase, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/bluemarblepayroll/acts_as_hashable/blob/master/CODE_OF_CONDUCT.md).
248
+
148
249
  ## License
149
250
 
150
251
  This project is MIT Licensed.
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -14,15 +14,30 @@ Gem::Specification.new do |s|
14
14
  DESCRIPTION
15
15
 
16
16
  s.authors = ['Matthew Ruggio']
17
+ s.bindir = 'exe'
17
18
  s.email = ['mruggio@bluemarblepayroll.com']
18
- s.files = `git ls-files`.split("\n")
19
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
20
- s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
19
+ s.executables = []
20
+ s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
21
21
  s.homepage = 'https://github.com/bluemarblepayroll/acts_as_hashable'
22
22
  s.license = 'MIT'
23
+ s.metadata = {
24
+ 'bug_tracker_uri' => 'https://github.com/bluemarblepayroll/acts_as_hashable/issues',
25
+ 'changelog_uri' => 'https://github.com/bluemarblepayroll/acts_as_hashable/blob/master/CHANGELOG.md',
26
+ 'documentation_uri' => 'https://www.rubydoc.info/gems/acts_as_hashable',
27
+ 'homepage_uri' => s.homepage,
28
+ 'source_code_uri' => s.homepage
29
+ }
23
30
 
24
- s.required_ruby_version = '>= 2.3.1'
31
+ s.required_ruby_version = '>= 2.5'
25
32
 
33
+ s.add_dependency('caution', '~>1')
34
+
35
+ s.add_development_dependency('guard-rspec', '~>4.7')
36
+ s.add_development_dependency('pry')
37
+ s.add_development_dependency('pry-byebug', '~> 3')
38
+ s.add_development_dependency('rake', '~> 13.0')
26
39
  s.add_development_dependency('rspec')
27
- s.add_development_dependency('rubocop', '~> 0.59.2')
40
+ s.add_development_dependency('rubocop', '~> 0.63.1')
41
+ s.add_development_dependency('simplecov', '~>0.16.1')
42
+ s.add_development_dependency('simplecov-console', '~>0.4.2')
28
43
  end
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'acts_as_hashable'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ require 'pry'
11
+ Pry.start
File without changes
@@ -7,4 +7,27 @@
7
7
  # LICENSE file in the root directory of this source tree.
8
8
  #
9
9
 
10
- require_relative 'acts_as_hashable/acts_as_hashable'
10
+ require 'caution'
11
+ require 'forwardable'
12
+
13
+ require_relative 'acts_as_hashable/factory'
14
+ require_relative 'acts_as_hashable/hash_refinements'
15
+ require_relative 'acts_as_hashable/type_factory'
16
+ require_relative 'acts_as_hashable/hashable'
17
+
18
+ module ActsAsHashable
19
+ # This module adds the class-level method that marks a class as hashable.
20
+ module DslHook
21
+ def acts_as_hashable
22
+ extend ::ActsAsHashable::Hashable
23
+ end
24
+
25
+ def acts_as_hashable_factory
26
+ extend ActsAsHashable::Factory
27
+ end
28
+ end
29
+ end
30
+
31
+ Object.class_eval do
32
+ extend ::ActsAsHashable::DslHook
33
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (c) 2019-present, Blue Marble Payroll, LLC
5
+ #
6
+ # This source code is licensed under the MIT license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+ #
9
+
10
+ module ActsAsHashable
11
+ # This class serves as a singleton that can make mapped acts_as_hashable components.
12
+ # It is important to note that these components *must* be acts_as_hashable objects.
13
+ # In order to use you just have to subclass this class and implement
14
+ # a method called 'registry', such as:
15
+ # def registry
16
+ # {
17
+ # 'Table': Table,
18
+ # 'Text': Text
19
+ # }
20
+ # end
21
+ # You can also use the 'register' DSL:
22
+ # register 'some_class_name', SomeClassName
23
+ # register 'some_class_name', '', SomeClassName
24
+ # or:
25
+ # register 'some_class_name', ->(_key) { SomeClassName }
26
+ module Factory
27
+ extend Forwardable
28
+
29
+ def_delegators :factory, :array, :make
30
+
31
+ def register(*args)
32
+ raise ArgumentError, "missing at least one key and value: #{args}" if args.length < 2
33
+
34
+ value = args.last
35
+
36
+ args[0..-2].each do |key|
37
+ registry[key] = value
38
+ end
39
+ end
40
+
41
+ def registry
42
+ @registry ||= {}
43
+ end
44
+
45
+ def materialize_registry
46
+ @registry.map do |k, v|
47
+ value = v.is_a?(Proc) ? v.call(k) : v
48
+ [k, value]
49
+ end.to_h
50
+ end
51
+
52
+ def type_key(key)
53
+ @typed_with = key
54
+ end
55
+
56
+ def typed_with
57
+ @typed_with || TypeFactory::DEFAULT_TYPE_KEY
58
+ end
59
+
60
+ private
61
+
62
+ def factory
63
+ @factory ||= TypeFactory.new(materialize_registry, typed_with)
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (c) 2019-present, Blue Marble Payroll, LLC
5
+ #
6
+ # This source code is licensed under the MIT license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+ #
9
+
10
+ module ActsAsHashable
11
+ # Let's provide a refinenment instead of monkey-patching Hash. That way we can stop
12
+ # polluting other libraries and internalize our specific needs.
13
+ module HashRefinements
14
+ refine Hash do
15
+ def symbolize_keys
16
+ map { |k, v| [k.to_sym, v] }.to_h
17
+ end
18
+ end
19
+ end
20
+ end
@@ -11,24 +11,62 @@ module ActsAsHashable
11
11
  # This class contains the main set of class-level methods that can be used by
12
12
  # hashable classes.
13
13
  module Hashable
14
+ using HashRefinements
15
+
16
+ class HydrationError < Caution::CauseInjectingError; end
17
+
18
+ HASHABLE_HYDRATORS = [
19
+ {
20
+ condition: ->(_context, object, _nullable) { object.is_a?(Hash) },
21
+ converter: lambda do |context, object, _nullable|
22
+ args = (object || {}).symbolize_keys
23
+
24
+ if args.keys.any?
25
+ context.new(**args)
26
+ else
27
+ context.new
28
+ end
29
+ end
30
+ },
31
+ {
32
+ condition: ->(context, object, _nullable) { object.is_a?(context) },
33
+ converter: ->(_context, object, _nullable) { object }
34
+ },
35
+ {
36
+ condition: ->(_context, object, nullable) { object.nil? && nullable },
37
+ converter: ->(_context, _object, _nullable) { nil }
38
+ },
39
+ {
40
+ condition: ->(_context, object, nullable) { object.nil? && !nullable },
41
+ converter: ->(context, _object, _nullable) { context.new }
42
+ }
43
+ ].freeze
44
+
45
+ private_constant :HASHABLE_HYDRATORS
46
+
14
47
  def array(object, nullable: true)
15
48
  objects = object.is_a?(Hash) ? [object] : Array(object)
16
49
 
17
- objects.select { |o| !!o }.map { |o| make(o, nullable: nullable) }
50
+ objects.reject { |o| o.is_a?(FalseClass) || o.nil? }
51
+ .map { |o| make(o, nullable: nullable) }
18
52
  end
19
53
 
20
54
  def make(object, nullable: true)
21
- if object.is_a?(Hash)
22
- new(**::ActsAsHashable::Utilities.symbolize_keys(object))
23
- elsif object.is_a?(self)
24
- object
25
- elsif object.nil? && nullable
26
- nil
27
- elsif object.nil? && !nullable
28
- new
29
- else
30
- raise "Cannot create hashable object with class name: #{object.class.name}"
55
+ HASHABLE_HYDRATORS.each do |hydrator|
56
+ next unless hydrator[:condition].call(self, object, nullable)
57
+
58
+ return hydrate(hydrator, object, nullable)
31
59
  end
60
+
61
+ raise ArgumentError, "Cannot create hashable object with class name: #{object.class.name}"
62
+ end
63
+
64
+ private
65
+
66
+ def hydrate(hydrator, object, nullable)
67
+ hydrator[:converter].call(self, object, nullable)
68
+ rescue ArgumentError
69
+ raise HydrationError, "#{name} cannot be hydrated using arguments: #{object}"
32
70
  end
33
71
  end
34
72
  end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (c) 2019-present, Blue Marble Payroll, LLC
5
+ #
6
+ # This source code is licensed under the MIT license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+ #
9
+
10
+ module ActsAsHashable
11
+ # A TypeFactory object understands how to build objects using a special designated 'type' key.
12
+ class TypeFactory
13
+ using HashRefinements
14
+
15
+ DEFAULT_TYPE_KEY = :type
16
+
17
+ attr_reader :registry, :type_key
18
+
19
+ def initialize(registry = {}, type_key = DEFAULT_TYPE_KEY)
20
+ @registry = registry.symbolize_keys
21
+ @type_key = type_key.to_s.to_sym
22
+
23
+ freeze
24
+ end
25
+
26
+ def array(objects = [])
27
+ objects = objects.is_a?(Hash) ? [objects] : Array(objects)
28
+
29
+ objects.map do |object|
30
+ object.is_a?(Hash) ? make(object) : object
31
+ end
32
+ end
33
+
34
+ def make(config = {})
35
+ config = (config || {}).symbolize_keys
36
+ type = config[type_key].to_s.to_sym
37
+ object_class = registry[type]
38
+
39
+ raise ArgumentError, "cannot find section from type: #{type}" unless object_class
40
+
41
+ config_without_type = config.reject { |k| k == type_key }
42
+
43
+ object_class.new(config_without_type)
44
+ end
45
+ end
46
+ end
@@ -8,5 +8,5 @@
8
8
  #
9
9
 
10
10
  module ActsAsHashable
11
- VERSION = '1.0.3'
11
+ VERSION = '1.2.0-alpha'
12
12
  end
metadata CHANGED
@@ -1,15 +1,85 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acts_as_hashable
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.2.0.pre.alpha
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthew Ruggio
8
8
  autorequire:
9
- bindir: bin
9
+ bindir: exe
10
10
  cert_chain: []
11
- date: 2018-09-28 00:00:00.000000000 Z
11
+ date: 2020-06-09 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: caution
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: guard-rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '4.7'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '4.7'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry-byebug
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '13.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '13.0'
13
83
  - !ruby/object:Gem::Dependency
14
84
  name: rspec
15
85
  requirement: !ruby/object:Gem::Requirement
@@ -30,14 +100,42 @@ dependencies:
30
100
  requirements:
31
101
  - - "~>"
32
102
  - !ruby/object:Gem::Version
33
- version: 0.59.2
103
+ version: 0.63.1
34
104
  type: :development
35
105
  prerelease: false
36
106
  version_requirements: !ruby/object:Gem::Requirement
37
107
  requirements:
38
108
  - - "~>"
39
109
  - !ruby/object:Gem::Version
40
- version: 0.59.2
110
+ version: 0.63.1
111
+ - !ruby/object:Gem::Dependency
112
+ name: simplecov
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 0.16.1
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 0.16.1
125
+ - !ruby/object:Gem::Dependency
126
+ name: simplecov-console
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 0.4.2
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: 0.4.2
41
139
  description: |2
42
140
  This is a small library that helps increase the pliability of object constructor signatures.
43
141
  Instead of instantiating via the constructor, this library can install helper factory
@@ -51,25 +149,33 @@ files:
51
149
  - ".editorconfig"
52
150
  - ".gitignore"
53
151
  - ".rubocop.yml"
54
- - ".rubocop_todo.yml"
55
152
  - ".ruby-version"
56
153
  - ".travis.yml"
154
+ - CHANGELOG.md
155
+ - CODE_OF_CONDUCT.md
57
156
  - Gemfile
58
- - Gemfile.lock
157
+ - Guardfile
59
158
  - LICENSE
60
159
  - README.md
160
+ - Rakefile
61
161
  - acts_as_hashable.gemspec
162
+ - bin/console
163
+ - exe/.gitkeep
62
164
  - lib/acts_as_hashable.rb
63
- - lib/acts_as_hashable/acts_as_hashable.rb
165
+ - lib/acts_as_hashable/factory.rb
166
+ - lib/acts_as_hashable/hash_refinements.rb
64
167
  - lib/acts_as_hashable/hashable.rb
65
- - lib/acts_as_hashable/utilities.rb
168
+ - lib/acts_as_hashable/type_factory.rb
66
169
  - lib/acts_as_hashable/version.rb
67
- - spec/acts_as_hashable_spec.rb
68
- - spec/examples.rb
69
170
  homepage: https://github.com/bluemarblepayroll/acts_as_hashable
70
171
  licenses:
71
172
  - MIT
72
- metadata: {}
173
+ metadata:
174
+ bug_tracker_uri: https://github.com/bluemarblepayroll/acts_as_hashable/issues
175
+ changelog_uri: https://github.com/bluemarblepayroll/acts_as_hashable/blob/master/CHANGELOG.md
176
+ documentation_uri: https://www.rubydoc.info/gems/acts_as_hashable
177
+ homepage_uri: https://github.com/bluemarblepayroll/acts_as_hashable
178
+ source_code_uri: https://github.com/bluemarblepayroll/acts_as_hashable
73
179
  post_install_message:
74
180
  rdoc_options: []
75
181
  require_paths:
@@ -78,18 +184,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
78
184
  requirements:
79
185
  - - ">="
80
186
  - !ruby/object:Gem::Version
81
- version: 2.3.1
187
+ version: '2.5'
82
188
  required_rubygems_version: !ruby/object:Gem::Requirement
83
189
  requirements:
84
- - - ">="
190
+ - - ">"
85
191
  - !ruby/object:Gem::Version
86
- version: '0'
192
+ version: 1.3.1
87
193
  requirements: []
88
- rubyforge_project:
89
- rubygems_version: 2.5.2.3
194
+ rubygems_version: 3.0.3
90
195
  signing_key:
91
196
  specification_version: 4
92
197
  summary: Simple hash-based factory methods for objects.
93
- test_files:
94
- - spec/acts_as_hashable_spec.rb
95
- - spec/examples.rb
198
+ test_files: []
@@ -1,50 +0,0 @@
1
- # This configuration was generated by
2
- # `rubocop --auto-gen-config`
3
- # on 2018-09-27 15:44:50 -0500 using RuboCop version 0.59.2.
4
- # The point is for the user to remove these configuration records
5
- # one by one as the offenses are removed from the code base.
6
- # Note that changes in the inspected code, or installation of new
7
- # versions of RuboCop, may require this file to be generated again.
8
-
9
- # Offense count: 5
10
- # Configuration parameters: CountComments, ExcludedMethods.
11
- # ExcludedMethods: refine
12
- Metrics/BlockLength:
13
- Max: 128
14
-
15
- # Offense count: 1
16
- Metrics/CyclomaticComplexity:
17
- Max: 7
18
-
19
- # Offense count: 1
20
- # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
21
- # URISchemes: http, https
22
- Metrics/LineLength:
23
- Max: 105
24
-
25
- # Offense count: 1
26
- # Configuration parameters: CountComments, ExcludedMethods.
27
- Metrics/MethodLength:
28
- Max: 11
29
-
30
- # Offense count: 1
31
- Metrics/PerceivedComplexity:
32
- Max: 8
33
-
34
- # Offense count: 1
35
- Style/DoubleNegation:
36
- Exclude:
37
- - 'lib/acts_as_hashable/hashable.rb'
38
-
39
- # Offense count: 1
40
- # Cop supports --auto-correct.
41
- # Configuration parameters: InverseMethods, InverseBlocks.
42
- Style/InverseMethods:
43
- Exclude:
44
- - 'lib/acts_as_hashable/hashable.rb'
45
-
46
- # Offense count: 1
47
- # Cop supports --auto-correct.
48
- Style/RescueModifier:
49
- Exclude:
50
- - 'lib/acts_as_hashable/utilities.rb'
@@ -1,50 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- acts_as_hashable (1.0.3)
5
-
6
- GEM
7
- remote: https://rubygems.org/
8
- specs:
9
- ast (2.4.0)
10
- diff-lcs (1.3)
11
- jaro_winkler (1.5.1)
12
- parallel (1.12.1)
13
- parser (2.5.1.2)
14
- ast (~> 2.4.0)
15
- powerpack (0.1.2)
16
- rainbow (3.0.0)
17
- rspec (3.8.0)
18
- rspec-core (~> 3.8.0)
19
- rspec-expectations (~> 3.8.0)
20
- rspec-mocks (~> 3.8.0)
21
- rspec-core (3.8.0)
22
- rspec-support (~> 3.8.0)
23
- rspec-expectations (3.8.1)
24
- diff-lcs (>= 1.2.0, < 2.0)
25
- rspec-support (~> 3.8.0)
26
- rspec-mocks (3.8.0)
27
- diff-lcs (>= 1.2.0, < 2.0)
28
- rspec-support (~> 3.8.0)
29
- rspec-support (3.8.0)
30
- rubocop (0.59.2)
31
- jaro_winkler (~> 1.5.1)
32
- parallel (~> 1.10)
33
- parser (>= 2.5, != 2.5.1.1)
34
- powerpack (~> 0.1)
35
- rainbow (>= 2.2.2, < 4.0)
36
- ruby-progressbar (~> 1.7)
37
- unicode-display_width (~> 1.0, >= 1.0.1)
38
- ruby-progressbar (1.10.0)
39
- unicode-display_width (1.4.0)
40
-
41
- PLATFORMS
42
- ruby
43
-
44
- DEPENDENCIES
45
- acts_as_hashable!
46
- rspec
47
- rubocop (~> 0.59.2)
48
-
49
- BUNDLED WITH
50
- 1.16.3
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #
4
- # Copyright (c) 2018-present, Blue Marble Payroll, LLC
5
- #
6
- # This source code is licensed under the MIT license found in the
7
- # LICENSE file in the root directory of this source tree.
8
- #
9
-
10
- require_relative 'utilities'
11
- require_relative 'hashable'
12
-
13
- module ActsAsHashable
14
- # This module adds the class-level method that marks a class as hashable.
15
- module DslHook
16
- def acts_as_hashable
17
- extend ::ActsAsHashable::Hashable
18
- end
19
- end
20
- end
21
-
22
- Object.class_eval do
23
- extend ::ActsAsHashable::DslHook
24
- end
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #
4
- # Copyright (c) 2018-present, Blue Marble Payroll, LLC
5
- #
6
- # This source code is licensed under the MIT license found in the
7
- # LICENSE file in the root directory of this source tree.
8
- #
9
-
10
- module ActsAsHashable
11
- # Some extra functions that are either ancillary to the main domain or polyfill other
12
- # mainstream imnplementations.
13
- class Utilities
14
- class << self
15
- # https://apidock.com/rails/Hash/symbolize_keys
16
- def symbolize_keys(hash)
17
- transform_keys(hash) { |key| key.to_sym rescue key }
18
- end
19
-
20
- # https://apidock.com/rails/v4.2.7/Hash/transform_keys
21
- def transform_keys(hash)
22
- return enum_for(:transform_keys) unless block_given?
23
-
24
- result = {}
25
-
26
- hash.keys.each do |key|
27
- result[yield(key)] = hash[key]
28
- end
29
-
30
- result
31
- end
32
- end
33
- end
34
- end
@@ -1,163 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #
4
- # Copyright (c) 2018-present, Blue Marble Payroll, LLC
5
- #
6
- # This source code is licensed under the MIT license found in the
7
- # LICENSE file in the root directory of this source tree.
8
- #
9
-
10
- require './lib/acts_as_hashable'
11
- require './spec/examples'
12
-
13
- describe ActsAsHashable do
14
- context '#make' do
15
- context 'with a hash constructor interface' do
16
- it 'should properly instantiate objects from a symbol-keyed hash' do
17
- pet = {
18
- name: 'Doug the dog',
19
- toy: { squishy: true }
20
- }
21
-
22
- pet_obj = Pet.make(pet)
23
-
24
- expect(pet_obj.name).to eq('Doug the dog')
25
- expect(pet_obj.toy.squishy).to be true
26
- end
27
- end
28
-
29
- context 'with keyword arguments interface' do
30
- it 'should properly instantiate objects from symbol-keyed hash' do
31
- head_of_household = {
32
- person: {
33
- name: 'Matt',
34
- age: 109
35
- },
36
- partner: {
37
- name: 'Katie',
38
- age: 110
39
- }
40
- }
41
-
42
- head_of_household_obj = HeadOfHousehold.make(head_of_household)
43
-
44
- expect(head_of_household_obj.person.name).to eq('Matt')
45
- expect(head_of_household_obj.person.age).to eq(109)
46
- expect(head_of_household_obj.partner.name).to eq('Katie')
47
- expect(head_of_household_obj.partner.age).to eq(110)
48
- end
49
-
50
- it 'should properly instantiate objects from symbol-keyed hash' do
51
- head_of_household = {
52
- 'person' => {
53
- 'name' => 'Matt',
54
- 'age' => 109
55
- },
56
- 'partner' => {
57
- 'name' => 'Katie',
58
- 'age' => 110
59
- }
60
- }
61
-
62
- head_of_household_obj = HeadOfHousehold.make(head_of_household)
63
-
64
- expect(head_of_household_obj.person.name).to eq('Matt')
65
- expect(head_of_household_obj.person.age).to eq(109)
66
- expect(head_of_household_obj.partner.name).to eq('Katie')
67
- expect(head_of_household_obj.partner.age).to eq(110)
68
- end
69
-
70
- it 'should raise an ArgumentError for missing required keyword' do
71
- head_of_household = {
72
- person: {
73
- # name: 'Matt',
74
- age: 109
75
- },
76
- partner: {
77
- name: 'Katie',
78
- age: 110
79
- }
80
- }
81
-
82
- expect { HeadOfHousehold.make(head_of_household) }.to raise_error(ArgumentError)
83
- end
84
-
85
- it 'should raise an ArgumentError for unknown required keyword' do
86
- head_of_household = {
87
- person: {
88
- name: 'Matt',
89
- age: 109,
90
- height_in_inches: 700
91
- },
92
- partner: {
93
- name: 'Katie',
94
- age: 110
95
- }
96
- }
97
-
98
- expect { HeadOfHousehold.make(head_of_household) }.to raise_error(ArgumentError)
99
- end
100
- end
101
-
102
- context '#array' do
103
- it 'should properly create objects and arrays of objects from symbol-keyed hash and arrays' do
104
- family = {
105
- head_of_household: {
106
- person: {
107
- name: 'Matt',
108
- age: 109
109
- },
110
- partner: {
111
- name: 'Katie',
112
- age: 110
113
- }
114
- },
115
- children: [
116
- { name: 'Martin', age: 29 },
117
- { name: 'Short', age: 99 }
118
- ]
119
- }
120
-
121
- family_obj = Family.make(family)
122
-
123
- expect(family_obj.head_of_household.person.name).to eq('Matt')
124
- expect(family_obj.head_of_household.person.age).to eq(109)
125
- expect(family_obj.head_of_household.partner.name).to eq('Katie')
126
- expect(family_obj.head_of_household.partner.age).to eq(110)
127
-
128
- expect(family_obj.children.length).to eq(2)
129
- expect(family_obj.children[0].name).to eq('Martin')
130
- expect(family_obj.children[0].age).to eq(29)
131
- expect(family_obj.children[1].name).to eq('Short')
132
- expect(family_obj.children[1].age).to eq(99)
133
- end
134
-
135
- it 'should properly instantiate arrays of objects when only a single hash is passed in' do
136
- family = {
137
- head_of_household: {
138
- person: {
139
- name: 'Matt',
140
- age: 109
141
- },
142
- partner: {
143
- name: 'Katie',
144
- age: 110
145
- }
146
- },
147
- children: { name: 'Martin', age: 29 }
148
- }
149
-
150
- family_obj = Family.make(family)
151
-
152
- expect(family_obj.head_of_household.person.name).to eq('Matt')
153
- expect(family_obj.head_of_household.person.age).to eq(109)
154
- expect(family_obj.head_of_household.partner.name).to eq('Katie')
155
- expect(family_obj.head_of_household.partner.age).to eq(110)
156
-
157
- expect(family_obj.children.length).to eq(1)
158
- expect(family_obj.children[0].name).to eq('Martin')
159
- expect(family_obj.children[0].age).to eq(29)
160
- end
161
- end
162
- end
163
- end
@@ -1,62 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #
4
- # Copyright (c) 2018-present, Blue Marble Payroll, LLC
5
- #
6
- # This source code is licensed under the MIT license found in the
7
- # LICENSE file in the root directory of this source tree.
8
- #
9
-
10
- class Toy
11
- acts_as_hashable
12
-
13
- attr_reader :squishy
14
-
15
- def initialize(opts = {})
16
- @squishy = opts[:squishy] || false
17
- end
18
- end
19
-
20
- class Pet
21
- acts_as_hashable
22
-
23
- attr_reader :name, :toy
24
-
25
- def initialize(opts = {})
26
- @name = opts[:name]
27
- @toy = Toy.make(opts[:toy])
28
- end
29
- end
30
-
31
- class Person
32
- acts_as_hashable
33
-
34
- attr_reader :name, :age
35
-
36
- def initialize(name:, age:)
37
- @name = name
38
- @age = age
39
- end
40
- end
41
-
42
- class HeadOfHousehold
43
- acts_as_hashable
44
-
45
- attr_reader :person, :partner
46
-
47
- def initialize(person:, partner: nil)
48
- @person = Person.make(person)
49
- @partner = Person.make(partner)
50
- end
51
- end
52
-
53
- class Family
54
- acts_as_hashable
55
-
56
- attr_reader :head_of_household, :children
57
-
58
- def initialize(head_of_household:, children: [])
59
- @head_of_household = HeadOfHousehold.make(head_of_household)
60
- @children = Person.array(children)
61
- end
62
- end