shamu 0.0.13 → 0.0.14

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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +55 -20
  3. data/Gemfile +3 -3
  4. data/Gemfile.lock +13 -11
  5. data/circle.yml +1 -1
  6. data/lib/shamu/attributes/assignment.rb +44 -5
  7. data/lib/shamu/attributes/camel_case.rb +21 -0
  8. data/lib/shamu/attributes/validation.rb +13 -1
  9. data/lib/shamu/attributes/validators/valid_validator.rb +14 -0
  10. data/lib/shamu/attributes/validators.rb +7 -0
  11. data/lib/shamu/attributes.rb +13 -8
  12. data/lib/shamu/auditing/auditing_service.rb +1 -5
  13. data/lib/shamu/auditing/support.rb +14 -2
  14. data/lib/shamu/entities/active_record.rb +16 -2
  15. data/lib/shamu/entities/active_record_soft_destroy.rb +7 -3
  16. data/lib/shamu/entities/entity.rb +1 -1
  17. data/lib/shamu/entities/entity_lookup_service.rb +137 -0
  18. data/lib/shamu/entities/entity_path.rb +6 -9
  19. data/lib/shamu/entities/list.rb +8 -2
  20. data/lib/shamu/entities/list_scope/paging.rb +3 -3
  21. data/lib/shamu/entities/list_scope/sorting.rb +21 -2
  22. data/lib/shamu/entities/list_scope/window_paging.rb +96 -0
  23. data/lib/shamu/entities/list_scope.rb +2 -2
  24. data/lib/shamu/entities/opaque_entity_lookup_service.rb +59 -0
  25. data/lib/shamu/entities/opaque_id.rb +54 -0
  26. data/lib/shamu/entities/paged_list.rb +137 -0
  27. data/lib/shamu/entities.rb +5 -1
  28. data/lib/shamu/events/active_record/service.rb +1 -2
  29. data/lib/shamu/events/in_memory/service.rb +1 -2
  30. data/lib/shamu/features/conditions/percentage.rb +3 -3
  31. data/lib/shamu/features/features_service.rb +2 -2
  32. data/lib/shamu/features/toggle.rb +2 -3
  33. data/lib/shamu/json_api/context.rb +0 -1
  34. data/lib/shamu/json_api/rails/controller.rb +0 -2
  35. data/lib/shamu/rails/controller.rb +0 -1
  36. data/lib/shamu/rails/entity.rb +1 -1
  37. data/lib/shamu/security/policy.rb +1 -2
  38. data/lib/shamu/services/active_record.rb +16 -0
  39. data/lib/shamu/services/active_record_crud.rb +32 -22
  40. data/lib/shamu/services/lazy_transform.rb +31 -0
  41. data/lib/shamu/services/request_support.rb +3 -2
  42. data/lib/shamu/services/service.rb +11 -3
  43. data/lib/shamu/to_model_id_extension.rb +2 -1
  44. data/lib/shamu/version.rb +2 -1
  45. data/shamu.gemspec +2 -1
  46. data/spec/lib/shamu/active_record_support.rb +6 -0
  47. data/spec/lib/shamu/attributes/assignment_spec.rb +69 -5
  48. data/spec/lib/shamu/attributes/camel_case_spec.rb +33 -0
  49. data/spec/lib/shamu/attributes/validation_spec.rb +9 -1
  50. data/spec/lib/shamu/attributes_spec.rb +4 -0
  51. data/spec/lib/shamu/entities/active_record_spec.rb +27 -0
  52. data/spec/lib/shamu/entities/entity_lookup_models.rb +11 -0
  53. data/spec/lib/shamu/entities/entity_lookup_service_spec.rb +77 -0
  54. data/spec/lib/shamu/entities/entity_path_spec.rb +3 -4
  55. data/spec/lib/shamu/entities/list_scope/paging_spec.rb +7 -3
  56. data/spec/lib/shamu/entities/list_scope/sorting_spec.rb +1 -7
  57. data/spec/lib/shamu/entities/opaque_entity_lookup_service_spec.rb +39 -0
  58. data/spec/lib/shamu/entities/opaque_id_spec.rb +30 -0
  59. data/spec/lib/shamu/entities/paged_list_spec.rb +170 -0
  60. data/spec/lib/shamu/services/active_record_crud_spec.rb +10 -1
  61. data/spec/lib/shamu/services/lazy_transform_spec.rb +14 -0
  62. data/spec/lib/shamu/to_model_id_extension_spec.rb +5 -1
  63. data/spec/support/active_record.rb +1 -1
  64. metadata +24 -4
