upgrow 0.0.3 → 0.0.4

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/lib/upgrow/action.rb +9 -4
  3. data/lib/upgrow/actions.rb +1 -1
  4. data/lib/upgrow/active_record_conversion.rb +43 -0
  5. data/lib/upgrow/{active_record_adapter.rb → active_record_queries.rb} +6 -6
  6. data/lib/upgrow/basic_model.rb +5 -1
  7. data/lib/upgrow/basic_repository.rb +1 -41
  8. data/lib/upgrow/immutable_struct.rb +1 -0
  9. data/lib/upgrow/input.rb +2 -3
  10. data/lib/upgrow/model.rb +3 -3
  11. data/lib/upgrow/repository.rb +5 -3
  12. data/lib/upgrow/result.rb +1 -1
  13. data/test/dummy/app/actions/application_action.rb +1 -1
  14. data/test/dummy/app/actions/articles/new_action.rb +1 -2
  15. data/test/dummy/app/actions/comments/new_action.rb +1 -2
  16. data/test/dummy/app/actions/sessions/new_action.rb +1 -2
  17. data/test/dummy/app/actions/users/new_action.rb +1 -2
  18. data/test/dummy/app/channels/application_cable/channel.rb +1 -0
  19. data/test/dummy/app/channels/application_cable/connection.rb +1 -0
  20. data/test/dummy/app/controllers/application_controller.rb +1 -0
  21. data/test/dummy/app/helpers/application_helper.rb +3 -4
  22. data/test/dummy/app/jobs/application_job.rb +1 -0
  23. data/test/dummy/app/mailers/application_mailer.rb +1 -0
  24. data/test/dummy/app/records/user_record.rb +1 -0
  25. data/test/dummy/app/repositories/article_repository.rb +7 -16
  26. data/test/dummy/app/repositories/comment_repository.rb +1 -0
  27. data/test/dummy/app/repositories/user_repository.rb +3 -2
  28. data/test/dummy/config/application.rb +1 -0
  29. data/test/dummy/config/boot.rb +1 -0
  30. data/test/dummy/config/environment.rb +1 -0
  31. data/test/dummy/config/environments/development.rb +1 -0
  32. data/test/dummy/config/environments/production.rb +2 -1
  33. data/test/dummy/config/environments/test.rb +1 -0
  34. data/test/dummy/config/initializers/assets.rb +1 -0
  35. data/test/dummy/config/initializers/backtrace_silencers.rb +1 -0
  36. data/test/dummy/config/initializers/cookies_serializer.rb +1 -0
  37. data/test/dummy/config/initializers/filter_parameter_logging.rb +1 -0
  38. data/test/dummy/config/initializers/wrap_parameters.rb +1 -0
  39. data/test/dummy/config/puma.rb +5 -4
  40. data/test/dummy/db/migrate/20210219211631_create_articles.rb +1 -0
  41. data/test/dummy/db/migrate/20210320140432_create_comments.rb +1 -0
  42. data/test/dummy/db/migrate/20210409164927_create_users.rb +1 -0
  43. data/test/rails_helper.rb +1 -1
  44. data/test/system/articles_test.rb +2 -2
  45. data/test/test_helper.rb +38 -0
  46. data/test/upgrow/action_test.rb +2 -2
  47. data/test/upgrow/active_record_conversion_test.rb +85 -0
  48. data/test/upgrow/{active_record_adapter_test.rb → active_record_queries_test.rb} +10 -12
  49. data/test/upgrow/basic_model_test.rb +6 -3
  50. data/test/upgrow/basic_repository_test.rb +0 -62
  51. metadata +9 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3238cf0308b8e81b0349792dc104880479702dc5c802a8985d2f37461fb1029f
4
- data.tar.gz: 3c06e81104d588ae28d500d4db9946e16afeee449eb7b58a70700a8e3f6b7bb1
3
+ metadata.gz: ee034f6fa4ddd317a01b6f80d4762259f205a6763cd06fdfa637cdac186f0063
4
+ data.tar.gz: 82b9fa66aab3ca2b3bec78ef2c04415d5aa199c0eb9734216010ae0806bb5476
5
5
  SHA512:
6
- metadata.gz: b7f23d2e55b0b06d99c168353c99e1b13e3ee4c0d9a12371e5f6771c7bc9dfae347512c583d716188454b634d54d57149e24b98cf203738d5aee718148405f29
7
- data.tar.gz: 646705859b0fb813b215081f1f52058b2b36b2e15b2a0b81cdeb0dfd537ebe30e84c2846a5b37e99d9646e9706015ded057f365100798bdec360918c8936ae5a
6
+ metadata.gz: 720d3134774aac67239a7681e2e61dcc525fbd8e38852ebb07f2ec82b8be8cc61ac0b71bcaea13fd375e2fc027bd5092aae11fdd70269da1d1fb299325afbd42
7
+ data.tar.gz: 0f16e74a9a07c2e186f10ec14806fc8c59890149204252f25ff2fbc083d8284625f1967a9a74db5e5d1dfb377f315aec864c739a39c8542071a622991bcf8bfb
data/lib/upgrow/action.rb CHANGED
@@ -3,11 +3,11 @@
3
3
  require_relative 'result'
