repository-base 0.4.0

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.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +27 -0
  5. data/.travis.yml +6 -0
  6. data/.yardopts +1 -0
  7. data/CHANGELOG.md +128 -0
  8. data/Gemfile +4 -0
  9. data/LICENSE +22 -0
  10. data/README.md +132 -0
  11. data/Rakefile +33 -0
  12. data/bin/bundle +105 -0
  13. data/bin/htmldiff +29 -0
  14. data/bin/kramdown +29 -0
  15. data/bin/ldiff +29 -0
  16. data/bin/rake +29 -0
  17. data/bin/rspec +29 -0
  18. data/bin/rubocop +29 -0
  19. data/bin/ruby-parse +29 -0
  20. data/bin/ruby-rewrite +29 -0
  21. data/bin/setup +43 -0
  22. data/bin/yard +29 -0
  23. data/bin/yardoc +29 -0
  24. data/bin/yri +29 -0
  25. data/doc/Repository.html +128 -0
  26. data/doc/Repository/Base.html +1248 -0
  27. data/doc/Repository/Base/Internals.html +133 -0
  28. data/doc/Repository/Base/Internals/RecordDeleter.html +687 -0
  29. data/doc/Repository/Base/Internals/RecordSaver.html +816 -0
  30. data/doc/Repository/Base/Internals/RecordUpdater.html +1026 -0
  31. data/doc/Repository/Base/Internals/SlugFinder.html +986 -0
  32. data/doc/_index.html +176 -0
  33. data/doc/class_list.html +51 -0
  34. data/doc/css/common.css +1 -0
  35. data/doc/css/full_list.css +58 -0
  36. data/doc/css/style.css +499 -0
  37. data/doc/file.CHANGELOG.html +240 -0
  38. data/doc/file.README.html +218 -0
  39. data/doc/file_list.html +61 -0
  40. data/doc/frames.html +17 -0
  41. data/doc/index.html +218 -0
  42. data/doc/js/app.js +248 -0
  43. data/doc/js/full_list.js +216 -0
  44. data/doc/js/jquery.js +4 -0
  45. data/doc/method_list.html +363 -0
  46. data/doc/top-level-namespace.html +110 -0
  47. data/lib/repository/base.rb +115 -0
  48. data/lib/repository/base/internals/internals.rb +6 -0
  49. data/lib/repository/base/internals/record_deleter.rb +46 -0
  50. data/lib/repository/base/internals/record_saver.rb +58 -0
  51. data/lib/repository/base/internals/record_updater.rb +54 -0
  52. data/lib/repository/base/internals/slug_finder.rb +70 -0
  53. data/lib/repository/base/version.rb +12 -0
  54. data/repository-base.gemspec +37 -0
  55. data/spec/repository/base_spec.rb +398 -0
  56. data/spec/spec_helper.rb +14 -0
  57. metadata +281 -0
