cuprum-rails 0.1.0

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