4
4
 
5
5
  module Upgrow
6
- # Actions represent the entry points to the apps core logic. These objects
6
+ # Actions represent the entry points to the app's core logic. These objects
7
7
  # coordinate workflows in order to get operations and activities done.
8
- # Ultimately, Actions are the public interface of the apps business layers.
8
+ # Ultimately, Actions are the public interface of the app's business layers.
9
9
  #
10
- # Rails controllers talk to the apps internals by sending messages to
10
+ # Rails controllers talk to the app's internals by sending messages to
11
11
  # specific Actions, optionally with the required inputs. Actions have a
12
12
  # one-to-one relationship with incoming requests: they are paired
13
13
  # symmetrically with end-user intents and demands. This is quite a special
@@ -15,7 +15,7 @@ module Upgrow
15
15
  # should be handled by a single Action.
16
16
  #
17
17
  # The fact that each Action represents a meaningful and complete
18
- # request-response cycle forces modularization for the apps business logic,
18
+ # request-response cycle forces modularization for the app's business logic,
19
19
  # exposing immediately complex relationships between objects at the same time
20
20
  # that frees up the app from scenarios such as interdependent requests. In
21
21
  # other words, Actions do not have knowledge or coupling between other Actions
@@ -29,6 +29,11 @@ module Upgrow
29
29
  # decorate methods implemented by subclasses so they can have additional
30
30
  # behaviour.
31
31
  module Decorator
32
+ # Calls the original `perform` method of the Action object and returns its
33
+ # Result. In case the Action throws a `:failure`, catches and returns its
34
+ # value, which is supposed to be a failed Result generated by the Action.
35
+ #
36
+ # @return [Result] the Action Result.
32
37
  def perform(...)
33
38
  catch(:failure) do
34
39
  super
@@ -24,7 +24,7 @@ module Upgrow
24
24
  #
25
25
  # @return [Action] the Action specified by the names.
26
26
  def [](*names)
27
- action_class_name = names.map(&:capitalize).join('::') + 'Action'
27
+ action_class_name = "#{names.map(&:capitalize).join("::")}Action"
28
28
  Object.const_get(action_class_name)
29
29
  end
30
30
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Upgrow
4
+ # Offers convenience funcionality to convert Active Records into Models.
5
+ module ActiveRecordConversion
6
+ # Converts the given Active Record or Records into Models.
7
+ #
8
+ # @param record_or_enum [ActiveRecord::Base, Enumerable] an Active Record
9
+ # instance or a collection of Records to be converted.
10
+ #
11
+ # @return [Model] the Model instance generated based on the single Record
12
+ # given.
13
+ # @return [Array<Model>] the collection of Model instances generated based
14
+ # on the collection of Records provided.
15
+ # @return [nil] if the given value is nil.
16
+ def to_model(record_or_enum)
17
+ if record_or_enum.respond_to?(:map)
18
+ record_or_enum.map do |record|
19
+ _to_model(record)
20
+ end
21
+ elsif !record_or_enum.nil?
22
+ _to_model(record_or_enum)
23
+ end
24
+ end
25
+
26
+ # @private
27
+ def _to_model(record)
28
+ associations = record.class.reflections.keys.map do |reflection|
29
+ association = record.association(reflection.to_sym)
30
+ next unless association.loaded?
31
+
32
+ name = reflection.sub('_record', '').to_sym
33
+ [name, to_model(record.public_send(reflection))]
34
+ end.compact.to_h
35
+
36
+ model_class = Object.const_get(record.class.name.delete_suffix('Record'))
37
+
38
+ attributes = record.attributes.merge(associations)
39
+
40
+ model_class.new(**attributes.transform_keys(&:to_sym))
41
+ end
42
+ end
43
+ end
@@ -4,7 +4,7 @@ module Upgrow
4
4
  # Mixin that implements Repository methods with an Active Record Base. When
5
5
  # included in a Repository class, it sets the default base to be a class
6
6
  # ending with `Record`.
7
- module ActiveRecordAdapter
7
+ module ActiveRecordQueries
8
8
  # Class methods for classes that include this module.
9
9
  module ClassMethods
10
10
  # Callback method used by Basic Repository to set a default Repository
@@ -19,7 +19,7 @@ module Upgrow
19
19
  # @return [Class] the Active Record Base class to be used as the
20
20
  # Repository base according to the architecture's naming convention.
21
21
  def default_base
22
- base_name = name[/\A(.+)Repository\z/, 1] + 'Record'
22
+ base_name = "#{name[/\A(.+)Repository\z/, 1]}Record"
23
23
  Object.const_get(base_name)
24
24
  end
25
25
  end
@@ -34,7 +34,7 @@ module Upgrow
34
34
  # @return [Array<Model>] a collection of Models representing all persisted
35
35
  # Records.
