cuprum-rails 0.1.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 (81) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +98 -0
  3. data/CODE_OF_CONDUCT.md +132 -0
  4. data/DEVELOPMENT.md +28 -0
  5. data/LICENSE +22 -0
  6. data/README.md +1045 -0
  7. data/lib/cuprum/rails/action.rb +45 -0
  8. data/lib/cuprum/rails/actions/create.rb +49 -0
  9. data/lib/cuprum/rails/actions/destroy.rb +22 -0
  10. data/lib/cuprum/rails/actions/edit.rb +22 -0
  11. data/lib/cuprum/rails/actions/index.rb +55 -0
  12. data/lib/cuprum/rails/actions/new.rb +19 -0
  13. data/lib/cuprum/rails/actions/resource_action.rb +75 -0
  14. data/lib/cuprum/rails/actions/show.rb +22 -0
  15. data/lib/cuprum/rails/actions/update.rb +59 -0
  16. data/lib/cuprum/rails/actions.rb +16 -0
  17. data/lib/cuprum/rails/collection.rb +115 -0
  18. data/lib/cuprum/rails/command.rb +137 -0
  19. data/lib/cuprum/rails/commands/assign_one.rb +66 -0
  20. data/lib/cuprum/rails/commands/build_one.rb +55 -0
  21. data/lib/cuprum/rails/commands/destroy_one.rb +43 -0
  22. data/lib/cuprum/rails/commands/find_many.rb +60 -0
  23. data/lib/cuprum/rails/commands/find_matching.rb +121 -0
  24. data/lib/cuprum/rails/commands/find_one.rb +50 -0
  25. data/lib/cuprum/rails/commands/insert_one.rb +41 -0
  26. data/lib/cuprum/rails/commands/update_one.rb +49 -0
  27. data/lib/cuprum/rails/commands/validate_one.rb +68 -0
  28. data/lib/cuprum/rails/commands.rb +18 -0
  29. data/lib/cuprum/rails/controller.rb +50 -0
  30. data/lib/cuprum/rails/controller_action.rb +121 -0
  31. data/lib/cuprum/rails/controllers/class_methods/actions.rb +57 -0
  32. data/lib/cuprum/rails/controllers/class_methods/configuration.rb +64 -0
  33. data/lib/cuprum/rails/controllers/class_methods/validations.rb +30 -0
  34. data/lib/cuprum/rails/controllers/class_methods.rb +15 -0
  35. data/lib/cuprum/rails/controllers/configuration.rb +53 -0
  36. data/lib/cuprum/rails/controllers.rb +10 -0
  37. data/lib/cuprum/rails/errors/missing_parameters.rb +33 -0
  38. data/lib/cuprum/rails/errors/missing_primary_key.rb +46 -0
  39. data/lib/cuprum/rails/errors/undefined_permitted_attributes.rb +34 -0
  40. data/lib/cuprum/rails/errors.rb +8 -0
  41. data/lib/cuprum/rails/map_errors.rb +44 -0
  42. data/lib/cuprum/rails/query.rb +77 -0
  43. data/lib/cuprum/rails/query_builder.rb +78 -0
  44. data/lib/cuprum/rails/repository.rb +44 -0
  45. data/lib/cuprum/rails/request.rb +105 -0
  46. data/lib/cuprum/rails/resource.rb +145 -0
  47. data/lib/cuprum/rails/responders/actions.rb +73 -0
  48. data/lib/cuprum/rails/responders/html/plural_resource.rb +62 -0
  49. data/lib/cuprum/rails/responders/html/singular_resource.rb +59 -0
  50. data/lib/cuprum/rails/responders/html.rb +11 -0
  51. data/lib/cuprum/rails/responders/html_responder.rb +129 -0
  52. data/lib/cuprum/rails/responders/json/resource.rb +60 -0
  53. data/lib/cuprum/rails/responders/json.rb +10 -0
  54. data/lib/cuprum/rails/responders/json_responder.rb +122 -0
  55. data/lib/cuprum/rails/responders/matching.rb +145 -0
  56. data/lib/cuprum/rails/responders/serialization.rb +36 -0
  57. data/lib/cuprum/rails/responders.rb +15 -0
  58. data/lib/cuprum/rails/responses/html/redirect_response.rb +29 -0
  59. data/lib/cuprum/rails/responses/html/render_response.rb +52 -0
  60. data/lib/cuprum/rails/responses/html.rb +11 -0
  61. data/lib/cuprum/rails/responses/json_response.rb +29 -0
  62. data/lib/cuprum/rails/responses.rb +11 -0
  63. data/lib/cuprum/rails/routes.rb +166 -0
  64. data/lib/cuprum/rails/routing/plural_routes.rb +26 -0
  65. data/lib/cuprum/rails/routing/singular_routes.rb +24 -0
  66. data/lib/cuprum/rails/routing.rb +11 -0
  67. data/lib/cuprum/rails/rspec/command_contract.rb +460 -0
  68. data/lib/cuprum/rails/rspec/define_route_contract.rb +84 -0
  69. data/lib/cuprum/rails/rspec.rb +8 -0
  70. data/lib/cuprum/rails/serializers/json/active_record_serializer.rb +24 -0
  71. data/lib/cuprum/rails/serializers/json/array_serializer.rb +40 -0
  72. data/lib/cuprum/rails/serializers/json/attributes_serializer.rb +217 -0
  73. data/lib/cuprum/rails/serializers/json/error_serializer.rb +24 -0
  74. data/lib/cuprum/rails/serializers/json/hash_serializer.rb +44 -0
  75. data/lib/cuprum/rails/serializers/json/identity_serializer.rb +21 -0
  76. data/lib/cuprum/rails/serializers/json/serializer.rb +66 -0
  77. data/lib/cuprum/rails/serializers/json.rb +40 -0
  78. data/lib/cuprum/rails/serializers.rb +10 -0
  79. data/lib/cuprum/rails/version.rb +59 -0
  80. data/lib/cuprum/rails.rb +31 -0
  81. metadata +286 -0
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ require 'cuprum/rails'
6
+
7
+ module Cuprum::Rails
8
+ # @api private
9
+ #
10
+ # Implements a controller action.
11
+ #
12
+ # @note This class should not be initialized directly. Instead, use the
13
+ # Cuprum::Rails::Controller.action class method to define an action.
14
+ class ControllerAction
15
+ extend Forwardable
16
+
17
+ # @param configuration [Cuprum::Rails::Controllers::Configuration] the
18
+ # configuration for the originating controller.
19
+ # @param action_class [Class] The class of the action command. Must be
20
+ # constructible with keyword :resource.
21
+ # @param action_name [String, Symbol] The name of the action.
22
+ # @param member_action [Boolean] True if the action acts on a collection
23
+ # item, not on the collection as a whole.
24
+ # @param serializers
25
+ # [Hash<Class, Object>, Hash<Symbol, Hash<Class, Object>>] The serializers
26
+ # for converting result values into serialized data.
27
+ def initialize(
28
+ configuration,
29
+ action_class:,
30
+ action_name:,
31
+ member_action: false,
32
+ serializers: {}
33
+ )
34
+ @configuration = configuration
35
+ @action_class = action_class
36
+ @action_name = action_name
37
+ @member_action = !!member_action # rubocop:disable Style/DoubleNegation
38
+ @serializers = serializers
39
+ end
40
+
41
+ # @return [Class] the class of the action command.
42
+ attr_reader :action_class
43
+
44
+ # @return [String, Symbol] the name of the action.
45
+ attr_reader :action_name
46
+
47
+ # @return [Cuprum::Rails::Controllers::Configuration] the configuration for
48
+ # the originating controller.
49
+ attr_reader :configuration
50
+
51
+ # @return [Hash<Class, Object>, Hash<Symbol, Hash<Class, Object>>] the
52
+ # serializers for converting result values into serialized data.
53
+ attr_reader :serializers
54
+
55
+ # @!method resource
56
+ # @return [Cuprum::Rails::Resource] the resource defined for the
57
+ # controller.
58
+
59
+ # @!method responder_for(format)
60
+ # Finds the configured responder for the requested format.
61
+ #
62
+ # @param format [Symbol] The format to respond to.
63
+ #
64
+ # @return [Class] the responder class defined for the format.
65
+ #
66
+ # @raise [Cuprum::Rails::Controller::UnknownFormatError] if the controller
67
+ # does not define a responder for the given format.
68
+
69
+ def_delegators :@configuration,
70
+ :resource,
71
+ :responder_for
72
+
73
+ # Executes the controller action.
74
+ #
75
+ # 1. Initializes the action command with the resource.
76
+ # 2. Calls the command with the request.
77
+ # 3. Builds the responder with the resource and action metadata.
78
+ # 4. Calls the responder with the action result.
79
+ #
80
+ # @param request [ActionDispatch::Request] The request to process.
81
+ #
82
+ # @return [#call] The response object.
83
+ def call(request)
84
+ responder = build_responder(request)
85
+ action = action_class.new(resource: resource)
86
+ result = action.call(request: request)
87
+
88
+ responder.call(result)
89
+ end
90
+
91
+ # @return [Boolean] true if the action acts on a collection item, not on the
92
+ # collection as a whole.
93
+ def member_action?
94
+ @member_action
95
+ end
96
+
97
+ private
98
+
99
+ def build_responder(request)
100
+ responder_class = responder_for(request.format)
101
+
102
+ responder_class.new(
103
+ action_name: action_name,
104
+ member_action: member_action?,
105
+ resource: resource,
106
+ serializers: merge_serializers_for(request.format)
107
+ )
108
+ end
109
+
110
+ def merge_serializers_for(format)
111
+ scoped_serializers(configuration.serializers, format: format)
112
+ .merge(scoped_serializers(serializers, format: format))
113
+ end
114
+
115
+ def scoped_serializers(serializers, format:)
116
+ serializers
117
+ .select { |key, _| key.is_a?(Class) }
118
+ .merge(serializers.fetch(format, {}))
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/rails/controller_action'
4
+ require 'cuprum/rails/controllers/class_methods'
5
+
6
+ module Cuprum::Rails::Controllers::ClassMethods
7
+ # Provides a DSL for defining controller actions.
8
+ module Actions
9
+ # Defines a controller action.
10
+ #
11
+ # @param action_name [String, Symbol] The name of the action.
12
+ # @param action_class [Class] The class of the action command. Must be
13
+ # constructible with keyword :resource.
14
+ # @param member [Boolean] True if the action acts on a collection item, not
15
+ # on the collection as a whole.
16
+ def action(action_name, action_class, member: false)
17
+ validate_name(action_name, as: 'action name')
18
+ validate_class(action_class, as: 'action class')
19
+
20
+ action_name = action_name.intern
21
+ own_actions[action_name] = Cuprum::Rails::ControllerAction.new(
22
+ configuration,
23
+ action_class: action_class,
24
+ action_name: action_name,
25
+ member_action: member
26
+ )
27
+
28
+ define_action(action_name)
29
+ end
30
+
31
+ # @return [Hash<Symbol, Cuprum::Rails::ControllerAction>] the actions
32
+ # defined for the controller.
33
+ def actions
34
+ ancestors
35
+ .select { |ancestor| ancestor.respond_to?(:own_actions) }
36
+ .reverse_each
37
+ .map(&:own_actions)
38
+ .reduce(&:merge)
39
+ end
40
+
41
+ # @private
42
+ def own_actions
43
+ @own_actions ||= {}
44
+ end
45
+
46
+ private
47
+
48
+ def define_action(action_name)
49
+ define_method(action_name) do
50
+ request = Cuprum::Rails::Request.build(request: self.request)
51
+ action = self.class.actions[action_name]
52
+ response = action.call(request)
53
+ response.call(self)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/rails/controllers/class_methods'
4
+ require 'cuprum/rails/controllers/configuration'
5
+ require 'cuprum/rails/serializers/json'
6
+
7
+ module Cuprum::Rails::Controllers::ClassMethods
8
+ # Provides a DSL for defining controller configuration.
9
+ module Configuration
10
+ # @return [Cuprum::Rails::Controllers::Configuration] the configured options
11
+ # for the controller.
12
+ def configuration
13
+ Cuprum::Rails::Controllers::Configuration.new(self)
14
+ end
15
+
16
+ # @private
17
+ def own_responders
18
+ @own_responders ||= {}
19
+ end
20
+
21
+ # Returns the resource defined for the controller.
22
+ #
23
+ # Controller subclasses must override this method.
24
+ #
25
+ # @return [Cuprum::Rails::Resource] the resource defined for the
26
+ # controller.
27
+ #
28
+ # @raise [Cuprum::Rails::Controller::UndefinedResourceError] if the
29
+ # controller does not define a resource.
30
+ def resource
31
+ raise Cuprum::Rails::Controller::UndefinedResourceError,
32
+ "no resource defined for #{name}"
33
+ end
34
+
35
+ # Assigns a responder class to handle requests of the specified format.
36
+ #
37
+ # @param format [String, Symbol] The request format to handle.
38
+ # @param responder_class [Class] The class of responder.
39
+ def responder(format, responder_class)
40
+ validate_name(format, as: 'format')
41
+ validate_class(responder_class, as: 'responder')
42
+
43
+ own_responders[format.intern] = responder_class
44
+ end
45
+
46
+ # @return [Hash<Symbol, Class>] the responder classes defined for the
47
+ # controller, by format.
48
+ def responders
49
+ ancestors
50
+ .select { |ancestor| ancestor.respond_to?(:own_responders) }
51
+ .reverse_each
52
+ .map(&:own_responders)
53
+ .reduce(&:merge)
54
+ end
55
+
56
+ # @return [Hash<Class, Object>, Hash<Symbol, Hash<Class, Object>>] the
57
+ # serializers for converting result values into serialized data.
58
+ def serializers
59
+ {
60
+ json: Cuprum::Rails::Serializers::Json.default_serializers
61
+ }
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/rails/controllers/class_methods'
4
+
5
+ module Cuprum::Rails::Controllers::ClassMethods
6
+ # @private
7
+ module Validations
8
+ private
9
+
10
+ def validate_class(value, as:) # rubocop:disable Naming/MethodParameterName
11
+ return if value.is_a?(Class)
12
+
13
+ raise ArgumentError, "#{as} must be a Class", caller(1..-1)
14
+ end
15
+
16
+ def validate_name(value, as:) # rubocop:disable Naming/MethodParameterName
17
+ raise ArgumentError, "#{as} can't be blank", caller(1..-1) if value.nil?
18
+
19
+ unless value.is_a?(String) || value.is_a?(Symbol)
20
+ raise ArgumentError,
21
+ "#{as} must be a String or Symbol",
22
+ caller(1..-1)
23
+ end
24
+
25
+ return unless value.to_s.empty?
26
+
27
+ raise ArgumentError, "#{as} can't be blank", caller(1..-1)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/rails/controllers'
4
+
5
+ module Cuprum::Rails::Controllers
6
+ # Namespace for controller class-specific functionality.
7
+ module ClassMethods
8
+ autoload :Actions,
9
+ 'cuprum/rails/controllers/class_methods/actions'
10
+ autoload :Configuration,
11
+ 'cuprum/rails/controllers/class_methods/configuration'
12
+ autoload :Validations,
13
+ 'cuprum/rails/controllers/class_methods/validations'
14
+ end
15
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ require 'cuprum/rails/controllers'
6
+
7
+ module Cuprum::Rails::Controllers
8
+ # Data object that stores a controller's configuration.
9
+ class Configuration
10
+ extend Forwardable
11
+
12
+ # @param controller [#resource, #responders] The controller to delegate
13
+ # configuration.
14
+ def initialize(controller)
15
+ @controller = controller
16
+ end
17
+
18
+ # @return [#resource, #responders] the controller to delegate configuration.
19
+ attr_reader :controller
20
+
21
+ # @!method resource
22
+ # @return [Cuprum::Rails::Resource] the resource defined for the
23
+ # controller.
24
+
25
+ # @!method responders
26
+ # @return [Hash<Symbol, Class>] the responder classes defined for the
27
+ # controller, by format.
28
+
29
+ # @!method serializers
30
+ # @return [Hash<Class, Object>, Hash<Symbol, Hash<Class, Object>>] the
31
+ # serializers for converting result values into serialized data.
32
+
33
+ def_delegators :@controller,
34
+ :resource,
35
+ :responders,
36
+ :serializers
37
+
38
+ # Finds the configured responder for the requested format.
39
+ #
40
+ # @param format [Symbol] The format to respond to.
41
+ #
42
+ # @return [Class] the responder class defined for the format.
43
+ #
44
+ # @raise [Cuprum::Rails::Controller::UnknownFormatError] if the controller
45
+ # does not define a responder for the given format.
46
+ def responder_for(format)
47
+ responders.fetch(format) do
48
+ raise Cuprum::Rails::Controller::UnknownFormatError,
49
+ "no responder registered for format #{format.inspect}"
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/rails'
4
+
5
+ module Cuprum::Rails
6
+ # Namespace for controller-specific functionality.
7
+ module Controllers
8
+ autoload :Configuration, 'cuprum/rails/controllers/configuration'
9
+ end
10
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/error'
4
+
5
+ require 'cuprum/rails/errors'
6
+
7
+ module Cuprum::Rails::Errors
8
+ # Error class when a parameters hash does not include a resource.
9
+ class MissingParameters < Cuprum::Error
10
+ # Short string used to identify the type of error.
11
+ TYPE = 'cuprum.rails.errors.missing_parameters'
12
+
13
+ # @param resource_name [Cuprum::Rails::Resource] The name of the resource.
14
+ def initialize(resource_name:)
15
+ @resource_name = resource_name
16
+
17
+ super(message: default_message, resource_name: resource_name)
18
+ end
19
+
20
+ # @return [Cuprum::Rails::Resource] the name of the resource.
21
+ attr_reader :resource_name
22
+
23
+ private
24
+
25
+ def as_json_data
26
+ { 'resource_name' => resource_name }
27
+ end
28
+
29
+ def default_message
30
+ "The #{resource_name.inspect} parameter is missing or empty"
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/error'
4
+
5
+ require 'cuprum/rails/errors'
6
+
7
+ module Cuprum::Rails::Errors
8
+ # Error class when a parameters hash does not include a primary key.
9
+ class MissingPrimaryKey < Cuprum::Error
10
+ # Short string used to identify the type of error.
11
+ TYPE = 'cuprum.rails.errors.missing_primary_key'
12
+
13
+ # @param primary_key [String, Symbol] The name of the resource primary key.
14
+ # @param resource_name [Cuprum::Rails::Resource] The name of the resource.
15
+ def initialize(primary_key:, resource_name:)
16
+ @primary_key = primary_key
17
+ @resource_name = resource_name
18
+
19
+ super(
20
+ message: default_message,
21
+ primary_key: primary_key,
22
+ resource_name: resource_name
23
+ )
24
+ end
25
+
26
+ # @return [String] the name of the resource primary key.
27
+ attr_reader :primary_key
28
+
29
+ # @return [Cuprum::Rails::Resource] the name of the resource.
30
+ attr_reader :resource_name
31
+
32
+ private
33
+
34
+ def as_json_data
35
+ {
36
+ 'primary_key' => primary_key,
37
+ 'resource_name' => resource_name
38
+ }
39
+ end
40
+
41
+ def default_message
42
+ "Unable to find #{resource_name} because the #{primary_key.inspect}" \
43
+ ' parameter is missing or empty'
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/error'
4
+
5
+ require 'cuprum/rails/errors'
6
+
7
+ module Cuprum::Rails::Errors
8
+ # Error class when a resource does not define permitted attributes.
9
+ class UndefinedPermittedAttributes < Cuprum::Error
10
+ # Short string used to identify the type of error.
11
+ TYPE = 'cuprum.rails.errors.undefined_permitted_attributes'
12
+
13
+ # @param resource_name [Cuprum::Rails::Resource] The name of the resource.
14
+ def initialize(resource_name:)
15
+ @resource_name = resource_name
16
+
17
+ super(message: default_message, resource_name: resource_name)
18
+ end
19
+
20
+ # @return [Cuprum::Rails::Resource] the name of the resource.
21
+ attr_reader :resource_name
22
+
23
+ private
24
+
25
+ def as_json_data
26
+ { 'resource_name' => resource_name }
27
+ end
28
+
29
+ def default_message
30
+ "Resource #{resource_name.inspect} does not define" \
31
+ ' permitted attributes'
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/rails'
4
+
5
+ module Cuprum::Rails
6
+ # Namespace for custom Cuprum::Rails error classes.
7
+ module Errors; end
8
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'stannum/errors'
4
+
5
+ require 'cuprum/rails'
6
+
7
+ module Cuprum::Rails
8
+ # Maps errors from a validated Rails model to a Stannum::Errors object.
9
+ class MapErrors
10
+ # @return [MapErrors] a memoized instance of the class.
11
+ def self.instance
12
+ @instance ||= new
13
+ end
14
+
15
+ # Maps an ActiveModel::Errors object to a Stannum::Errors object.
16
+ #
17
+ # @param native_errors [ActiveModel::Errors] The Rails error object.
18
+ #
19
+ # @return [Stannum::Errors] the generated errors object.
20
+ def call(native_errors:)
21
+ unless native_errors.is_a?(ActiveModel::Errors)
22
+ raise ArgumentError,
23
+ 'native_errors must be an instance of ActiveModel::Errors'
24
+ end
25
+
26
+ map_errors(native_errors: native_errors)
27
+ end
28
+
29
+ private
30
+
31
+ def map_errors(native_errors:)
32
+ errors = Stannum::Errors.new
33
+
34
+ native_errors.each do |error|
35
+ attribute = error.attribute
36
+ scoped = attribute == :base ? errors : errors[attribute]
37
+
38
+ scoped.add(error.type, message: error.message, **error.options)
39
+ end
40
+
41
+ errors
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/collections/query'
4
+
5
+ require 'cuprum/rails'
6
+ require 'cuprum/rails/query_builder'
7
+
8
+ module Cuprum::Rails
9
+ # Interface for performing query operations on a Rails collection.
10
+ class Query < Cuprum::Collections::Query
11
+ extend Forwardable
12
+ include Enumerable
13
+
14
+ def_delegators :@native_query,
15
+ :each,
16
+ :exists?,
17
+ :to_a
18
+
19
+ # @param record_class [Class] The class of the collection items. Must be a
20
+ # subclass of ActiveRecord::Base.
21
+ # @param native_query [ActiveRecord::Relation] A relation used to scope the
22
+ # query.
23
+ def initialize(record_class, native_query: nil)
24
+ super()
25
+
26
+ default_order = { record_class.primary_key => :asc }
27
+ @native_query = native_query || record_class.all.order(default_order)
28
+ @record_class = record_class
29
+ @limit = nil
30
+ @offset = nil
31
+ @order = default_order
32
+ end
33
+
34
+ # @return [Class] the class of the collection items.
35
+ attr_reader :record_class
36
+
37
+ protected
38
+
39
+ def query_builder
40
+ Cuprum::Rails::QueryBuilder.new(self)
41
+ end
42
+
43
+ def reset!
44
+ @native_query.reset
45
+
46
+ self
47
+ end
48
+
49
+ def with_limit(count)
50
+ @native_query = @native_query.limit(count)
51
+
52
+ super
53
+ end
54
+
55
+ def with_native_query(native_query)
56
+ @native_query = native_query
57
+
58
+ self
59
+ end
60
+
61
+ def with_offset(count)
62
+ @native_query = @native_query.offset(count)
63
+
64
+ super
65
+ end
66
+
67
+ def with_order(order)
68
+ @native_query = @native_query.reorder(order)
69
+
70
+ super
71
+ end
72
+
73
+ private
74
+
75
+ attr_reader :native_query
76
+ end
77
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/collections/query_builder'
4
+
5
+ require 'cuprum/rails'
6
+
7
+ module Cuprum::Rails
8
+ # Applies filter operations for a Rails collection query.
9
+ class QueryBuilder < Cuprum::Collections::QueryBuilder
10
+ # @param base_query [Cuprum::Rails::Query] The query to build.
11
+ def initialize(base_query)
12
+ super
13
+
14
+ @native_query = base_query.send(:native_query)
15
+ end
16
+
17
+ private
18
+
19
+ attr_reader :native_query
20
+
21
+ def build_native_query(criteria)
22
+ native_query.where(
23
+ criteria
24
+ .map do |(attribute, operator, value)|
25
+ send(operator, attribute, value)
26
+ end
27
+ .join(' AND ')
28
+ )
29
+ end
30
+
31
+ def build_query(criteria)
32
+ super.send(:with_native_query, build_native_query(criteria))
33
+ end
34
+
35
+ def equal(attribute, value)
36
+ return sanitize("#{attribute} IS NULL") if value.nil?
37
+
38
+ sanitize("#{attribute} = :value", value: value)
39
+ end
40
+
41
+ def greater_than(attribute, value)
42
+ sanitize("#{attribute} > :value", value: value)
43
+ end
44
+
45
+ def greater_than_or_equal_to(attribute, value)
46
+ sanitize("#{attribute} >= :value", value: value)
47
+ end
48
+
49
+ def less_than(attribute, value)
50
+ sanitize("#{attribute} < :value", value: value)
51
+ end
52
+
53
+ def less_than_or_equal_to(attribute, value)
54
+ sanitize("#{attribute} <= :value", value: value)
55
+ end
56
+
57
+ def not_equal(attribute, value)
58
+ return sanitize("#{attribute} IS NOT NULL") if value.nil?
59
+
60
+ sanitize("(#{attribute} != :value OR #{attribute} IS NULL)", value: value)
61
+ end
62
+
63
+ def not_one_of(attribute, value)
64
+ sanitize(
65
+ "(#{attribute} NOT IN (:value) OR #{attribute} IS NULL)",
66
+ value: value
67
+ )
68
+ end
69
+
70
+ def one_of(attribute, value)
71
+ sanitize("#{attribute} IN (:value)", value: value)
72
+ end
73
+
74
+ def sanitize(*conditions)
75
+ ActiveRecord::Base.sanitize_sql_for_conditions(conditions)
76
+ end
77
+ end
78
+ end