@@ -158,11 +158,11 @@ module Shamu
158
158
 
159
159
  # @param [String] path of the default config file.
160
160
  # @return [String]
161
- def default_config_path=( path )
161
+ def default_config_path=( path ) # rubocop:disable Style/TrivialAccessors
162
162
  @default_config_path = path
163
163
  end
164
164
  end
165
165
 
166
166
  end
167
167
  end
168
- end
168
+ end
@@ -76,7 +76,6 @@ module Shamu
76
76
  !retire_at || context.time > retire_at
77
77
  end
78
78
 
79
-
80
79
  def initialize( attributes )
81
80
  fail ArgumentError, "Must provide a retire_at attribute for '#{ attributes[ 'name' ] }' toggle." unless attributes["retire_at"] # rubocop:disable Metrics/LineLength
82
81
  fail ArgumentError, "Type must be one of #{ TYPES } for '#{ attributes[ 'name' ] }' toggle." unless TYPES.include?( attributes["type"] ) # rubocop:disable Metrics/LineLength
@@ -107,7 +106,7 @@ module Shamu
107
106
  def load_from_path( path, toggles, state )
108
107
  path = File.expand_path( path, state.file_path )
109
108
  File.open( path, "r" ) do |file|
110
- yaml = YAML.load( file.read )
109
+ yaml = YAML.load( file.read ) # rubocop:disable Security/YAMLLoad
111
110
  parse_node( yaml, toggles, ParsingState.new( state.name, File.dirname( path ) ) )
112
111
  end
113
112
  end
@@ -146,4 +145,4 @@ module Shamu
146
145
  ParsingState = Struct.new( :name, :file_path )
147
146
  end
148
147
  end
149
- end
148
+ end
@@ -95,7 +95,6 @@ module Shamu
95
95
  presenter
96
96
  end
97
97
 
98
-
99
98
  # @return [Hash] of request param options to be output in the response meta.
100
99
  def params_meta
101
100
  return unless fields.any?
@@ -234,7 +234,6 @@ module Shamu
234
234
  presenters: presenters == :not_set ? json_context_presenters : presenters
235
235
  end
236
236
 
237
-
238
237
  # See (Shamu::Rails::Entity#request_params)
239
238
  def request_params( param_key )
240
239
  if relationships = json_request_payload[ :relationships ]
@@ -291,7 +290,6 @@ module Shamu
291
290
  end
292
291
  end
293
292
 
294
-
295
293
  def json_context_fields
296
294
  params[:fields]
297
295
  end
@@ -59,7 +59,6 @@ module Shamu
59
59
  secure_services.any? { |s| s.permit?( *args ) }
60
60
  end
61
61
 
62
-
63
62
  # @!visibility public
64
63
  #
65
64
  # Gets the security principal for the current request.
@@ -21,7 +21,7 @@ module Shamu
21
21
  return unless service.respond_to?( :request_for )
22
22
  return unless request = service.request_for( action, entity )
23
23
 
24
- param_key ||= entity.model_name.param_key
24
+ param_key ||= entity.model_name.param_key if entity
25
25
  request.assign_attributes( request_params( param_key ) )
26
26
 
27
27
  service.authorize!( action, entity, request ) if service.respond_to?( :authorize! )
@@ -55,7 +55,6 @@ module Shamu
55
55
  @roles = roles || []
56
56
  end
57
57
 
58
-
59
58
  # Authorize the given `action` on the given resource. If it is not
60
59
  # {#permit? permitted} then an exception is raised.
61
60
  #
@@ -286,4 +285,4 @@ module Shamu
286
285
 
287
286
  end
288
287
  end
289
- end
288
+ end
@@ -53,6 +53,22 @@ module Shamu
53
53
  end
54
54
  end
55
55
 