36
36
  def all
37
- base.all.map { |record| to_model(record.attributes) }
37
+ to_model(base.all)
38
38
  end
39
39
 
40
40
  # Persists a new Record with the given input, and materializes the newly
@@ -46,7 +46,7 @@ module Upgrow
46
46
  # Record.
47
47
  def create(input)
48
48
  record = base.create!(input.attributes)
49
- to_model(record.attributes)
49
+ to_model(record)
50
50
  end
51
51
 
52
52
  # Retrieves the Record with the given ID, representing its data as a Model.
@@ -57,7 +57,7 @@ module Upgrow
57
57
  # ID.
58
58
  def find(id)
59
59
  record = base.find(id)
60
- to_model(record.attributes)
60
+ to_model(record)
61
61
  end
62
62
 
63
63
  # Updates the Record with the given ID with the given Input attributes.
@@ -69,7 +69,7 @@ module Upgrow
69
69
  # @return [Model] the Model instance with the updated data of the Record.
70
70
  def update(id, input)
71
71
  record = base.update(id, input.attributes)
72
- to_model(record.attributes)
72
+ to_model(record)
73
73
  end
74
74
 
75
75
  # Deletes the Record that has the given ID.
@@ -54,7 +54,11 @@ module Upgrow
54
54
 
55
55
  def method_missing(name, *args, &block)
56
56
  return super unless associations.include?(name)
57
- associations[name] || raise(AssociationNotLoadedError)
57
+
58
+ associations[name] || raise(
59
+ AssociationNotLoadedError,
60
+ "Association #{name} not loaded for #{self.class.name}."
61
+ )
58
62
  end
59
63
 
60
64
  def respond_to_missing?(name, _include_private = false)
@@ -7,17 +7,6 @@ module Upgrow
7
7
  class BasicRepository
8
8
  class << self
9
9
  attr_writer :base
10
- attr_writer :model_class
11
-
12
- # model_class [Class] the Model class to be used to map and return the
13
- # materialized data as instances of the domain. Defaults to a constant
14
- # derived from the Repository class' name. For example, a `UserRepository`
15
- # will have its default Model class set to `User`.
16
- #
17
- # @return [Class] the Repository Model class.
18
- def model_class
19
- @model_class || default_model_class
20
- end
21
10
 
22
11
  # the base object to be used internally to retrieve the persisted data.
23
12
  # For example, a base class in which queries can be performed for a
@@ -31,42 +20,13 @@ module Upgrow
31
20
  private
32
21
 
33
22
  def default_base; end
34
-
35
- def default_model_class
36
- model_class_name = name.delete_suffix('Repository')
37
- Object.const_get(model_class_name)
38
- end
39
23
  end
40
24
 
41
- attr_reader :base, :model_class
25
+ attr_reader :base
42
26
 
43
27
  # Sets the Basic Repositorie's state.
44
28
  def initialize
45
29
  @base = self.class.base
46
- @model_class = self.class.model_class
47
- end
48
-
49
- # Represents the raw Hash of data attributes as a Model instance from the
50
- # Repositorie's Model class.
51
- #
52
- # @param model_class_or_attributes [Class, Hash<Symbol, Object>] the Model
53
- # class to be instantiated, in case it is a different class than the
54
- # Repositorie's Model class, or the list of attributes the model will
55
- # have, in case the Model class is the Repositorie's Model class.
56
- # @param attributes [Hash<Symbol, Object>] the list of attributes the Model
57
- # will have, in case the Model to be instantiated is passed as the first
58
- # argument.
59
- #
60
- # @return [Model] the Model instance populated with the given attributes.
61
- def to_model(model_class_or_attributes, attributes = {})
62
- model_class = model_class_or_attributes
63
-
64
- if model_class_or_attributes.respond_to?(:transform_keys)
65
- model_class = self.model_class
66
- attributes = model_class_or_attributes
67
- end
68
-
69
- model_class.new(**attributes.transform_keys(&:to_sym))
70
30
  end
71
31
  end
72
32
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Upgrow
3
4
  # A read-only Struct. An Immutable Struct is initialized with its member
4
5
  # values and subsequent state changes are not permitted.
data/lib/upgrow/input.rb CHANGED
@@ -21,7 +21,7 @@ module Upgrow
21
21
  # integrity for inputs, since user-entered data can contain any information of
22
22
  # different types, or even not to be present at all.
23
23
  #
24
- # User input validation is a core part of any apps business logic. It ensures
24
+ # User input validation is a core part of any app's business logic. It ensures
25
25
  # that incoming data is sane, proper, and respects a predefined schema. A
26
26
  # default Rails app overloads Record objects with yet another responsibility:
27
27
  # being the place where validation rules are written and checked. While there
@@ -48,7 +48,6 @@ module Upgrow
48
48
 
49
49
  # Overwrites the validation context writer so the Input's state is not
50
50
  # mutated.
51
- def validation_context=(_)
52
- end
51
+ def validation_context=(_); end
53
52
  end
54
53
  end
