upgrow 0.0.3 → 0.0.4

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