56
+ # @param [ActiveRecord::Relation, Enumerable] source
57
+ # @return [Boolean] true if the source supports paging and has paging
58
+ # constraints set.
59
+ def source_paged?( source )
60
+ source.respond_to?( :current_page ) && !!source.current_page
61
+ end
62
+
63
+ # (see Service#build_entity_list)
64
+ def build_entity_list( source )
65
+ if source_paged?( source )
66
+ Shamu::Entities::PagedList.new( source )
67
+ else
68
+ super
69
+ end
70
+ end
71
+
56
72
  end
57
73
  end
58
74
  end
@@ -43,7 +43,7 @@ module Shamu
43
43
  extend ActiveSupport::Concern
44
44
 
45
45
  # Known DSL methods defined by {ActiveRecordCrud}.
46
- DSL_METHODS = %i( create update change destroy find list lookup finders ).freeze
46
+ DSL_METHODS = %i[ create update change destroy find list lookup finders ].freeze
47
47
 
48
48
  included do |base|
49
49
  base.include Shamu::Services::RequestSupport
@@ -173,15 +173,21 @@ module Shamu
173
173
  # @yieldparam (see .apply_changes)
174
174
  # @return [Result] the result of the request.
175
175
  # @return [void]
176
- def change( method = :update, &block )
176
+ def change( method = :update, &block ) # rubocop:disable Metrics/PerceivedComplexity, Metrics/AbcSize
177
177
  define_method method do |id, params = nil|
178
178
  klass = request_class( method )
179
179
 
180
- id, params = id.id, id if id.is_a?( klass ) && !params
180
+ params, id = id, id[ :id ] if !params && !id.respond_to?( :to_model_id )
181
181
 
182
- with_request params, klass do |request|
183
- record = model_class.find( id.to_model_id )
184
- authorize! method, build_entity( record ), request
182
+ with_partial_request params, klass do |request|
183
+ record = model_class.find( id.to_model_id || request.id )
184
+ entity = build_entity( record )
185
+
186
+ backfill_attributes = entity.to_attributes( only: request.unassigned_attributes )
187
+ request.assign_attributes backfill_attributes
188
+ next unless request.valid?
189
+
190
+ authorize! method, entity, request
185
191
 
186
192
  request.apply_to( record )
187
193
 
@@ -213,6 +219,26 @@ module Shamu
213
219
  change :update, &block
214
220
  end
215
221
 
222
+ # Define a `destroy( id )` method that takes an {Entities::Entity} {Entities::Entity#id}
223
+ # and destroys the resource.
224
+ #
225
+ # @param [ActiveRecord::Relation] default_scope to use when finding
226
+ # records.
227
+ # @return [void]
228
+ def destroy( default_scope = model_class.all )
229
+ define_method :destroy do |params|
230
+ klass = request_class( :destroy )
231
+
232
+ params = { id: params } if params.respond_to?( :to_model_id )
233
+
234
+ with_request params, klass do |request|
235
+ record = default_scope.find( request.id )
236
+ authorize! :destroy, build_entity( record )
237
+ next record unless record.destroy
238
+ end
239
+ end
240
+ end
241
+
216
242
  # Define a private method `apply_changes` on the service used by the
217
243
  # {.create} and {.change} defined methods to apply changes in a
218
244
  # {Request} to the model.
@@ -311,22 +337,6 @@ module Shamu
311
337
  end
312
338
  end
313
339
 
314
- # Define a `destroy( id )` method that takes an {Entities::Entity} {Entities::Entity#id}
315
- # and destroys the resource.
316
- #
317
- # @param [ActiveRecord::Relation] default_scope to use when finding
318
- # records.
319
- # @return [void]
320
- def destroy( default_scope = model_class.all )
321
- define_method :destroy do |id|
322
- wrap_not_found do
323
- record = default_scope.find( id.to_model_id )
324
- authorize! :destroy, build_entity( record )
325
- record.destroy
326
- end
327
- end
328
- end
329
-
330
340
  # Define a private `build_entities( records )` method that
331
341
  # constructs an {Entities::Entity} for each of the given `records`.
332
342
  #
@@ -53,6 +53,22 @@ module Shamu
53
53
  end
54
54
  end
55
55
 
56
+ # Get the last transformed value without transforming the entire list.
57
+ # @overload last(n)
58
+ # @overload last
59
+ # @return [Object]
60
+ def last( *args )
61
+ if args.any?
62
+ transformed.last( *args )
63
+ else
64
+ return @last if defined? @last
65
+ @last = begin
66
+ value = source.last
67
+ raise_if_not_transformed( transformer.call( [ value ] ) ).last unless value.nil?
68
+ end
69
+ end
70
+ end
71
+
56
72
  # @return [Boolean] true if there are no source values.