data/lib/upgrow/model.rb CHANGED
@@ -4,7 +4,7 @@ require_relative 'active_record_schema'
4
4
  require_relative 'basic_model'
5
5
 
6
6
  module Upgrow
7
- # Models are objects that represent core entities of the apps business logic.
7
+ # Models are objects that represent core entities of the app's business logic.
8
8
  # These are usually persisted and can be fetched and created as needed. They
9
9
  # have unique keys for identification (usually a numeric value), and, most
10
10
  # importantly perhaps, they are immutable. This is the key difference between
@@ -12,7 +12,7 @@ module Upgrow
12
12
  # referred to as models in typical Rails default apps.
13
13
  #
14
14
  # Another difference between Models and Records is that, once instantiated,
15
- # Models simply hold its attributes immutably, and they dont have any
15
+ # Models simply hold its attributes immutably, and they don't have any
16
16
  # capabilities to create or update any information in the persistence layer.
17
17
  #
18
18
  # The collaboration between Repositories and Models is what allows Active
@@ -27,7 +27,7 @@ module Upgrow
27
27
  super
28
28
 
29
29
  subclass.schema = ActiveRecordSchema.new(
30
- subclass.name + 'Record', subclass.schema
30
+ "#{subclass.name}Record", subclass.schema
31
31
  )
32
32
  end
33
33
  end
@@ -1,15 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'active_record_adapter'
3
+ require_relative 'active_record_conversion'
4
+ require_relative 'active_record_queries'
4
5
  require_relative 'basic_repository'
5
6
 
6
7
  module Upgrow
7
8
  # Repositories are responsible for the persistence layer of the app. They
8
- # encapsulate Rails Active Record in a subset of simple methods for querying
9
+ # encapsulate Rails' Active Record in a subset of simple methods for querying
9
10
  # and persistence of data, and return simple read-only objects as a result.
10
11
  # This allows the app to isolate Active Record only to this subset, exposing
11
12
  # only the desired queries and methods to other layers through Repositories.
12
13
  class Repository < BasicRepository
13
- include ActiveRecordAdapter
14
+ include ActiveRecordConversion
15
+ include ActiveRecordQueries
14
16
  end
15
17
  end
data/lib/upgrow/result.rb CHANGED
@@ -18,7 +18,7 @@ module Upgrow
18
18
  #
19
19
  # Additionally, Result instances behave like monadic values by offering
20
20
  # bindings to be called only in case of success or failure, which further
21
- # simplifies the callers code by not having to use conditional to check for
21
+ # simplifies the caller's code by not having to use conditional to check for
22
22
  # errors.
23
23
  class Result < ImmutableStruct
24
24
  class << self
@@ -3,8 +3,8 @@
3
3
  class ApplicationAction < Upgrow::Action
4
4
  expose :current_user
5
5
 
6
- # rubocop:disable Lint/MissingSuper
7
6
  def initialize(user_id:)
7
+ super()
8
8
  @current_user = UserRepository.new.find_from_context(user_id)
9
9
  end
10
10
  end
@@ -2,7 +2,6 @@
2
2
 
3
3
  module Articles
4
4
  class NewAction < UserAction
5
- def perform
6
- end
5
+ def perform; end
7
6
  end
8
7
  end
@@ -2,7 +2,6 @@
2
2
 
3
3
  module Comments
4
4
  class NewAction < UserAction
5
- def perform
6
- end
5
+ def perform; end
7
6
  end
8
7
  end
@@ -2,7 +2,6 @@
2
2
 
3
3
  module Sessions
4
4
  class NewAction < ApplicationAction
5
- def perform
6
- end
5
+ def perform; end
7
6
  end
8
7
  end
@@ -2,7 +2,6 @@
2
2
 
3
3
  module Users
4
4
  class NewAction < ApplicationAction
5
- def perform
6
- end
5
+ def perform; end
7
6
  end
8
7
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module ApplicationCable
3
4
  class Channel < ActionCable::Channel::Base
4
5
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module ApplicationCable
3
4
  class Connection < ActionCable::Connection::Base
4
5
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class ApplicationController < ActionController::Base
3
4
  rescue_from(UserAction::UnauthorizedError) { redirect_to(new_session_path) }
4
5
 
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module ApplicationHelper
3
4
  class BulmaFormBuilder < ActionView::Helpers::FormBuilder
4
5
  # Generic field wrapper for form inputs.
@@ -111,12 +112,10 @@ module ApplicationHelper
111
112
 
112
113
  private
113
114
 
114
- def control(expanded: false)
115
+ def control(expanded: false, &block)
115
116
  control_class = ['control']
116
117
  control_class << ['is-expanded'] if expanded
117
- @template.content_tag(:p, class: control_class) do
118
- yield
119
- end
118
+ @template.content_tag(:p, class: control_class, &block)
120
119
  end
121
120
 
122
121
  def errors_for(method)
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class ApplicationJob < ActiveJob::Base
3
4
  # Automatically retry jobs that encountered a deadlock