@@ -0,0 +1,110 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>
7
+ Top Level Namespace
8
+
9
+ &mdash; Documentation by YARD 0.9.12
10
+
11
+ </title>
12
+
13
+ <link rel="stylesheet" href="css/style.css" type="text/css" charset="utf-8" />
14
+
15
+ <link rel="stylesheet" href="css/common.css" type="text/css" charset="utf-8" />
16
+
17
+ <script type="text/javascript" charset="utf-8">
18
+ pathId = "";
19
+ relpath = '';
20
+ </script>
21
+
22
+
23
+ <script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
24
+
25
+ <script type="text/javascript" charset="utf-8" src="js/app.js"></script>
26
+
27
+
28
+ </head>
29
+ <body>
30
+ <div class="nav_wrap">
31
+ <iframe id="nav" src="class_list.html?1"></iframe>
32
+ <div id="resizer"></div>
33
+ </div>
34
+
35
+ <div id="main" tabindex="-1">
36
+ <div id="header">
37
+ <div id="menu">
38
+
39
+ <a href="_index.html">Index</a> &raquo;
40
+
41
+
42
+ <span class="title">Top Level Namespace</span>
43
+
44
+ </div>
45
+
46
+ <div id="search">
47
+
48
+ <a class="full_list_link" id="class_list_link"
49
+ href="class_list.html">
50
+
51
+ <svg width="24" height="24">
52
+ <rect x="0" y="4" width="24" height="4" rx="1" ry="1"></rect>
53
+ <rect x="0" y="12" width="24" height="4" rx="1" ry="1"></rect>
54
+ <rect x="0" y="20" width="24" height="4" rx="1" ry="1"></rect>
55
+ </svg>
56
+ </a>
57
+
58
+ </div>
59
+ <div class="clear"></div>
60
+ </div>
61
+
62
+ <div id="content"><h1>Top Level Namespace
63
+
64
+
65
+
66
+ </h1>
67
+ <div class="box_info">
68
+
69
+
70
+
71
+
72
+
73
+
74
+
75
+
76
+
77
+
78
+
79
+ </div>
80
+
81
+ <h2>Defined Under Namespace</h2>
82
+ <p class="children">
83
+
84
+
85
+ <strong class="modules">Modules:</strong> <span class='object_link'><a href="Repository.html" title="Repository (module)">Repository</a></span>
86
+
87
+
88
+
89
+
90
+ </p>
91
+
92
+
93
+
94
+
95
+
96
+
97
+
98
+
99
+
100
+ </div>
101
+
102
+ <div id="footer">
103
+ Generated on Sat Feb 3 03:00:17 2018 by
104
+ <a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
105
+ 0.9.12 (ruby-2.5.0).
106
+ </div>
107
+
108
+ </div>
109
+ </body>
110
+ </html>
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'repository/base/version'
4
+
5
+ require 'repository/base/internals/internals'
6
+ require 'repository/support/store_result'
7
+
8
+ # The `Repository` module functions as a namespace for implementing the
9
+ # repository logic in a [Data Mapper pattern](http://martinfowler.com/eaaCatalog/dataMapper.html)
10
+ # implementation.
11
+ # The Repository module initially included two names:
12
+ #
13
+ # 1. `Base`; and
14
+ # 1. `Support` (a namespace containing several related classes).
15
+ #
16
+ module Repository
17
+ # Base class for Repository in Data Mapper pattern.
18
+ class Base
19
+ # Internal support code exclusively used by Repository::Base
20
+ # @since 0.0.1
21
+ module Internals
22
+ end
23
+ private_constant :Internals
24
+ include Internals
25
+
26
+ attr_reader :dao, :factory
27
+
28
+ # Initialise a new `Repository::Base` instance.
29
+ # @param factory Has a .create method to create entities from DAO records.
30
+ # @param dao Data Access Object implements persistence without business
31
+ # logic.
32
+ def initialize(factory:, dao:)
33
+ validate_initializer_argument(:dao, dao)
34
+ validate_initializer_argument(:factory, factory)
35
+ @factory = factory
36
+ @dao = dao
37
+ end
38
+
39
+ # Add a new record with attributes matching the specified entity to the
40
+ # associated DAO.
41
+ # @param entity Entity specifying record to be persisted to new DAO record.
42
+ # @return [Repository::Support::StoreResult] An object containing
43
+ # information about the success or failure of an action.
44
+ def add(entity)
45
+ record = dao.new filtered_attributes_for(entity)
46
+ RecordSaver.new(record: record, factory: factory).result
47
+ end
48
+
49
+ # Return an array of entities matching all records currently in the
50
+ # associated DAO.
51
+ # @return [Array] Array of entities as supplied by the `factory`.
52
+ # @since 0.0.2
53
+ def all
54
+ dao.all.map { |record| factory.create record }
55
+ end
56
+
57
+ # Remove a record from the underlying DAO whose slug matches the passed-in
58
+ # identifier.
59
+ # @param identifier [String] [Slug](http://en.wikipedia.org/wiki/Semantic_URL#Slug)
60
+ # for record to be deleted.
61
+ # @return [Repository::Support::StoreResult] An object containing
62
+ # information about the success or failure of an action.
63
+ # @since 0.0.5
64
+ def delete(identifier)
65
+ RecordDeleter.new(identifier: identifier, dao: dao, factory: factory)
66
+ .delete
67
+ end
68
+
69
+ # Find a record in the DAO and, on success, return a corresponding entity
70
+ # using the specified [slug](http://en.wikipedia.org/wiki/Semantic_URL#Slug),
71
+ # *not* a numeric record ID, as a search identifier.
72
+ # @param slug [String] [Slug](http://en.wikipedia.org/wiki/Semantic_URL#Slug)
73
+ # for record to be deleted.
74
+ # @return [Repository::Support::StoreResult] An object containing
75
+ # information about the success or failure of an action.
76
+ # @since 0.0.3
77
+ def find_by_slug(slug)
78
+ SlugFinder.new(slug: slug, dao: dao, factory: factory).find
79
+ end
80
+
81
+ # Update a record in the DAO corresponding to the specified identifier,
82
+ # using the specified attribute-name/value pairs.
83
+ # @param identifier [String] [Slug](http://en.wikipedia.org/wiki/Semantic_URL#Slug)
84
+ # for record to be deleted.
85
+ # @param updated_attrs [Hash] Attributes to be updated.
86
+ # @since 0.0.4
87
+ # @example
88
+ # result = user_repo.update @user.slug, params[:user_params]
89
+ # @user = result.entity if result.success?
90
+ def update(identifier:, updated_attrs:)
91
+ RecordUpdater.new(identifier: identifier, updated_attrs: updated_attrs,
92
+ dao: dao, factory: factory).update
93
+ end
94
+
95
+ private
96
+
97
+ # supporting #initialize
98
+
99
+ # Verifies that parameter passed to #initialize is a Class.
100
+ # @param arg_sym [Symbol] Which parameter is being validated, either `:dao`
101
+ # or `:factory`.
102
+ # @param value Parameter value being validated. Must be a Class.
103
+ # @return [boolean]
104
+ def validate_initializer_argument(arg_sym, value)
105
+ message = "the :#{arg_sym} argument must be a Class"
106
+ raise ArgumentError, message unless value.respond_to? :new
107
+ end
108
+
109
+ # supporting #add
110
+
111
+ def filtered_attributes_for(entity) # :nodoc:
112
+ entity.attributes.to_hash.reject { |k, _v| k == :errors }
113
+ end
114
+ end # class Repository::Base
115
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'record_deleter'
4
+ require_relative 'record_saver'
5
+ require_relative 'record_updater'
6
+ require_relative 'slug_finder'
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'repository/support/store_result'
4
+ require_relative 'slug_finder'
5
+
6
+ module Repository
7
+ # Base class for Repository in Data Mapper pattern.
8
+ class Base
9
+ # Internal support code exclusively used by Repository::Base
10
+ module Internals
11
+ # Stows away details of reporting update success/failure.
12
+ # @since 0.0.5
13
+ class RecordDeleter
14
+ include Repository::Support
15
+
16
+ # Initializes a new instance of `SlugFinder`.
17
+ # @param identifier [String] [Slug](http://en.wikipedia.org/wiki/Semantic_URL#Slug)
18
+ # for record to be deleted.
19
+ # @param dao Data Access Object implements persistence without business
20
+ # logic.
21
+ # @param factory Factory-pattern class to build an entity from an
22
+ # existing DAO record.
23
+ def initialize(identifier:, dao:, factory:)
24
+ @identifier = identifier
25
+ @dao = dao
26
+ @factory = factory
27
+ end
28
+
29
+ # Command-pattern method returning indication of success or failure of
30
+ # attempt to delete identified record.
31
+ # @return Repository::Support::StoreResult
32
+ def delete
33
+ finder = SlugFinder.new slug: identifier, dao: dao, factory: factory
34
+ result = finder.find
35
+ return result unless result.success
36
+ dao.delete identifier
37
+ result
38
+ end
39
+
40
+ private
41
+
42
+ attr_reader :dao, :factory, :identifier
43
+ end # class Repository::Base::Internals::RecordDeleter
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'repository/support/error_factory'
4
+ require 'repository/support/store_result'
5
+
6
+ module Repository
7
+ # Base class for Repository in Data Mapper pattern.
8
+ class Base
9
+ # Internal support code exclusively used by Repository::Base
10
+ module Internals
11
+ # Reports on the success or failure of saving a *DAO record*, using the
12
+ # `Repository::Support::StoreResult` instance returned from `#result`.
13
+ # @since 0.0.1
14
+ class RecordSaver
15
+ include Repository::Support
16
+
17
+ # Sets instance variable(s) on a new `RecordSaver` instanace.
18
+ # @param record DAO record to attempt to save.
19
+ def initialize(record:, factory:)
20
+ @record = record
21
+ @factory = factory
22
+ end
23
+
24
+ # Command-pattern method returning indication of success or failure of
25
+ # attempt to save record.
26
+ # @return Repository::Support::StoreResult
27
+ # @see #failed_result
28
+ # @see #successful_result
29
+ def result
30
+ if record.save
31
+ successful_result
32
+ else
33
+ failed_result
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ attr_reader :factory, :record
40
+
41
+ # Represent error data sourced from an `ActiveModel::Errors` object as
42
+ # an Array of `{field: 'field', message: 'message'}` hashes.
43
+ def error_hashes
44
+ ErrorFactory.create record.errors
45
+ end
46
+
47
+ def failed_result
48
+ StoreResult::Failure.new error_hashes
49
+ end
50
+
51
+ def successful_result
52
+ entity = factory.create record
53
+ StoreResult::Success.new entity
54
+ end
55
+ end # class Internals::RecordSaver
56
+ end # module Repository::Base::Internals
57
+ end # class Repository::Base
58
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'repository/support/store_result'
4
+
5
+ module Repository
6
+ # Base class for Repository in Data Mapper pattern.
7
+ class Base
8
+ # Internal support code exclusively used by Repository::Base
9
+ module Internals
10
+ # Stows away details of reporting update success/failure.
11
+ # @since 0.0.4
12
+ class RecordUpdater
13
+ include Repository::Support
14
+
15
+ # Initializes a new instance of `RecordUpdater`.
16
+ # @param identifier [String] Slug uniquely identifying the record in the
17
+ # DAO to update
18
+ # @param updated_attrs [Hash] Attributes to be updated.
19
+ # @param dao Data Access Object implements persistence without business
20
+ # logic.
21
+ # @param factory Class whose `#create` method converts a DAO record to
22
+ # an entity.
23
+ def initialize(identifier:, updated_attrs:, dao:, factory:)
24
+ @identifier = identifier
25
+ @updated_attrs = updated_attrs
26
+ @dao = dao
27
+ @factory = factory
28
+ end
29
+
30
+ # Command-pattern method to update a record in the persistence layer,
31
+ # based on the parameters sent to `#initialize`.
32
+ # @return [Repository::Support::StoreResult]
33
+ def update
34
+ @record = dao.where(slug: identifier).first
35
+ return failed_result unless @record.update(updated_attrs.to_h)
36
+ successful_result
37
+ end
38
+
39
+ private
40
+
41
+ attr_reader :dao, :factory, :identifier, :record, :updated_attrs
42
+
43
+ def failed_result # :nodoc:
44
+ StoreResult::Failure.new record.errors
45
+ end
46
+
47
+ def successful_result # :nodoc:
48
+ entity = factory.create record
49
+ StoreResult::Success.new entity
50
+ end
51
+ end # class Repository::Base::Internals::RecordUpdater
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'repository/support/result_builder'
4
+
5
+ module Repository
6
+ # Base class for Repository in Data Mapper pattern.
7
+ class Base
8
+ # Internal support code exclusively used by Repository::Base
9
+ module Internals
10
+ # Find a slug in the DAO, reporting errors if not successful.
11
+ # @since 0.0.3
12
+ class SlugFinder
13
+ include Repository::Support
14
+
15
+ # Initializes a new instance of `SlugFinder`.
16
+ # @param slug [String] [Slug](http://en.wikipedia.org/wiki/Semantic_URL#Slug)
17
+ # for record to be deleted.
18
+ # @param dao Data Access Object implements persistence without business
19
+ # logic.
20
+ def initialize(slug:, dao:, factory:)
21
+ @slug = slug
22
+ @dao = dao
23
+ @factory = factory
24
+ end
25
+
26
+ # Command-pattern method to search underlying DAO for record matching
27
+ # slug. Returns a `Repository::Support::StoreResult` instance with the
28
+ # corresponding entity on success, or with the error hash built by
29
+ # `Repository::Support::ErrorFactory.create` on failure.
30
+ # @return [Repository::Support::StoreResult]
31
+ # @see #errors_for_slug
32
+ # @see #result_builder
33
+ def find
34
+ result_builder(entity_for_slug).build do |_failed_record|
35
+ ErrorFactory.create errors_for_slug
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ attr_reader :dao, :factory, :slug
42
+
43
+ # Builds an entity from an existing DAO record matching the slug.
44
+ # @return If the slug matches an existing DAO record, returns an entity
45
+ # as built from that record, otherwise returns `nil`.
46
+ def entity_for_slug
47
+ record = dao.where(slug: slug).first
48
+ factory.create(record) if record
49
+ end
50
+
51
+ # Builds an [`ActiveModel::Errors`](http://api.rubyonrails.org/classes/ActiveModel/Errors.html)
52
+ # instance and adds a slug-not-found message to it.
53
+ # @return [ActiveModel::Errors]
54
+ # @see #find
55
+ def errors_for_slug
56
+ errors = ActiveModel::Errors.new dao
57
+ errors.add :slug, "not found: '#{slug}'"
58
+ errors
59
+ end
60
+
61
+ # Returns a new `Repository::Support::ResultBuilder` instance, passing
62
+ # the parameter specified to this method as its `#initialize` parameter.
63
+ # @param record DAO record to pass to `ResultBuilder#initialize`.
64
+ def result_builder(record)
65
+ ResultBuilder.new record
66
+ end
67
+ end # class Repository::Base::Internals::SlugFinder
68
+ end
69
+ end
70
+ end