57
73
  def empty?
58
74
  source.empty?
@@ -80,6 +96,21 @@ module Shamu
80
96
  end
81
97
  end
82
98
 
99
+ # For all other methods, force a transform then delegate to the
100
+ # transformed list.
101
+
102
+ def method_missing( name, *args, &block )
103
+ if respond_to_missing?( name, false )
104
+ source.public_send( name, *args, &block )
105
+ else
106
+ super
107
+ end
108
+ end
109
+
110
+ def respond_to_missing?( *args )
111
+ super || source.respond_to?( *args )
112
+ end
113
+
83
114
  private
84
115
 
85
116
  attr_reader :source
@@ -88,15 +88,16 @@ module Shamu
88
88
  # @see #with_request
89
89
  def with_partial_request( params, request_class, &block )
90
90
  request = request_class.coerce( params )
91
+ sources = yield( request )
92
+
91
93
  # Make sure the request captures errors even if the block doesn't
92
94
  # check
93
95
  request.valid?
94
- sources = yield( request )
95
96
 
96
97
  if sources.is_a?( Result )
97
98
  sources
98
99
  else
99
- result request, *Array.wrap( sources )
100
+ result( *Array.wrap( sources ), request: request )
100
101
  end
101
102
  end
102
103
 
@@ -101,7 +101,7 @@ module Shamu
101
101
  #
102
102
  # @param [Enumerable] records the raw list of records.
103
103
  # @yield (record)
104
- # @yieldparam [Object] record the raw value from the `list` to to
104
+ # @yieldparam [Enumerable<Object>] records the raw values from the `list` to
105
105
  # transform to an {Entities::Entity}.
106
106
  # @yieldreturn [Entities::Entity]
107
107
  # @return [Entities::List]
@@ -112,9 +112,17 @@ module Shamu
112
112
  transformer ||= method( :build_entities )
113
113
  end
114
114
 
115
- Entities::List.new LazyTransform.new( records, &transformer )
115
+ build_entity_list build_records_transform( records, &transformer )
116
116
  end
117
117
 
118
+ def build_records_transform( records, &transformer )
119
+ LazyTransform.new( records, &transformer )
120
+ end
121
+
122
+ def build_entity_list( source )
123
+ Entities::List.new source
124
+ end
125
+
118
126
  # @!visibility public
119
127
  # Match a list of records with the ids used to look up those records.
120
128
  # Uses a {Entities::NullEntity} if the id doesn't have a matching record.
@@ -219,7 +227,7 @@ module Shamu
219
227
  # @!visibility public
220
228
  #
221
229
  # Find an associated entity from a dependent service. Attempts to
222
- # effeciently handle multiple requests to lookup associations by caching
230
+ # efficiently handle multiple requests to lookup associations by caching
223
231
  # all the associated entities when {#lookup_association} is used
224
232
  # repeatedly when building an entity.
225
233
  #
@@ -11,6 +11,7 @@ module Shamu
11
11
  Integer.include Integers
12
12
  String.include Strings
13
13
  Array.include Enumerables
14
+ NilClass.include Integers
14
15
 
15
16
  ActiveRecord::Base.include Models if defined? ActiveRecord::Base
16
17
  end
@@ -47,4 +48,4 @@ module Shamu
47
48
  end
48
49
  end
49
50
 
50
- Shamu::ToModelIdExtension.extend!
51
+ Shamu::ToModelIdExtension.extend!
data/lib/shamu/version.rb CHANGED
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Shamu
3
4
  # The primary version number
4
- VERSION_NUMBER = "0.0.13"
5
+ VERSION_NUMBER = "0.0.14"
5
6
 
6
7
  # Version suffix such as 'beta' or 'alpha'
7
8
  VERSION_SUFFIX = ""
data/shamu.gemspec CHANGED
@@ -1,4 +1,5 @@
1
1
  # coding: utf-8
2
+
2
3
  lib = File.expand_path( "../lib", __FILE__ )
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require "shamu/version"
@@ -23,7 +24,7 @@ Gem::Specification.new do |spec|
23
24
  spec.add_dependency "activemodel", ">= 5.0"