4
5
  # retry_on ActiveRecord::Deadlocked
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class ApplicationMailer < ActionMailer::Base
3
4
  default from: 'from@example.com'
4
5
  layout 'mailer'
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class UserRecord < ApplicationRecord
3
4
  self.table_name = 'users'
4
5
 
@@ -2,29 +2,20 @@
2
2
 
3
3
  class ArticleRepository < Upgrow::Repository
4
4
  def all
5
- base.all.includes(:user_record).map do |record|
6
- user = to_model(User, record.user_record.attributes)
7
- to_model(record.attributes.merge(user: user))
8
- end
5
+ to_model(base.all.includes(:user_record))
9
6
  end
10
7
 
11
8
  def find_with_comments(id)
12
- record = ArticleRecord.find(id)
9
+ record = base
10
+ .includes(:user_record)
11
+ .includes(comment_records: :user_record)
12
+ .find(id)
13
13
 
14
- comment_records = record.comment_records.includes(:user_record)
15
-
16
- comments = comment_records.map do |comment_record|
17
- user = to_model(User, comment_record.user_record.attributes)
18
- to_model(Comment, comment_record.attributes.merge(user: user))
19
- end
20
-
21
- user = to_model(User, record.user_record.attributes)
22
-
23
- to_model(record.attributes.merge(comments: comments, user: user))
14
+ to_model(record)
24
15
  end
25
16
 
26
17
  def find_for_user(id, user:)
27
18
  record = base.find_by(id: id, user_id: user.id)
28
- to_model(record.attributes) if record
19
+ to_model(record)
29
20
  end
30
21
  end
@@ -1,3 +1,4 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class CommentRepository < Upgrow::Repository
3
4
  end
@@ -3,10 +3,11 @@
3
3
  class UserRepository < Upgrow::Repository
4
4
  def find_for_authentication(input)
5
5
  record = UserRecord.find_by(email: input.email)
6
- to_model(record.attributes) if record&.authenticate(input.password)
6
+ to_model(record) if record&.authenticate(input.password)
7
7
  end
8
8
 
9
9
  def find_from_context(id)
10
- UserRecord.find_by(id: id)
10
+ record = UserRecord.find_by(id: id)
11
+ to_model(record)
11
12
  end
12
13
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative 'boot'
3
4
 
4
5
  require 'rails/all'
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  # Set up gems listed in the Gemfile.
3
4
  ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../Gemfile', __dir__)
4
5
 
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  # Load the Rails application.
3
4
  require_relative 'application'
4
5
 
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'active_support/core_ext/integer/time'
3
4
 
4
5
  Rails.application.configure do
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'active_support/core_ext/integer/time'
3
4
 
4
5
  Rails.application.configure do
@@ -100,7 +101,7 @@ Rails.application.configure do
100
101
  # )
101
102
 
102
103
  if ENV['RAILS_LOG_TO_STDOUT'].present?
103
- logger = ActiveSupport::Logger.new(STDOUT)
104
+ logger = ActiveSupport::Logger.new($stdout)
104
105
  logger.formatter = config.log_formatter
105
106
  config.logger = ActiveSupport::TaggedLogging.new(logger)
106
107
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'active_support/core_ext/integer/time'
3
4
 
4
5
  # The test environment is used exclusively to run your application's
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  # Be sure to restart your server when you modify this file.
3
4
 
4
5
  # Version of your assets, change this if you want to expire all your assets.
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  # Be sure to restart your server when you modify this file.
3
4
 
4
5
  # You can add backtrace silencers for libraries that you're using but don't wish
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  # Be sure to restart your server when you modify this file.
3
4
 
4
5
  # Specify a serializer for the signed and encrypted cookie jars.
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  # Be sure to restart your server when you modify this file.
3
4
 
4
5
  # Configure sensitive parameters which will be filtered from the log file.
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  # Be sure to restart your server when you modify this file.
3
4
 
4
5
  # This file contains settings for ActionController::ParamsWrapper which
@@ -1,11 +1,12 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  # Puma can serve each request in a thread from an internal thread pool.
3
4
  # The `threads` method setting takes two numbers: a minimum and maximum.
4
5
  # Any libraries that use thread pools should be configured to match
5
6
  # the maximum value specified for Puma. Default is set to 5 threads for minimum
6
7
  # and maximum; this matches the default thread size of Active Record.
7
8
  #
8
- max_threads_count = ENV.fetch('RAILS_MAX_THREADS') { 5 }
9
+ max_threads_count = ENV.fetch('RAILS_MAX_THREADS', 5)
9
10
  min_threads_count = ENV.fetch('RAILS_MIN_THREADS') { max_threads_count }
10
11
  threads min_threads_count, max_threads_count
11
12
 
@@ -16,14 +17,14 @@ worker_timeout 3600 if ENV.fetch('RAILS_ENV', 'development') == 'development'
16
17
 
17
18
  # Specifies the `port` that Puma will listen on to receive requests.
18
19
  #
19
- port ENV.fetch('PORT') { 3000 }
20
+ port ENV.fetch('PORT', 3000)
20
21
 
21
22
  # Specifies the `environment` that Puma will run in.
22
23
  #
23
- environment ENV.fetch('RAILS_ENV') { 'development' }
24
+ environment ENV.fetch('RAILS_ENV', 'development')
24
25
 
25
26
  # Specifies the `pidfile` that Puma will use.
26
- pidfile ENV.fetch('PIDFILE') { 'tmp/pids/server.pid' }
27
+ pidfile ENV.fetch('PIDFILE', 'tmp/pids/server.pid')
27
28
 
28
29
  # Specifies the number of `workers` to boot in clustered mode.
29
30
  # Workers are forked web server processes. If using threads and workers together
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class CreateArticles < ActiveRecord::Migration[6.1]
3
4
  def change
4
5
  create_table(:articles) do |t|
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class CreateComments < ActiveRecord::Migration[6.1]
3
4
  def change
4
5
  create_table(:comments) do |t|
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class CreateUsers < ActiveRecord::Migration[6.1]
3
4
  def change
4
5
  create_table(:users) do |t|
data/test/rails_helper.rb CHANGED
@@ -14,7 +14,7 @@ if ActiveSupport::TestCase.respond_to?(:fixture_path=)
14
14
  ActionDispatch::IntegrationTest.fixture_path =
15
15
  ActiveSupport::TestCase.fixture_path
16
16
  ActiveSupport::TestCase.file_fixture_path =
17
- ActiveSupport::TestCase.fixture_path + '/files'
17
+ "#{ActiveSupport::TestCase.fixture_path}/files"
18
18
  ActiveSupport::TestCase.fixtures(:all)
19
19
  end
20
20
 
@@ -120,7 +120,7 @@ class ArticlesTest < ApplicationSystemTestCase
120
120
  test 'guest User cannot access the edit page directly' do
121
121
  click_link 'Lorem Barnak'
122
122
 
123
- visit current_path + '/edit'
123
+ visit "#{current_path}/edit"
124
124
 
125
125
  assert_title 'Sign In'
126
126
  end
@@ -137,7 +137,7 @@ class ArticlesTest < ApplicationSystemTestCase
137
137
 
138
138
  click_link 'The Hobbit'
139
139
 
140
- visit current_path + '/edit'
140
+ visit "#{current_path}/edit"
141
141
 
142
142
  assert_title 'Sign In'
143
143
  end
data/test/test_helper.rb CHANGED
@@ -10,3 +10,41 @@ module Warning
10
10
  end
11
11
  end
12
12
  Warning[:deprecated] = true
13
+
14
+ class TestRecord
15
+ class Association
16
+ def initialize(loaded:)
17
+ @loaded = loaded
18
+ end
19
+
20
+ def loaded?
21
+ @loaded
22
+ end
23
+ end
24
+
25
+ class << self
26
+ attr_accessor :reflections
27
+
28
+ def inherited(subclass)
29
+ super
30
+ subclass.reflections = {}
31
+ end
32
+
33
+ def belongs_to(name)
34
+ reflections[name.to_s] = :belongs_to
35
+ define_method(name) { attributes[name.to_s] }
36
+ end
37
+ end
38
+
39
+ def initialize(**attributes)
40
+ @attributes = attributes
41
+ end
42
+
43
+ def attributes
44
+ @attributes.transform_keys(&:to_s)
45
+ end
46
+
47
+ def association(name)
48
+ Association.new(loaded: !public_send(name).nil?)
49
+ end
50
+ end
@@ -31,7 +31,7 @@ module Upgrow
31
31
  class FailedAction < SampleAction
32
32
  def perform
33
33
  super
34
- failure(:error_1, :error_2)
34
+ failure(:error1, :error2)
35
35
  end
36
36
  end
37
37
 
@@ -78,7 +78,7 @@ module Upgrow
78
78
  result = FailedAction.new.perform
79
79
 
80
80
  assert_equal 'volmer', result.user
81
- assert_equal [:error_1, :error_2], result.errors
81
+ assert_equal [:error1, :error2], result.errors
82
82
  end
83
83
 