24
25
  spec.add_dependency "activesupport", ">= 5.0"
25
26
  spec.add_dependency "scorpion-ioc", "~> 0.6"
26
- spec.add_dependency "multi_json", "~> 1.11.2"
27
+ spec.add_dependency "multi_json", "~> 1.11"
27
28
  spec.add_dependency "rack", ">= 1"
28
29
  spec.add_dependency "listen", "~> 3"
29
30
  spec.add_dependency "crc32", "~> 1"
@@ -1,5 +1,6 @@
1
1
  require "active_record"
2
2
  require "shamu/entities/active_record"
3
+ require "shamu/entities/active_record_soft_destroy"
3
4
 
4
5
  module ActiveRecordSpec
5
6
  class Favorite < ::ActiveRecord::Base
@@ -29,4 +30,9 @@ module ActiveRecordSpec
29
30
  class FavoriteScope < Shamu::Entities::ListScope
30
31
  attribute :name
31
32
  end
33
+
34
+ class FavoriteEntity < Shamu::Entities::Entity
35
+ model :record
36
+ attribute :name, on: :record
37
+ end
32
38
  end
@@ -59,6 +59,49 @@ describe Shamu::Attributes::Assignment do
59
59
  expect( instance.q ).to eq "ABC"
60
60
  end
61
61
 
62
+ describe "#assigned_attributes" do
63
+ let( :klass ) do
64
+ Class.new do
65
+ include Shamu::Attributes
66
+ include Shamu::Attributes::Assignment
67
+
68
+ attribute :name
69
+ attribute :email
70
+ end
71
+ end
72
+
73
+ it "identifies attributes assigned in constructor" do
74
+ instance = klass.new name: "set"
75
+
76
+ expect( instance.assigned_attributes ).to include :name
77
+ expect( instance.assigned_attributes ).not_to include :email
78
+ expect( instance.unassigned_attributes ).to include :email
79
+ end
80
+
81
+ it "does not identity attributes memoized by reading" do
82
+ instance = klass.new name: "set"
83
+
84
+ expect( instance.email ).to be_nil
85
+ expect( instance.assigned_attributes ).to include :name
86
+ expect( instance.assigned_attributes ).not_to include :email
87
+ expect( instance.set?( :email ) ).to be_truthy
88
+ end
89
+
90
+ it "identifies attributes assigned explicitly" do
91
+ instance = klass.new
92
+ instance.name = "set"
93
+
94
+ expect( instance.assigned_attributes ).to include :name
95
+ expect( instance.assigned_attributes ).not_to include :email
96
+ end
97
+
98
+ it "identifies attribute_assigned?" do
99
+ instance = klass.new name: "set"
100
+ expect( instance.name_assigned? ).to be_truthy
101
+ end
102
+ end
103
+
104
+
62
105
  describe "coercion" do
63
106
  it "coerces using given method name" do
64
107
  klass = Class.new( base_klass ) do
@@ -83,6 +126,19 @@ describe Shamu::Attributes::Assignment do
83
126
  expect( instance.label ).to eq "coerced"
84
127
  end
85
128
 
129
+ it "coerces using a class" do
130
+ coerce_class = Class.new do
131
+ def initialize( v ); end
132
+ end
133
+
134
+ klass = Class.new( base_klass ) do
135
+ attribute :label, coerce: coerce_class
136
+ end
137
+
138
+ instance = klass.new( label: "original" )
139
+ expect( instance.label ).to be_a coerce_class
140
+ end
141
+
86
142
  describe "smart" do
87
143
  let( :klass ) do
88
144
  Class.new( base_klass ) do
@@ -91,6 +147,7 @@ describe Shamu::Attributes::Assignment do
91
147
 
92
148
  attribute :user_id
93
149
  attribute :tag_ids
150
+ attribute :id
94
151
  end
95
152
  end
96
153
  let( :instance ) { klass.new }
@@ -109,16 +166,23 @@ describe Shamu::Attributes::Assignment do
109
166
  instance.expire_on = value
110
167
  end
111
168
 
112
- it "coerces nnn_id to an Integer" do
169
+ it "coerces nnn_id to a model id" do
113
170
  value = double