84
84
  test '#failure throws a failed Result with the given errors' do
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ require 'upgrow/active_record_conversion'
6
+ require 'upgrow/basic_model'
7
+ require 'upgrow/basic_repository'
8
+ require 'upgrow/input'
9
+
10
+ module Upgrow
11
+ class ActiveRecordConversionTest < ActiveSupport::TestCase
12
+ class UserRecord < TestRecord
13
+ end
14
+
15
+ class ArticleRecord < TestRecord
16
+ belongs_to :user_record
17
+ end
18
+
19
+ class User < BasicModel
20
+ attribute :name
21
+ end
22
+
23
+ class Article < BasicModel
24
+ attribute :title
25
+ belongs_to :user
26
+ end
27
+
28
+ class ArticleRepository < BasicRepository
29
+ include ActiveRecordConversion
30
+ end
31
+
32
+ setup do
33
+ @repository = ArticleRepository.new
34
+ @record = ArticleRecord.new(id: 1, title: 'The Hobbit')
35
+ end
36
+
37
+ test '#to_model converts a Record into a Model' do
38
+ model = @repository.to_model(@record)
39
+
40
+ assert_instance_of Article, model
41
+ assert_equal 1, model.id
42
+ assert_equal 'The Hobbit', model.title
43
+ end
44
+
45
+ test '#to_model converts an array of Records into an array of Models' do
46
+ second_record = ArticleRecord.new(id: 2, title: 'Harry Potter')
47
+
48
+ models = @repository.to_model([@record, second_record])
49
+
50
+ assert_equal 2, models.size
51
+
52
+ assert_instance_of Article, models.first
53
+ assert_equal 'The Hobbit', models.first.title
54
+ assert_equal 1, models.first.id
55
+
56
+ assert_instance_of Article, models.last
57
+ assert_equal 'Harry Potter', models.last.title
58
+ assert_equal 2, models.last.id
59
+ end
60
+
61
+ test '#to_model adds any loaded assocation to the resulting Model' do
62
+ user_record = UserRecord.new(id: 666, name: 'JRR Tolkien')
63
+
64
+ record = ArticleRecord.new(
65
+ id: 1, title: 'The Hobbit', user_record: user_record
66
+ )
67
+
68
+ model = @repository.to_model(record)
69
+
70
+ assert_instance_of User, model.user
71
+ assert_equal 666, model.user.id
72
+ assert_equal 'JRR Tolkien', model.user.name
73
+ end
74
+
75
+ test '#to_model does not add unloaded assocations to the resulting Model' do
76
+ model = @repository.to_model(@record)
77
+
78
+ assert_raises(BasicModel::AssociationNotLoadedError) { model.user }
79
+ end
80
+
81
+ test '#to_model is nil when the given object is nil' do
82
+ assert_nil @repository.to_model(nil)
83
+ end
84
+ end
85
+ end
@@ -2,34 +2,32 @@
2
2
 
3
3
  require 'test_helper'
4
4
 
5
- require 'upgrow/active_record_adapter'
6
5
  require 'upgrow/basic_model'
7
- require 'upgrow/basic_repository'
6
+ require 'upgrow/repository'
8
7
  require 'upgrow/input'
9
8
 
10
9
  module Upgrow
11
- class ActiveRecordAdapterTest < ActiveSupport::TestCase
10
+ class ActiveRecordQueriesTest < ActiveSupport::TestCase
12
11
  class User < BasicModel
13
12
  attribute :name
14
13
  end
15
14
 
15
+ class UserRecord < TestRecord
16
+ end
17
+
16
18
  class UserInput < Input
17
19
  attribute :name
18
20
  end
19
21
 
20
- class UserRepository < BasicRepository
21
- include ActiveRecordAdapter
22
+ class UserRepository < Repository
22
23
  end
23
24
 
24
- class UserRecord; end
25
-
26
25
  setup do
27
26
  @base = Minitest::Mock.new
28
27
  UserRepository.base = @base
29
28
  @repository = UserRepository.new
30
- @record = Minitest::Mock.new
31
- @record_attributes = { name: 'volmer', id: 1 }
32
- @record.expect(:attributes, @record_attributes)
29
+
30
+ @record = UserRecord.new(name: 'volmer', id: 1)
33
31
  end
34
32
 
35
33
  test '.base is the Active Record Base class according to the Repository name by default' do
@@ -70,9 +68,9 @@ module Upgrow
70
68
 
71
69
  test '#update changes the existing Record attributes' do
72
70
  input = UserInput.new(name: 'rafael')
71
+ new_record = UserRecord.new(id: 1, name: 'rafael')
73
72
 
74
- @base.expect(:update, @record, [1, { name: 'rafael' }])
75
- @record_attributes[:name] = 'rafael'
73
+ @base.expect(:update, new_record, [1, { name: 'rafael' }])
76
74
 
77
75
  model = @repository.update(1, input)
78
76
 
@@ -76,17 +76,20 @@ module Upgrow
76
76
  model = SampleModel.new(
77
77
  title: 'volmer',
78
78
  body: 'My long body',
79
- id: 1,
79
+ id: 1
80
80
  )
81
81
 
82
- assert_raises(Model::AssociationNotLoadedError) { model.user }
82
+ error = assert_raises(Model::AssociationNotLoadedError) { model.user }
83
+ message = 'Association user not loaded for ' \
84
+ 'Upgrow::BasicModelTest::SampleModel.'
85
+ assert_equal message, error.message
83
86
  end
84
87
 
85
88
  test 'attribute readers work when association is not loaded' do
86
89
  model = SampleModel.new(
87
90
  title: 'volmer',
88
91
  body: 'My long body',
89
- id: 1,
92
+ id: 1
90
93
  )
91
94
 
92
95
  assert_equal 'volmer', model.title
@@ -4,25 +4,15 @@ require 'test_helper'
4
4
 
5
5
  module Upgrow