114
- expect( value ).to receive( :to_i )
171
+ expect( value ).to receive( :to_model_id )
115
172
 
116
173
  instance.user_id = value
117
174
  end
118
175
 
119
- it "coerces nnn_ids to an array of Integers" do
176
+ it "coerces id to a model id" do
177
+ value = double
178
+ expect( value ).to receive( :to_model_id )
179
+
180
+ instance.id = value
181
+ end
182
+
183
+ it "coerces nnn_ids to an array o model ids" do
120
184
  value = double
121
- expect( value ).to receive( :to_i )
185
+ expect( value ).to receive( :to_model_id )
122
186
 
123
187
  instance.tag_ids = value
124
188
  end
@@ -126,4 +190,4 @@ describe Shamu::Attributes::Assignment do
126
190
  end
127
191
  end
128
192
 
129
- end
193
+ end
@@ -0,0 +1,33 @@
1
+ require "spec_helper"
2
+
3
+ describe Shamu::Attributes::CamelCase do
4
+ let( :klass ) do
5
+ Class.new do
6
+ include Shamu::Attributes
7
+ include Shamu::Attributes::Assignment
8
+ include Shamu::Attributes::CamelCase
9
+
10
+ attribute :name
11
+ attribute :camel_case
12
+ end
13
+ end
14
+
15
+ it "responds to camelcased version" do
16
+ expect( klass.new( camel_case: "Word" ).camelCase ).to eq "Word"
17
+ end
18
+
19
+ it "assigns camelized version from input args" do
20
+ expect( klass.new( camelCase: "Word" ).camel_case ).to eq "Word"
21
+ end
22
+
23
+ it "allows short non-camelized words" do
24
+ expect( klass.new( name: "Pete" ).name ).to eq "Pete"
25
+ end
26
+
27
+ it "assigns the original attribute" do
28
+ instance = klass.new
29
+ instance.camelCase = "Worked"
30
+
31
+ expect( instance.camel_case ).to eq "Worked"
32
+ end
33
+ end
@@ -50,4 +50,12 @@ describe Shamu::Attributes::Validation do
50
50
  instance.valid?
51
51
  instance.valid?
52
52
  end
53
- end
53
+
54
+ it "supports shamu validators with simple hash names" do
55
+ nested = Class.new( klass ) do
56
+ attribute :nested, valid: true
57
+ end
58
+
59
+ expect( nested.validators ).to include Shamu::Attributes::Validators::ValidValidator
60
+ end
61
+ end
@@ -56,6 +56,10 @@ describe Shamu::Attributes do
56
56
  it "is false if the attribute has not been set" do
57
57
  expect( klass.new ).not_to be_set :name
58
58
  end
59
+
60
+ it "has per attribute set? method" do
61
+ expect( klass.new( name: "Set" ).name_set? ).to be_truthy
62
+ end
59
63
  end
60
64
 
61
65
 
@@ -1,6 +1,7 @@
1
1
  require "spec_helper"
2
2
  require "active_record"
3
3
  require "shamu/entities/active_record"
4
+ require "shamu/entities/active_record_soft_destroy"
4
5
 
5
6
  describe Shamu::Entities::ActiveRecord do
6
7
  use_active_record
@@ -37,6 +38,32 @@ describe Shamu::Entities::ActiveRecord do
37
38
  expect( relation.limit_value ).to eq 25
38
39
  end
39
40
 
41
+ it "filters by window paging" do
42
+ klass = Class.new( ActiveRecordSpec::FavoriteScope ) do
43
+ include Shamu::Entities::ListScope::WindowPaging
44
+ end
45
+
46
+ scope = klass.new( first: 10, after: 30 )
47
+ relation = ActiveRecordSpec::Favorite.by_list_scope( scope )
48
+
49
+ expect( relation.offset_value ).to eq 30
50
+ expect( relation.limit_value ).to eq 10
51
+ end
52
+
53
+ it "filters by inverse window paging" do
54
+ klass = Class.new( ActiveRecordSpec::FavoriteScope ) do
55
+ include Shamu::Entities::ListScope::WindowPaging
56
+ end
57
+
58
+ scope = klass.new
59
+ expect( scope ).to receive( :reverse_sort! ).at_least( :once )
60
+ scope.assign_attributes( last: 10, before: 30 )
61
+ relation = ActiveRecordSpec::Favorite.by_list_scope( scope )
62
+
63
+ expect( relation.offset_value ).to eq 30
64
+ expect( relation.limit_value ).to eq 10
65
+ end
66
+
40
67
  it "filters by dates" do