6
6
  class BasicRepositoryTest < ActiveSupport::TestCase
7
- class User < BasicModel; end
8
-
9
- class Car < BasicModel
10
- attribute :wheels
11
- end
12
-
13
7
  class UserRepository < BasicRepository; end
14
8
 
15
- class NoModelRepository < BasicRepository; end
16
-
17
9
  setup do
18
10
  @original_base = UserRepository.base
19
- @original_model_class = UserRepository.model_class
20
11
  @repository = UserRepository.new
21
12
  end
22
13
 
23
14
  teardown do
24
15
  UserRepository.base = @original_base
25
- UserRepository.model_class = @original_model_class
26
16
  end
27
17
 
28
18
  test '.base is nil by default' do
@@ -34,61 +24,9 @@ module Upgrow
34
24
  assert_equal :my_base, UserRepository.base
35
25
  end
36
26
 
37
- test '.model_class is iferred based on the Repository name' do
38
- assert_equal User, UserRepository.model_class
39
- end
40
-
41
- test '.model_class can be set' do
42
- UserRepository.model_class = :my_model_class
43
- assert_equal :my_model_class, UserRepository.model_class
44
- end
45
-
46
- test '.model_class raises a Name Error if the Model class is undefined' do
47
- error = assert_raises(NameError) do
48
- NoModelRepository.model_class
49
- end
50
-
51
- assert_includes(
52
- error.message,
53
- 'uninitialized constant Upgrow::BasicRepositoryTest::NoModel'
54
- )
55
- end
56
-
57
27
  test '#base is inferred from the Repository class base' do
58
28
  UserRepository.base = :my_base
59
29
  assert_equal :my_base, UserRepository.new.base
60
30
  end
61
-
62
- test '#model_class is inferred from the Repository class Model class' do
63
- UserRepository.model_class = :my_model_class
64
- assert_equal :my_model_class, UserRepository.new.model_class
65
- end
66
-
67
- test '#to_model returns a new Model with the given attributes as keyword arguments' do
68
- model = @repository.to_model(id: 1)
69
-
70
- assert_equal 1, model.id
71
- end
72
-
73
- test '#to_model accepts a Model class explicitly' do
74
- model = @repository.to_model(Car, id: 1, wheels: 4)
75
-
76
- assert_equal 1, model.id
77
- assert_equal 4, model.wheels
78
- end
79
-
80
- test '#to_model accepts a Hash of Symbols' do
81
- args = { id: 1 }
82
- model = @repository.to_model(args)
83
-
84
- assert_equal 1, model.id
85
- end
86
-
87
- test '#to_model accepts a Hash of Strings' do
88
- args = { 'id' => 1 }
89
- model = @repository.to_model(args)
90
-
91
- assert_equal 1, model.id
92
- end
93
31
  end
94
32
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: upgrow
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify Engineering
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-05 00:00:00.000000000 Z
11
+ date: 2021-05-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -35,7 +35,8 @@ files:
35
35
  - lib/upgrow.rb
36
36
  - lib/upgrow/action.rb
37
37
  - lib/upgrow/actions.rb
38
- - lib/upgrow/active_record_adapter.rb
38
+ - lib/upgrow/active_record_conversion.rb
39
+ - lib/upgrow/active_record_queries.rb
39
40
  - lib/upgrow/active_record_schema.rb
40
41
  - lib/upgrow/basic_model.rb
41
42
  - lib/upgrow/basic_repository.rb
@@ -123,7 +124,8 @@ files:
123
124
  - test/test_helper.rb
124
125
  - test/upgrow/action_test.rb
125
126
  - test/upgrow/actions_test.rb
126
- - test/upgrow/active_record_adapter_test.rb
127
+ - test/upgrow/active_record_conversion_test.rb
128
+ - test/upgrow/active_record_queries_test.rb
127
129
  - test/upgrow/active_record_schema_test.rb
128
130
  - test/upgrow/basic_model_test.rb
129
131
  - test/upgrow/basic_repository_test.rb
@@ -139,7 +141,7 @@ homepage: https://github.com/Shopify/upgrow
139
141
  licenses:
140
142
  - MIT
141
143
  metadata:
142
- source_code_uri: https://github.com/Shopify/upgrow/tree/v0.0.3
144
+ source_code_uri: https://github.com/Shopify/upgrow/tree/v0.0.4
143
145
  allowed_push_host: https://rubygems.org
144
146
  post_install_message:
145
147
  rdoc_options: []
@@ -236,7 +238,8 @@ test_files:
236
238
  - test/test_helper.rb
237
239
  - test/upgrow/action_test.rb
238
240
  - test/upgrow/actions_test.rb
239
- - test/upgrow/active_record_adapter_test.rb
241
+ - test/upgrow/active_record_conversion_test.rb
242
+ - test/upgrow/active_record_queries_test.rb
240
243
  - test/upgrow/active_record_schema_test.rb
241
244
  - test/upgrow/basic_model_test.rb
242
245
  - test/upgrow/basic_repository_test.rb