41
68
  klass = Class.new( ActiveRecordSpec::FavoriteScope ) do
42
69
  include Shamu::Entities::ListScope::Dates
@@ -0,0 +1,11 @@
1
+ module EntityLookupServiceSpecs
2
+ class ExamplesService < Shamu::Services::Service
3
+ end
4
+
5
+ class CustomService < Shamu::Services::Service
6
+ end
7
+
8
+ class ExampleEntity < Shamu::Entities::Entity
9
+ attribute :id
10
+ end
11
+ end
@@ -0,0 +1,77 @@
1
+ require "spec_helper"
2
+ require_relative "entity_lookup_models"
3
+
4
+
5
+ describe Shamu::Entities::EntityLookupService do
6
+
7
+ let( :service ) do
8
+ scorpion.new( Shamu::Entities::EntityLookupService, { "Water" => EntityLookupServiceSpecs::CustomService }, {} )
9
+ end
10
+
11
+ describe "#service_class_for" do
12
+ it "finds default service" do
13
+ expect( service.service_class_for( "EntityLookupServiceSpecs::Example" ) ).to be \
14
+ EntityLookupServiceSpecs::ExamplesService
15
+ end
16
+
17
+ it "uses custom service" do
18
+ expect( service.service_class_for( "Water" ) ).to be EntityLookupServiceSpecs::CustomService
19
+ end
20
+ end
21
+
22
+ describe "#ids" do
23
+ it "maps entities to their entity path" do
24
+ entity = EntityLookupServiceSpecs::ExampleEntity.new( id: 5 )
25
+ expect( service.ids( entity ) ).to eq( [ "EntityLookupServiceSpecs::Example[5]" ] )
26
+ end
27
+ end
28
+
29
+ describe "#record_ids" do
30
+ it "gets the original record id" do
31
+ entity = EntityLookupServiceSpecs::ExampleEntity.new( id: 5 )
32
+ id = service.ids( entity ).first
33
+
34
+ expect( service.record_ids( id ).first ).to eq( 5 )
35
+ end
36
+ end
37
+
38
+ describe "#lookup" do
39
+ let( :examples_service ) { double( EntityLookupServiceSpecs::ExamplesService ) }
40
+ let( :custom_service ) { double( EntityLookupServiceSpecs::CustomService ) }
41
+
42
+ before( :each ) do
43
+ allow( service.scorpion ).to receive( :fetch )
44
+ .with( EntityLookupServiceSpecs::ExamplesService )
45
+ .and_return( examples_service )
46
+
47
+ allow( service.scorpion ).to receive( :fetch )
48
+ .with( EntityLookupServiceSpecs::CustomService )
49
+ .and_return( custom_service )
50
+ end
51
+
52
+ it "finds an entity" do
53
+ expect( examples_service ).to receive( :lookup ).with( "4" ).and_return [ "Found" ]
54
+
55
+ expect( service.lookup( "EntityLookupServiceSpecs::Example[4]" ).to_a ).to eq [ "Found" ]
56
+ end
57
+
58
+ it "batches common entity types" do
59
+ expect( examples_service ).to receive( :lookup ).with( "4", "91" ).and_return( [ "One", "Two" ] )
60
+
61
+ returned = service.lookup( "EntityLookupServiceSpecs::Example[4]", "EntityLookupServiceSpecs::Example[91]" )
62
+ expect( returned.to_a ).to eq [ "One", "Two" ]
63
+ end
64
+
65
+ it "returns in same order" do
66
+ expect( examples_service ).to receive( :lookup ).with( "4", "91" ).and_return( [ "One", "Two" ] )
67
+ expect( custom_service ).to receive( :lookup ).with( "500" ).and_return( [ "Agua" ] )
68
+
69
+ returned = service.lookup( "EntityLookupServiceSpecs::Example[4]",
70
+ "Water[500]",
71
+ "EntityLookupServiceSpecs::Example[91]" )
72
+
73
+ expect( returned.to_a ).to eq [ "One", "Agua", "Two" ]
74
+ end
75
+
76
+ end
77
+ end