hoodoo 1.6.1 → 1.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -13
- data/lib/hoodoo/active/active_record/finder.rb +138 -2
- data/lib/hoodoo/client/augmented_array.rb +10 -1
- data/lib/hoodoo/client/client.rb +2 -1
- data/lib/hoodoo/client/endpoint/endpoints/http_based.rb +6 -3
- data/lib/hoodoo/services/middleware/middleware.rb +21 -4
- data/lib/hoodoo/services/services/response.rb +66 -11
- data/lib/hoodoo/version.rb +1 -1
- data/spec/active/active_record/finder_spec.rb +117 -0
- data/spec/client/client_spec.rb +36 -5
- data/spec/services/middleware/middleware_exotic_communication_spec.rb +5 -1
- data/spec/services/middleware/middleware_multi_local_spec.rb +4 -1
- data/spec/services/middleware/middleware_multi_remote_spec.rb +21 -17
- data/spec/services/middleware/middleware_spec.rb +21 -2
- data/spec/services/services/response_spec.rb +73 -2
- metadata +41 -41
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
M2I5ZmQyMmU0YTljY2JkNmE1ZGU4YzhhMzdmMzcxMDVmMGZiNzY2Nw==
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 21732b5cd34041936e018495404cb3950ff70b85
|
4
|
+
data.tar.gz: b37d1a9ef5e225653be451d8e775ea07cf5a9690
|
7
5
|
SHA512:
|
8
|
-
metadata.gz:
|
9
|
-
|
10
|
-
NjJkNDZmODE4NTRkODA3ZGY0N2M0OWY5NmZjNTFiNzk0YzQxODVmM2RlZDc4
|
11
|
-
YmViMDFkMWY0MzZlNThkMzk0MWY5ZDRjM2Q1MTRlMTI5YmY4NmE=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
NTQ4OWZjYjA5Y2ZhOWIzN2NkODM4NWUwZTc5MDdiOGE0OTVhNGMxNjY5NzIy
|
14
|
-
MTgwYWExNDk4M2NjNWQ4MDU3ZDJjNzk2Y2ZjYmQyNmU1Y2M2NzhhMDAxNDc3
|
15
|
-
YjkyNmIxYTZjZDNlZGNhMDRmNTc2MDQxNWI3NjFlMDg3ZTIzNzQ=
|
6
|
+
metadata.gz: 39c52f8db587f906e2c377ab9fae3939697b872366c07aace735439538f6412fe12efae8996652b746ab31e8704334329e5d71125ca14c60be978f9352aa0308
|
7
|
+
data.tar.gz: bc4e9624bff7ad092b1ca7e0f86e64c2b8c634ee12649fd22bbb27de26d360dd870dfe4d7644639b5cc30fabbd30294e13c004e9efde77cff07f35fe3879a2eb
|
@@ -53,6 +53,7 @@ module Hoodoo
|
|
53
53
|
model.class_attribute(
|
54
54
|
:nz_co_loyalty_hoodoo_show_id_fields,
|
55
55
|
:nz_co_loyalty_hoodoo_show_id_substitute,
|
56
|
+
:nz_co_loyalty_hoodoo_estimate_counts_with,
|
56
57
|
:nz_co_loyalty_hoodoo_search_with,
|
57
58
|
:nz_co_loyalty_hoodoo_filter_with,
|
58
59
|
{
|
@@ -373,7 +374,12 @@ module Hoodoo
|
|
373
374
|
# end
|
374
375
|
#
|
375
376
|
# Note the use of helper method #dataset_size to count the total
|
376
|
-
# amount of results in the dataset without pagination.
|
377
|
+
# amount of results in the dataset without pagination. A resource may
|
378
|
+
# alternatively choose to use #estimated_dataset_size for a fast count
|
379
|
+
# estimation, or neither (though this is generally not recommended) or
|
380
|
+
# - permissible but unusual - include both.
|
381
|
+
#
|
382
|
+
# context.response.set_resources( results, nil, finder.estimated_dataset_size )
|
377
383
|
#
|
378
384
|
# The service middleware enforces sane values for things like list
|
379
385
|
# offsets, sort keys and so-on according to service interface
|
@@ -495,7 +501,137 @@ module Hoodoo
|
|
495
501
|
# +dataset_size+ parameter.
|
496
502
|
#
|
497
503
|
def dataset_size
|
498
|
-
return all.limit( nil ).offset( nil ).count
|
504
|
+
return all.limit( nil ).offset( nil ).count()
|
505
|
+
end
|
506
|
+
|
507
|
+
# As #dataset_size, but allows a configurable counting back-end via
|
508
|
+
# #estimated_count and #estimate_counts_with. This method is intended
|
509
|
+
# to be used for fast count estimations, usually for performance
|
510
|
+
# reasons if an accurate #dataset_size count is too slow to compute.
|
511
|
+
#
|
512
|
+
def estimated_dataset_size
|
513
|
+
return all.limit( nil ).offset( nil ).estimated_count()
|
514
|
+
end
|
515
|
+
|
516
|
+
# In absence of other configuration, this method just calls through
|
517
|
+
# to Active Record's #count, but you can override the counting
|
518
|
+
# mechanism with a Proc which gets called to do the counting instead.
|
519
|
+
#
|
520
|
+
# The use case is for databases where counting may be slow for some
|
521
|
+
# reason. For example, in PostgreSQL 9, the MVCC model means that big
|
522
|
+
# tables under heavy write load may take extremely long times to be
|
523
|
+
# counted as a full sequential row scan gets activated. In the case
|
524
|
+
# of PostgreSQL, there's an estimation available as an alternative;
|
525
|
+
# its accuracy depends on how often the +ANALYZE+ command is run, but
|
526
|
+
# at least its execution speed is always very small.
|
527
|
+
#
|
528
|
+
# The #estimated_dataset_size method runs through here for counting so
|
529
|
+
# you need to ensure that your count estimation method can cope with
|
530
|
+
# whatever queries that might arise from the scope chains involved in
|
531
|
+
# instances of the model at hand, within the service code that uses
|
532
|
+
# that model.
|
533
|
+
#
|
534
|
+
# Specify a count estimation Proc with #estimate_counts_with. Such
|
535
|
+
# blocks are permitted to return +nil+ if the estimation is considered
|
536
|
+
# to be wildly wrong or unobtainable; in that case, the returned value
|
537
|
+
# for the estimated count will be +nil+ too.
|
538
|
+
#
|
539
|
+
def estimated_count
|
540
|
+
counter = self.nz_co_loyalty_hoodoo_estimate_counts_with
|
541
|
+
|
542
|
+
if ( counter.nil? )
|
543
|
+
return all.count
|
544
|
+
else
|
545
|
+
return counter.call( all.to_sql )
|
546
|
+
end
|
547
|
+
end
|
548
|
+
|
549
|
+
# This method is related to #estimated_count, so read the documentation
|
550
|
+
# for that as an introduction first.
|
551
|
+
#
|
552
|
+
# In #estimated_count, a PostgreSQL example is given. Continuing with
|
553
|
+
# this, we could implement an estimation mechanism via Hoodoo's fast
|
554
|
+
# counter with something like the approach described here:
|
555
|
+
#
|
556
|
+
# * https://wiki.postgresql.org/wiki/Count_estimate
|
557
|
+
# * http://www.verygoodindicators.com/blog/2015/04/07/faster-count-queries/
|
558
|
+
#
|
559
|
+
# First, you would need a migration in your service to implement the
|
560
|
+
# estimation method as a PLPGSQL function:
|
561
|
+
#
|
562
|
+
# class CreateFastCountFunction < ActiveRecord::Migration
|
563
|
+
# def up
|
564
|
+
# execute <<-SQL
|
565
|
+
# CREATE FUNCTION estimated_count(query text) RETURNS integer AS
|
566
|
+
# $func$
|
567
|
+
# DECLARE
|
568
|
+
# rec record;
|
569
|
+
# rows integer;
|
570
|
+
# BEGIN
|
571
|
+
# FOR rec IN EXECUTE 'EXPLAIN ' || query LOOP
|
572
|
+
# rows := substring(rec."QUERY PLAN" FROM ' rows=([[:digit:]]+)');
|
573
|
+
# EXIT WHEN rows IS NOT NULL;
|
574
|
+
# END LOOP;
|
575
|
+
#
|
576
|
+
# RETURN rows;
|
577
|
+
# END
|
578
|
+
# $func$ LANGUAGE plpgsql;
|
579
|
+
# SQL
|
580
|
+
# end
|
581
|
+
#
|
582
|
+
# def down
|
583
|
+
# execute "DROP FUNCTION estimated_count(query text);"
|
584
|
+
# end
|
585
|
+
# end
|
586
|
+
#
|
587
|
+
# This takes arbitrary query text so should cope with pretty much any
|
588
|
+
# kind of ActiveRecord query chain and resulting SQL. Run the database
|
589
|
+
# migration, then define a Proc which calls the new function:
|
590
|
+
#
|
591
|
+
# counter = Proc.new do | sql |
|
592
|
+
# begin
|
593
|
+
# sql = sql.gsub( "'", "''" ) # Escape SQL for insertion below
|
594
|
+
# ActiveRecord::Base.connection.execute(
|
595
|
+
# "SELECT estimated_count('#{ sql }')"
|
596
|
+
# ).first[ 'estimated_count' ].to_i
|
597
|
+
# rescue
|
598
|
+
# nil
|
599
|
+
# end
|
600
|
+
#
|
601
|
+
# Suppose we have a model called +Purchase+; next tell this model to
|
602
|
+
# use the above Proc for fast counting and use it:
|
603
|
+
#
|
604
|
+
# Purchase.estimate_counts_with( counter )
|
605
|
+
#
|
606
|
+
# Purchase.estimated_count()
|
607
|
+
# # => An integer; and you can use scope chains, just like #count:
|
608
|
+
# Purchase.where(...conditions...).estimated_count()
|
609
|
+
# # => An integer
|
610
|
+
#
|
611
|
+
# A real-life example showing how running PostgreSQL's +ANALYZE+
|
612
|
+
# command can make a difference:
|
613
|
+
#
|
614
|
+
# [1] pry(main)> Purchase.estimated_count
|
615
|
+
# => 68
|
616
|
+
# [2] pry(main)> Purchase.count
|
617
|
+
# => 76
|
618
|
+
# [3] pry(main)> ActiveRecord::Base.connection.execute( 'ANALYZE' )
|
619
|
+
# => #<PG::Result:0x007f89b62cdcc8 status=PGRES_COMMAND_OK ntuples=0 nfields=0 cmd_tuples=0>
|
620
|
+
# [4] pry(main)> Purchase.estimated_count
|
621
|
+
# => 76
|
622
|
+
#
|
623
|
+
# Parameters:
|
624
|
+
#
|
625
|
+
# +proc+:: The Proc to call. It must accept one parameter, which is the
|
626
|
+
# SQL query for which the count is to be run, as a String. It
|
627
|
+
# must evaluate to an Integer estimation, or +nil+ if it is
|
628
|
+
# not able to provide any/useful estimations, in its opinion.
|
629
|
+
#
|
630
|
+
# Pass +nil+ to remove the custom counter method and restore
|
631
|
+
# default behaviour.
|
632
|
+
#
|
633
|
+
def estimate_counts_with( proc )
|
634
|
+
self.nz_co_loyalty_hoodoo_estimate_counts_with = proc
|
499
635
|
end
|
500
636
|
|
501
637
|
# Specify a search mapping for use by #list to automatically restrict
|
@@ -20,9 +20,18 @@ module Hoodoo
|
|
20
20
|
|
21
21
|
# For lists, the (optional) total size of the data set, of which
|
22
22
|
# the contents of this Array will often only represent a single
|
23
|
-
# page. If unknown, the value is +nil
|
23
|
+
# page. If unknown, the value is +nil+, but as an alternative, an
|
24
|
+
# estimated size may be available in #estimated_dataset_size.
|
24
25
|
#
|
25
26
|
attr_accessor :dataset_size
|
27
|
+
|
28
|
+
# For lists, the (optional) estimated size of the data set, of
|
29
|
+
# which the contents of this Array will often only represent a
|
30
|
+
# single page. If unknown, the value is +nil+. The accuracy of
|
31
|
+
# the estimation is unknown.
|
32
|
+
#
|
33
|
+
attr_accessor :estimated_dataset_size
|
34
|
+
|
26
35
|
end
|
27
36
|
|
28
37
|
end
|
data/lib/hoodoo/client/client.rb
CHANGED
@@ -76,7 +76,8 @@ module Hoodoo
|
|
76
76
|
# call and supports Hoodoo::Client::AugmentedArray#dataset_size which
|
77
77
|
# (if the called Resource endpoint implementation provides the
|
78
78
|
# information) gives the total size of the data set at the time of
|
79
|
-
# calling.
|
79
|
+
# calling. Hoodoo::Client::AugmentedArray#estimated_dataset_size
|
80
|
+
# likewise gives access to the estimated count, if available.
|
80
81
|
#
|
81
82
|
# The other 4 methods return a Hoodoo::Client::AugmentedHash. This is a
|
82
83
|
# Hash subclass. Both the Array and Hash subclasses provide a common
|
@@ -325,9 +325,12 @@ module Hoodoo
|
|
325
325
|
# part, else the hash part.
|
326
326
|
|
327
327
|
if ( parsed[ '_data' ].is_a?( ::Array ) )
|
328
|
-
size
|
329
|
-
|
330
|
-
|
328
|
+
size = parsed[ '_dataset_size' ]
|
329
|
+
estimated_size = parsed[ '_estimated_dataset_size' ]
|
330
|
+
|
331
|
+
parsed = parsed[ '_data' ]
|
332
|
+
parsed.dataset_size = size
|
333
|
+
parsed.estimated_dataset_size = estimated_size
|
331
334
|
|
332
335
|
elsif ( parsed[ 'kind' ] == 'Errors' )
|
333
336
|
|
@@ -923,8 +923,9 @@ module Hoodoo; module Services
|
|
923
923
|
body = local_response.body
|
924
924
|
|
925
925
|
if action == :list && body.is_a?( ::Array )
|
926
|
-
result
|
927
|
-
result.dataset_size
|
926
|
+
result = Hoodoo::Client::AugmentedArray.new( body )
|
927
|
+
result.dataset_size = local_response.dataset_size
|
928
|
+
result.estimated_dataset_size = local_response.estimated_dataset_size
|
928
929
|
|
929
930
|
elsif action != :list && body.is_a?( ::Hash )
|
930
931
|
result = Hoodoo::Client::AugmentedHash[ body ]
|
@@ -1947,8 +1948,24 @@ module Hoodoo; module Services
|
|
1947
1948
|
# describing the current interaction. Updated on exit.
|
1948
1949
|
#
|
1949
1950
|
def deal_with_content_type_header( interaction )
|
1950
|
-
|
1951
|
-
|
1951
|
+
|
1952
|
+
# An in-the-wild Content-Type header value of
|
1953
|
+
# "application/json; charset=utf-8, application/x-www-form-urlencoded"
|
1954
|
+
# from Postman caused Rack 1.6.4 to break and raise an exception. Trap
|
1955
|
+
# any exceptions from the Rack request calls below and assume that they
|
1956
|
+
# indicate a malformed header.
|
1957
|
+
#
|
1958
|
+
begin
|
1959
|
+
content_type = interaction.rack_request.media_type
|
1960
|
+
content_encoding = interaction.rack_request.content_charset
|
1961
|
+
rescue
|
1962
|
+
interaction.context.response.errors.add_error(
|
1963
|
+
'platform.malformed',
|
1964
|
+
'message' => "Content-Type '#{ interaction.rack_request.content_type || "<unknown>" }' is malformed"
|
1965
|
+
)
|
1966
|
+
|
1967
|
+
return
|
1968
|
+
end
|
1952
1969
|
|
1953
1970
|
content_type.downcase! unless content_type.nil?
|
1954
1971
|
content_encoding.downcase! unless content_encoding.nil?
|
@@ -71,13 +71,21 @@ module Hoodoo; module Services
|
|
71
71
|
attr_accessor :body
|
72
72
|
alias_method :set_resource, :body=
|
73
73
|
|
74
|
-
# Read back a
|
74
|
+
# Read back a dataset size given by a prior call to #set_resources,
|
75
75
|
# or +nil+ if none has been provided (either the response contains no
|
76
76
|
# list yet/at all, or an Array was given but the dataset size was not
|
77
|
-
# supplied).
|
77
|
+
# supplied). If the dataset size is absent, an estimation may be
|
78
|
+
# present; see #estimated_dataset_size.
|
78
79
|
#
|
79
80
|
attr_reader :dataset_size
|
80
81
|
|
82
|
+
# Read back an estimated dataset size given by a prior call to
|
83
|
+
# #set_resources, or +nil+ if none has been provided (either the
|
84
|
+
# response contains no list yet/at all, or an Array was given but
|
85
|
+
# a dataset size estimation was not supplied).
|
86
|
+
#
|
87
|
+
attr_reader :estimated_dataset_size
|
88
|
+
|
81
89
|
# Create a new instance, ready to take on a response. The service
|
82
90
|
# middleware is responsible for doing this.
|
83
91
|
#
|
@@ -90,12 +98,13 @@ module Hoodoo; module Services
|
|
90
98
|
raise "Hoodoo::Services::Response.new must be given a valid Interaction ID (got '#{ interaction_id.inspect }')"
|
91
99
|
end
|
92
100
|
|
93
|
-
@interaction_id
|
94
|
-
@errors
|
95
|
-
@headers
|
96
|
-
@http_status_code
|
97
|
-
@body
|
98
|
-
@dataset_size
|
101
|
+
@interaction_id = interaction_id
|
102
|
+
@errors = Hoodoo::Errors.new()
|
103
|
+
@headers = {}
|
104
|
+
@http_status_code = 200
|
105
|
+
@body = {}
|
106
|
+
@dataset_size = nil
|
107
|
+
@estimated_dataset_size = nil
|
99
108
|
|
100
109
|
end
|
101
110
|
|
@@ -112,26 +121,67 @@ module Hoodoo; module Services
|
|
112
121
|
# array of items. Although you can just assign an array to either of
|
113
122
|
# #body or #set_resource, calling #set_resources is more semantically
|
114
123
|
# correct and provides an additional feature; you can specify the total
|
115
|
-
# number of items in the dataset.
|
124
|
+
# number of items in the dataset either precisely, or as an estimation.
|
116
125
|
#
|
117
126
|
# For example, if you were listing a page of 50 resource instances but
|
118
127
|
# the total matching dataset of that list included 344 instances, you
|
119
128
|
# would pass 344 in the +dataset_size+ input parameter. This is optional
|
120
129
|
# but highly recommended as it is often very useful for calling clients.
|
121
130
|
#
|
131
|
+
# If for any reason you aren't able to quickly produce an accurate count
|
132
|
+
# but _can_ produce an estimation, call #set_estimated_resources instead.
|
133
|
+
#
|
122
134
|
# +array+:: Array of resource representations (Ruby Array with
|
123
135
|
# Ruby Hash entries representing rendered resources,
|
124
136
|
# ideally through the Hoodoo::Presenters framework).
|
125
137
|
#
|
126
138
|
# +dataset_size+:: Optional _total_ number of items in the entire dataset
|
127
139
|
# of which +array+ is, most likely, just a subset due to
|
128
|
-
# paginated lists via offset and limit parameters.
|
140
|
+
# paginated lists via offset and limit parameters. This
|
141
|
+
# value was accurate at the instant of counting.
|
129
142
|
#
|
130
143
|
def set_resources( array, dataset_size = nil )
|
131
144
|
self.body = array
|
132
145
|
@dataset_size = dataset_size
|
133
146
|
end
|
134
147
|
|
148
|
+
# A companion to #set_resources. See the documentation of that method for
|
149
|
+
# background information.
|
150
|
+
#
|
151
|
+
# If the persistence layer in use and data volumes expected for a given
|
152
|
+
# resource make accurate counting too slow to compute, your persistence
|
153
|
+
# layer might support a mechanism for producing an _estimated_ count
|
154
|
+
# quickly instead. For example, PostgreSQL 9's row counting can be slow
|
155
|
+
# due to MVCC but there are PostgreSQL-specific ways of obtaining a row
|
156
|
+
# count estimation quickly. If this applies to you, call here to
|
157
|
+
# correctly specify the estimation in a way that makes it clear to the
|
158
|
+
# calling client that it's not an accurate result.
|
159
|
+
#
|
160
|
+
# Technically you could call *both* this *and* #set_resources to set both
|
161
|
+
# an accurate and an estimated count, though it's hard to imagine a use
|
162
|
+
# case for this outside of testing scenarios; but note that each call
|
163
|
+
# will override any previous setting of the #body property.
|
164
|
+
#
|
165
|
+
# If using the Hoodoo::ActiveRecord extensions for your persistence layer,
|
166
|
+
# then please also see
|
167
|
+
# Hoodoo::ActiveRecord::Finder::ClassMethods::estimated_dataset_size.
|
168
|
+
#
|
169
|
+
# +array+:: Array of resource representations (Ruby Array
|
170
|
+
# with Ruby Hash entries representing rendered
|
171
|
+
# resources, ideally through the
|
172
|
+
# Hoodoo::Presenters framework).
|
173
|
+
#
|
174
|
+
# +estimated_dataset_size+:: Optional _total_ number of items in the
|
175
|
+
# entire dataset of which +array+ is, most
|
176
|
+
# likely, just a subset due to paginated lists
|
177
|
+
# via offset and limit parameters; this value
|
178
|
+
# is an estimation with undefined accuracy.
|
179
|
+
#
|
180
|
+
def set_estimated_resources( array, estimated_dataset_size = nil )
|
181
|
+
self.body = array
|
182
|
+
@estimated_dataset_size = estimated_dataset_size
|
183
|
+
end
|
184
|
+
|
135
185
|
# Add an HTTP header to the internal collection that will be used for the
|
136
186
|
# response. Trying to set data for the same HTTP header name more than once
|
137
187
|
# will result in an exception being raised unless the +overwrite+ parameter
|
@@ -289,14 +339,19 @@ module Hoodoo; module Services
|
|
289
339
|
|
290
340
|
if body_data.is_a?( ::Array )
|
291
341
|
response_hash = { '_data' => body_data }
|
292
|
-
response_hash[ '_dataset_size'
|
342
|
+
response_hash[ '_dataset_size' ] = @dataset_size unless @dataset_size.nil?
|
343
|
+
response_hash[ '_estimated_dataset_size' ] = @estimated_dataset_size unless @estimated_dataset_size.nil?
|
293
344
|
response_string = ::JSON.generate( response_hash )
|
345
|
+
|
294
346
|
elsif body_data.is_a?( ::Hash )
|
295
347
|
response_string = ::JSON.generate( body_data )
|
348
|
+
|
296
349
|
elsif body_data.is_a?( ::String )
|
297
350
|
response_string = body_data
|
351
|
+
|
298
352
|
else
|
299
353
|
raise "Hoodoo::Services::Response\#for_rack given unrecognised body data class '#{ body_data.class.name }'"
|
354
|
+
|
300
355
|
end
|
301
356
|
|
302
357
|
rack_response.write( response_string )
|
data/lib/hoodoo/version.rb
CHANGED
@@ -435,6 +435,8 @@ describe Hoodoo::ActiveRecord::Finder do
|
|
435
435
|
|
436
436
|
context '#list' do
|
437
437
|
it 'lists with pages, offsets and counts' do
|
438
|
+
expect_any_instance_of( RSpecModelFinderTest ).to_not receive( :estimated_dataset_size )
|
439
|
+
|
438
440
|
@list_params.offset = 1 # 0 is first record
|
439
441
|
@list_params.limit = 1
|
440
442
|
|
@@ -453,6 +455,121 @@ describe Hoodoo::ActiveRecord::Finder do
|
|
453
455
|
|
454
456
|
# ==========================================================================
|
455
457
|
|
458
|
+
context 'counting' do
|
459
|
+
it 'lists with a normal count' do
|
460
|
+
finder = RSpecModelFinderTest.list( @list_params )
|
461
|
+
|
462
|
+
expect( finder ).to receive( :dataset_size ).at_least( :once ).and_call_original
|
463
|
+
expect( finder ).to receive( :count ).at_least( :once ).and_call_original
|
464
|
+
|
465
|
+
expect( finder ).to_not receive( :estimated_dataset_size )
|
466
|
+
expect( finder ).to_not receive( :estimated_count )
|
467
|
+
|
468
|
+
result = finder.dataset_size()
|
469
|
+
|
470
|
+
expect( result ).to_not be_nil
|
471
|
+
end
|
472
|
+
|
473
|
+
it 'lists with an estimated count' do
|
474
|
+
finder = RSpecModelFinderTest.list( @list_params )
|
475
|
+
|
476
|
+
expect( finder ).to_not receive( :dataset_size )
|
477
|
+
|
478
|
+
expect( finder ).to receive( :estimated_dataset_size ).at_least( :once ).and_call_original
|
479
|
+
expect( finder ).to receive( :estimated_count ).at_least( :once ).and_call_original
|
480
|
+
expect( finder ).to receive( :count ).at_least( :once ).and_call_original
|
481
|
+
|
482
|
+
result = finder.estimated_dataset_size
|
483
|
+
|
484
|
+
expect( result ).to_not be_nil
|
485
|
+
end
|
486
|
+
|
487
|
+
context 'RDoc-recommended PostgreSQL migration example' do
|
488
|
+
before :each do
|
489
|
+
ActiveRecord::Base.connection.execute <<-SQL
|
490
|
+
CREATE FUNCTION estimated_count(query text) RETURNS integer AS
|
491
|
+
$func$
|
492
|
+
DECLARE
|
493
|
+
rec record;
|
494
|
+
rows integer;
|
495
|
+
BEGIN
|
496
|
+
FOR rec IN EXECUTE 'EXPLAIN ' || query LOOP
|
497
|
+
rows := substring(rec."QUERY PLAN" FROM ' rows=([[:digit:]]+)');
|
498
|
+
EXIT WHEN rows IS NOT NULL;
|
499
|
+
END LOOP;
|
500
|
+
|
501
|
+
RETURN rows;
|
502
|
+
END
|
503
|
+
$func$ LANGUAGE plpgsql;
|
504
|
+
SQL
|
505
|
+
|
506
|
+
counter = Proc.new do | sql |
|
507
|
+
begin
|
508
|
+
ActiveRecord::Base.connection.execute(
|
509
|
+
"SELECT estimated_count('#{ sql}')"
|
510
|
+
).first[ 'estimated_count' ].to_i
|
511
|
+
rescue
|
512
|
+
nil
|
513
|
+
end
|
514
|
+
end
|
515
|
+
|
516
|
+
RSpecModelFinderTest.estimate_counts_with( counter )
|
517
|
+
|
518
|
+
# Tests start by ensuring the database knows about the current object count.
|
519
|
+
#
|
520
|
+
ActiveRecord::Base.connection.execute( 'ANALYZE' )
|
521
|
+
end
|
522
|
+
|
523
|
+
after :each do
|
524
|
+
ActiveRecord::Base.connection.execute "DROP FUNCTION estimated_count(query text);"
|
525
|
+
RSpecModelFinderTest.estimate_counts_with( nil )
|
526
|
+
end
|
527
|
+
|
528
|
+
context 'estimate' do
|
529
|
+
before :each do
|
530
|
+
@initial_accurate_count = RSpecModelFinderTest.count
|
531
|
+
|
532
|
+
# The outer 'before' code ensures an accurate initial count of 3,
|
533
|
+
# but we're going add in a few more unestimated items.
|
534
|
+
#
|
535
|
+
@uncounted1 = RSpecModelFinderTest.new.save!
|
536
|
+
@uncounted2 = RSpecModelFinderTest.new.save!
|
537
|
+
@uncounted3 = RSpecModelFinderTest.new.save!
|
538
|
+
|
539
|
+
@subsequent_accurate_count = RSpecModelFinderTest.count
|
540
|
+
end
|
541
|
+
|
542
|
+
it 'is initially inaccurate' do
|
543
|
+
finder = RSpecModelFinderTest.list( @list_params )
|
544
|
+
result = finder.estimated_dataset_size
|
545
|
+
expect( result ).to eq( @initial_accurate_count )
|
546
|
+
end
|
547
|
+
|
548
|
+
# The outer 'before' code kind of already tests this anyway since if
|
549
|
+
# the analyze call therein didn't work, prerequisites in the tests
|
550
|
+
# would be wrong and other tests would fail. It's useful to
|
551
|
+
# double-check something this important though.
|
552
|
+
#
|
553
|
+
it 'is accurate after ANALYZE' do
|
554
|
+
ActiveRecord::Base.connection.execute( 'ANALYZE' )
|
555
|
+
|
556
|
+
finder = RSpecModelFinderTest.list( @list_params )
|
557
|
+
result = finder.estimated_dataset_size
|
558
|
+
expect( result ).to eq( @subsequent_accurate_count )
|
559
|
+
end
|
560
|
+
|
561
|
+
it 'is "nil" if the Proc evaluates thus' do
|
562
|
+
RSpecModelFinderTest.estimate_counts_with( Proc.new() { | sql | nil } )
|
563
|
+
finder = RSpecModelFinderTest.list( @list_params )
|
564
|
+
result = finder.estimated_dataset_size
|
565
|
+
expect( result ).to be_nil
|
566
|
+
end
|
567
|
+
end
|
568
|
+
end
|
569
|
+
end
|
570
|
+
|
571
|
+
# ==========================================================================
|
572
|
+
|
456
573
|
context 'search' do
|
457
574
|
it 'searches without chain' do
|
458
575
|
@list_params.search_data = {
|
data/spec/client/client_spec.rb
CHANGED
@@ -80,10 +80,13 @@ class RSpecClientTestTargetImplementation < Hoodoo::Services::Implementation
|
|
80
80
|
return
|
81
81
|
end
|
82
82
|
|
83
|
-
context
|
84
|
-
|
85
|
-
|
86
|
-
|
83
|
+
resources = [ mock( context ), mock( context ), mock( context ) ]
|
84
|
+
|
85
|
+
if context.request.embeds.include?( 'estimated_counts_please' )
|
86
|
+
context.response.set_estimated_resources( resources, resources.count )
|
87
|
+
else
|
88
|
+
context.response.set_resources( resources, resources.count )
|
89
|
+
end
|
87
90
|
end
|
88
91
|
|
89
92
|
def create( context )
|
@@ -171,7 +174,7 @@ class RSpecClientTestTargetInterface < Hoodoo::Services::Interface
|
|
171
174
|
endpoint :r_spec_client_test_targets, RSpecClientTestTargetImplementation
|
172
175
|
public_actions :show
|
173
176
|
actions :list, :create, :update, :delete
|
174
|
-
embeds :foo, :bar, :baz
|
177
|
+
embeds :foo, :bar, :baz, :estimated_counts_please
|
175
178
|
end
|
176
179
|
end
|
177
180
|
|
@@ -588,6 +591,7 @@ describe Hoodoo::Client do
|
|
588
591
|
result = @endpoint.list( query_hash )
|
589
592
|
expect( result.platform_errors.has_errors? ).to eq( false )
|
590
593
|
expect( result.dataset_size ).to eq( result.size )
|
594
|
+
expect( result.estimated_dataset_size ).to be_nil
|
591
595
|
|
592
596
|
expect( result[ 0 ][ 'embeds' ] ).to eq( embeds )
|
593
597
|
expect( result[ 0 ][ 'language' ] ).to eq( @expected_locale )
|
@@ -636,6 +640,19 @@ describe Hoodoo::Client do
|
|
636
640
|
|
637
641
|
option_based_expectations( result )
|
638
642
|
end
|
643
|
+
|
644
|
+
it "provides estimations" do
|
645
|
+
query_hash = { '_embed' => 'estimated_counts_please' }
|
646
|
+
|
647
|
+
result = @endpoint.list( query_hash )
|
648
|
+
expect( result.platform_errors.has_errors? ).to eq( false )
|
649
|
+
expect( result.dataset_size ).to be_nil
|
650
|
+
expect( result.estimated_dataset_size ).to eq( result.size )
|
651
|
+
|
652
|
+
expect( result[ 0 ][ 'language' ] ).to eq( @expected_locale )
|
653
|
+
|
654
|
+
option_based_expectations( result )
|
655
|
+
end
|
639
656
|
end
|
640
657
|
|
641
658
|
before :each do
|
@@ -854,6 +871,7 @@ describe Hoodoo::Client do
|
|
854
871
|
result = @endpoint.list( query_hash )
|
855
872
|
expect( result.platform_errors.has_errors? ).to eq( false )
|
856
873
|
expect( result.dataset_size ).to eq( result.size )
|
874
|
+
expect( result.estimated_dataset_size ).to be_nil
|
857
875
|
|
858
876
|
expect( result[ 0 ][ 'embeds' ] ).to eq( embeds )
|
859
877
|
expect( result[ 0 ][ 'language' ] ).to eq( @expected_locale )
|
@@ -903,6 +921,19 @@ describe Hoodoo::Client do
|
|
903
921
|
option_based_expectations( result )
|
904
922
|
end
|
905
923
|
|
924
|
+
it "provides estimations" do
|
925
|
+
query_hash = { '_embed' => 'estimated_counts_please' }
|
926
|
+
|
927
|
+
result = @endpoint.list( query_hash )
|
928
|
+
expect( result.platform_errors.has_errors? ).to eq( false )
|
929
|
+
expect( result.dataset_size ).to be_nil
|
930
|
+
expect( result.estimated_dataset_size ).to eq( result.size )
|
931
|
+
|
932
|
+
expect( result[ 0 ][ 'language' ] ).to eq( @expected_locale )
|
933
|
+
|
934
|
+
option_based_expectations( result )
|
935
|
+
end
|
936
|
+
|
906
937
|
it 'automatically retries' do
|
907
938
|
result = @endpoint.list()
|
908
939
|
expect( result.platform_errors.has_errors? ).to eq( false )
|
@@ -8,6 +8,7 @@ describe Hoodoo::Services::Middleware do
|
|
8
8
|
|
9
9
|
class RSpecTestServiceExoticStubImplementation < Hoodoo::Services::Implementation
|
10
10
|
def list( context )
|
11
|
+
context.response.set_estimated_resources( [], 88 )
|
11
12
|
context.response.set_resources( [], 99 )
|
12
13
|
end
|
13
14
|
end
|
@@ -151,11 +152,13 @@ describe Hoodoo::Services::Middleware do
|
|
151
152
|
endpoint = @mw.inter_resource_endpoint_for( 'Version', 2, @interaction )
|
152
153
|
|
153
154
|
# The endpoint should've been called locally; the implementation at
|
154
|
-
# the top of this file sets an empty array with dataset size 99
|
155
|
+
# the top of this file sets an empty array with dataset size 99 *and*
|
156
|
+
# an estimated dataset size of 88.
|
155
157
|
|
156
158
|
mock_result = endpoint.list()
|
157
159
|
expect( mock_result ).to be_empty
|
158
160
|
expect( mock_result.dataset_size ).to eq( 99 )
|
161
|
+
expect( mock_result.estimated_dataset_size ).to eq( 88 )
|
159
162
|
end
|
160
163
|
|
161
164
|
it 'complains about a missing Alchemy instance' do
|
@@ -558,6 +561,7 @@ describe Hoodoo::Services::Middleware do
|
|
558
561
|
|
559
562
|
expect( mock_result ).to eq( Hoodoo::Client::AugmentedArray.new )
|
560
563
|
expect( mock_result.dataset_size ).to eq(99)
|
564
|
+
expect( mock_result.estimated_dataset_size ).to eq( 88 )
|
561
565
|
expect( mock_result.platform_errors.has_errors? ).to eq( false )
|
562
566
|
end
|
563
567
|
end
|
@@ -23,7 +23,9 @@ class RSpecTestInterResourceCallsAImplementation < Hoodoo::Services::Implementat
|
|
23
23
|
if search_offset > 0
|
24
24
|
context.response.add_error( 'service_calls_a.triggered', 'reference' => { :offset => search_offset } )
|
25
25
|
else
|
26
|
-
|
26
|
+
array = [ 1, 2, 3, 4 ]
|
27
|
+
context.response.set_estimated_resources( array, 1234 )
|
28
|
+
context.response.set_resources( array, 4321 )
|
27
29
|
expectable_hook( context )
|
28
30
|
end
|
29
31
|
end
|
@@ -415,6 +417,7 @@ describe Hoodoo::Services::Middleware::InterResourceLocal do
|
|
415
417
|
expect_any_instance_of(RSpecTestInterResourceCallsBImplementation).to receive(:expectable_result_hook).once do | ignored_rspec_mock_instance, result |
|
416
418
|
expect(result).to eq([1,2,3,4])
|
417
419
|
expect(result.dataset_size).to eq(4321)
|
420
|
+
expect(result.estimated_dataset_size).to eq(1234)
|
418
421
|
end
|
419
422
|
|
420
423
|
get '/v1/rspec_test_inter_resource_calls_b',
|
@@ -28,14 +28,15 @@ class TestEchoImplementation < Hoodoo::Services::Implementation
|
|
28
28
|
return
|
29
29
|
end
|
30
30
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
)
|
31
|
+
array =
|
32
|
+
[
|
33
|
+
{ 'list0' => TestEchoImplementation.to_h( context ) },
|
34
|
+
{ 'list1' => TestEchoImplementation.to_h( context ) },
|
35
|
+
{ 'list2' => TestEchoImplementation.to_h( context ) }
|
36
|
+
]
|
37
|
+
|
38
|
+
context.response.set_resources( array, 49 )
|
39
|
+
context.response.set_estimated_resources( array, 50 )
|
39
40
|
end
|
40
41
|
|
41
42
|
def show( context )
|
@@ -189,15 +190,16 @@ class TestCallImplementation < Hoodoo::Services::Implementation
|
|
189
190
|
|
190
191
|
return if result.adds_errors_to?( context.response.errors )
|
191
192
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
)
|
193
|
+
array =
|
194
|
+
[
|
195
|
+
{ 'listA' => result },
|
196
|
+
{ 'listB' => result },
|
197
|
+
{ 'listC' => result },
|
198
|
+
{ 'options' => result.response_options }
|
199
|
+
]
|
200
|
+
|
201
|
+
context.response.set_resources( array, ( result.dataset_size || 0 ) + 2 )
|
202
|
+
context.response.set_estimated_resources( array, ( result.estimated_dataset_size || 0 ) + 2 )
|
201
203
|
end
|
202
204
|
|
203
205
|
def show( context )
|
@@ -434,6 +436,7 @@ describe Hoodoo::Services::Middleware do
|
|
434
436
|
}
|
435
437
|
)
|
436
438
|
expect( parsed[ '_dataset_size' ] ).to eq( 49 )
|
439
|
+
expect( parsed[ '_estimated_dataset_size' ] ).to eq( 50 )
|
437
440
|
end
|
438
441
|
|
439
442
|
it 'lists things with callbacks', :check_callbacks => true do
|
@@ -957,6 +960,7 @@ describe Hoodoo::Services::Middleware do
|
|
957
960
|
)
|
958
961
|
|
959
962
|
expect( parsed[ '_dataset_size' ]).to eq( 51 )
|
963
|
+
expect( parsed[ '_estimated_dataset_size' ] ).to eq( 52 )
|
960
964
|
|
961
965
|
expect( parsed[ '_data' ][ 3 ] ).to_not be_nil
|
962
966
|
expect_response_options_for( parsed[ '_data' ][ 3 ][ 'options' ] )
|
@@ -225,7 +225,7 @@ describe Hoodoo::Services::Middleware do
|
|
225
225
|
expect(result['errors'][0]['message']).to eq("Content-Type 'application/json' does not match supported types '[\"application/json\"]' and/or encodings '[\"utf-8\"]'")
|
226
226
|
end
|
227
227
|
|
228
|
-
it '
|
228
|
+
it 'complains about incorrect content types' do
|
229
229
|
get '/v2/rspec_test_service_stub', nil, { 'CONTENT_TYPE' => 'some/thing; charset=utf-8' }
|
230
230
|
|
231
231
|
expect(last_response.status).to eq(422)
|
@@ -235,7 +235,7 @@ describe Hoodoo::Services::Middleware do
|
|
235
235
|
expect(result['errors'][0]['message']).to eq("Content-Type 'some/thing; charset=utf-8' does not match supported types '[\"application/json\"]' and/or encodings '[\"utf-8\"]'")
|
236
236
|
end
|
237
237
|
|
238
|
-
it '
|
238
|
+
it 'complains about incorrect content type charsets' do
|
239
239
|
get '/v2/rspec_test_service_stub', nil, { 'CONTENT_TYPE' => 'application/json; charset=madeup' }
|
240
240
|
|
241
241
|
expect(last_response.status).to eq(422)
|
@@ -245,6 +245,25 @@ describe Hoodoo::Services::Middleware do
|
|
245
245
|
expect(result['errors'][0]['message']).to eq("Content-Type 'application/json; charset=madeup' does not match supported types '[\"application/json\"]' and/or encodings '[\"utf-8\"]'")
|
246
246
|
end
|
247
247
|
|
248
|
+
it 'rejects malformed attempts to specify a list of options' do
|
249
|
+
types =
|
250
|
+
[
|
251
|
+
'application/json; charset=utf-8, application/x-www-form-urlencoded',
|
252
|
+
'application/x-www-form-urlencoded, application/json; charset=utf-8',
|
253
|
+
'application/x-www-form-urlencoded, application/json; charset=utf-8, application/json; charset=madeup'
|
254
|
+
]
|
255
|
+
|
256
|
+
types.each do | type |
|
257
|
+
get '/v2/rspec_test_service_stub', nil, { 'CONTENT_TYPE' => type }
|
258
|
+
|
259
|
+
expect(last_response.status).to eq(422)
|
260
|
+
|
261
|
+
result = JSON.parse(last_response.body)
|
262
|
+
expect(result['errors'][0]['code']).to eq('platform.malformed')
|
263
|
+
expect(result['errors'][0]['message']).to eq("Content-Type '#{ type }' is malformed")
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
248
267
|
it 'should generate interaction IDs and other standard headers even for error states' do
|
249
268
|
get '/v2/rspec_test_service_stub'
|
250
269
|
|
@@ -202,7 +202,7 @@ describe Hoodoo::Services::Response do
|
|
202
202
|
expect(body.body).to eq([expected])
|
203
203
|
end
|
204
204
|
|
205
|
-
it '
|
205
|
+
it 'returns non-error condition Rack data correctly with an Array body' do
|
206
206
|
response_array = [ { this: 'should not be ignored' }, { neither: 'should this' } ]
|
207
207
|
@r.body = response_array
|
208
208
|
|
@@ -214,6 +214,47 @@ describe Hoodoo::Services::Response do
|
|
214
214
|
expect(body.body).to eq([expected])
|
215
215
|
end
|
216
216
|
|
217
|
+
it 'returns non-error condition Rack data correctly with a dataset size' do
|
218
|
+
response_array = [ { this: 'should not be ignored' }, { neither: 'should this' } ]
|
219
|
+
@r.set_resources( response_array, response_array.count )
|
220
|
+
|
221
|
+
status, headers, body = @r.for_rack
|
222
|
+
|
223
|
+
expected = JSON.generate( { '_data' => response_array, '_dataset_size' => response_array.count } )
|
224
|
+
expect( status ).to eq( 200 )
|
225
|
+
expect( headers ).to eq( { 'Content-Length' => expected.length.to_s } )
|
226
|
+
expect( body.body ).to eq( [ expected ] )
|
227
|
+
end
|
228
|
+
|
229
|
+
it 'returns non-error condition Rack data correctly with an estimated dataset size' do
|
230
|
+
response_array = [ { this: 'should not be ignored' }, { neither: 'should this' } ]
|
231
|
+
@r.set_estimated_resources( response_array, response_array.count )
|
232
|
+
|
233
|
+
status, headers, body = @r.for_rack
|
234
|
+
|
235
|
+
expected = JSON.generate( { '_data' => response_array, '_estimated_dataset_size' => response_array.count } )
|
236
|
+
expect( status ).to eq( 200 )
|
237
|
+
expect( headers ).to eq( { 'Content-Length' => expected.length.to_s } )
|
238
|
+
expect( body.body ).to eq( [ expected ] )
|
239
|
+
end
|
240
|
+
|
241
|
+
it 'returns non-error condition Rack data correctly with both an accurate and an estimated dataset size' do
|
242
|
+
response_array = [ { this: 'should not be ignored' }, { neither: 'should this' } ]
|
243
|
+
|
244
|
+
@r.set_resources( response_array, response_array.count )
|
245
|
+
@r.set_estimated_resources( response_array, response_array.count )
|
246
|
+
|
247
|
+
status, headers, body = @r.for_rack
|
248
|
+
|
249
|
+
expected = JSON.generate( { '_data' => response_array,
|
250
|
+
'_dataset_size' => response_array.count,
|
251
|
+
'_estimated_dataset_size' => response_array.count } )
|
252
|
+
|
253
|
+
expect( status ).to eq( 200 )
|
254
|
+
expect( headers ).to eq( { 'Content-Length' => expected.length.to_s } )
|
255
|
+
expect( body.body ).to eq( [ expected ] )
|
256
|
+
end
|
257
|
+
|
217
258
|
it 'should allow pre-encoded strings in the body' do
|
218
259
|
@r.body = 'Hello World!'
|
219
260
|
|
@@ -232,7 +273,7 @@ describe Hoodoo::Services::Response do
|
|
232
273
|
end
|
233
274
|
end
|
234
275
|
|
235
|
-
context
|
276
|
+
context '#not_found' do
|
236
277
|
|
237
278
|
let(:ident) { 'an_ident' }
|
238
279
|
before { @r.not_found(ident) }
|
@@ -247,4 +288,34 @@ describe Hoodoo::Services::Response do
|
|
247
288
|
expect(@r.halt_processing?).to eq(true)
|
248
289
|
end
|
249
290
|
end
|
291
|
+
|
292
|
+
context '#set_resources and #set_estimated_resources' do
|
293
|
+
it '#set_resources sets #body and #dataset_size' do
|
294
|
+
array = [ 1, 2, 3, 4 ]
|
295
|
+
@r.set_resources( array, 4321 )
|
296
|
+
expect( @r.body ).to match_array( array )
|
297
|
+
expect( @r.dataset_size ).to eq( 4321 )
|
298
|
+
expect( @r.estimated_dataset_size ).to be_nil
|
299
|
+
end
|
300
|
+
|
301
|
+
it '#set_estimated_resources sets #body and #estimated_dataset_size' do
|
302
|
+
array = [ 4, 3, 2, 1 ]
|
303
|
+
@r.set_estimated_resources( array, 1234 )
|
304
|
+
expect( @r.body ).to match_array( array )
|
305
|
+
expect( @r.dataset_size ).to be_nil
|
306
|
+
expect( @r.estimated_dataset_size ).to eq( 1234 )
|
307
|
+
end
|
308
|
+
|
309
|
+
it 'both together set all properties with the most recent call setting #body' do
|
310
|
+
array_1 = [ 1, 2, 3, 4 ]
|
311
|
+
@r.set_resources( array_1, 4321 )
|
312
|
+
|
313
|
+
array_2 = [ 4, 3, 2, 1 ]
|
314
|
+
@r.set_estimated_resources( array_2, 1234 )
|
315
|
+
|
316
|
+
expect( @r.body ).to match_array( array_2 )
|
317
|
+
expect( @r.dataset_size ).to eq( 4321 )
|
318
|
+
expect( @r.estimated_dataset_size ).to eq( 1234 )
|
319
|
+
end
|
320
|
+
end
|
250
321
|
end
|
metadata
CHANGED
@@ -1,265 +1,265 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hoodoo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Loyalty New Zealand
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-04-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: kgio
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - ~>
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '2.9'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - ~>
|
24
|
+
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '2.9'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: dalli
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - ~>
|
31
|
+
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '2.7'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - ~>
|
38
|
+
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '2.7'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rake
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - ~>
|
45
|
+
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: '10.4'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - ~>
|
52
|
+
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '10.4'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: simplecov-rcov
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- - ~>
|
59
|
+
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '0.2'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- - ~>
|
66
|
+
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0.2'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: rdoc
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- - ~>
|
73
|
+
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
75
|
version: '4.2'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- - ~>
|
80
|
+
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '4.2'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: rack-test
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
|
-
- - ~>
|
87
|
+
- - "~>"
|
88
88
|
- !ruby/object:Gem::Version
|
89
89
|
version: '0.6'
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
|
-
- - ~>
|
94
|
+
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0.6'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: alchemy-flux
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
|
-
- - ~>
|
101
|
+
- - "~>"
|
102
102
|
- !ruby/object:Gem::Version
|
103
103
|
version: '1.0'
|
104
104
|
type: :development
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
|
-
- - ~>
|
108
|
+
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '1.0'
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
112
|
name: rspec
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
114
114
|
requirements:
|
115
|
-
- - ~>
|
115
|
+
- - "~>"
|
116
116
|
- !ruby/object:Gem::Version
|
117
117
|
version: '3.3'
|
118
118
|
type: :development
|
119
119
|
prerelease: false
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
121
121
|
requirements:
|
122
|
-
- - ~>
|
122
|
+
- - "~>"
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '3.3'
|
125
125
|
- !ruby/object:Gem::Dependency
|
126
126
|
name: rspec-mocks
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
128
128
|
requirements:
|
129
|
-
- - ~>
|
129
|
+
- - "~>"
|
130
130
|
- !ruby/object:Gem::Version
|
131
131
|
version: '3.3'
|
132
132
|
type: :development
|
133
133
|
prerelease: false
|
134
134
|
version_requirements: !ruby/object:Gem::Requirement
|
135
135
|
requirements:
|
136
|
-
- - ~>
|
136
|
+
- - "~>"
|
137
137
|
- !ruby/object:Gem::Version
|
138
138
|
version: '3.3'
|
139
139
|
- !ruby/object:Gem::Dependency
|
140
140
|
name: activerecord
|
141
141
|
requirement: !ruby/object:Gem::Requirement
|
142
142
|
requirements:
|
143
|
-
- - ~>
|
143
|
+
- - "~>"
|
144
144
|
- !ruby/object:Gem::Version
|
145
145
|
version: '4.2'
|
146
146
|
type: :development
|
147
147
|
prerelease: false
|
148
148
|
version_requirements: !ruby/object:Gem::Requirement
|
149
149
|
requirements:
|
150
|
-
- - ~>
|
150
|
+
- - "~>"
|
151
151
|
- !ruby/object:Gem::Version
|
152
152
|
version: '4.2'
|
153
153
|
- !ruby/object:Gem::Dependency
|
154
154
|
name: activesupport
|
155
155
|
requirement: !ruby/object:Gem::Requirement
|
156
156
|
requirements:
|
157
|
-
- - ~>
|
157
|
+
- - "~>"
|
158
158
|
- !ruby/object:Gem::Version
|
159
159
|
version: '4.2'
|
160
160
|
type: :development
|
161
161
|
prerelease: false
|
162
162
|
version_requirements: !ruby/object:Gem::Requirement
|
163
163
|
requirements:
|
164
|
-
- - ~>
|
164
|
+
- - "~>"
|
165
165
|
- !ruby/object:Gem::Version
|
166
166
|
version: '4.2'
|
167
167
|
- !ruby/object:Gem::Dependency
|
168
168
|
name: database_cleaner
|
169
169
|
requirement: !ruby/object:Gem::Requirement
|
170
170
|
requirements:
|
171
|
-
- - ~>
|
171
|
+
- - "~>"
|
172
172
|
- !ruby/object:Gem::Version
|
173
173
|
version: 1.4.0
|
174
174
|
type: :development
|
175
175
|
prerelease: false
|
176
176
|
version_requirements: !ruby/object:Gem::Requirement
|
177
177
|
requirements:
|
178
|
-
- - ~>
|
178
|
+
- - "~>"
|
179
179
|
- !ruby/object:Gem::Version
|
180
180
|
version: 1.4.0
|
181
181
|
- !ruby/object:Gem::Dependency
|
182
182
|
name: pg
|
183
183
|
requirement: !ruby/object:Gem::Requirement
|
184
184
|
requirements:
|
185
|
-
- - ~>
|
185
|
+
- - "~>"
|
186
186
|
- !ruby/object:Gem::Version
|
187
187
|
version: '0.18'
|
188
188
|
type: :development
|
189
189
|
prerelease: false
|
190
190
|
version_requirements: !ruby/object:Gem::Requirement
|
191
191
|
requirements:
|
192
|
-
- - ~>
|
192
|
+
- - "~>"
|
193
193
|
- !ruby/object:Gem::Version
|
194
194
|
version: '0.18'
|
195
195
|
- !ruby/object:Gem::Dependency
|
196
196
|
name: byebug
|
197
197
|
requirement: !ruby/object:Gem::Requirement
|
198
198
|
requirements:
|
199
|
-
- - ~>
|
199
|
+
- - "~>"
|
200
200
|
- !ruby/object:Gem::Version
|
201
201
|
version: '3.5'
|
202
202
|
type: :development
|
203
203
|
prerelease: false
|
204
204
|
version_requirements: !ruby/object:Gem::Requirement
|
205
205
|
requirements:
|
206
|
-
- - ~>
|
206
|
+
- - "~>"
|
207
207
|
- !ruby/object:Gem::Version
|
208
208
|
version: '3.5'
|
209
209
|
- !ruby/object:Gem::Dependency
|
210
210
|
name: timecop
|
211
211
|
requirement: !ruby/object:Gem::Requirement
|
212
212
|
requirements:
|
213
|
-
- - ~>
|
213
|
+
- - "~>"
|
214
214
|
- !ruby/object:Gem::Version
|
215
215
|
version: '0.8'
|
216
216
|
type: :development
|
217
217
|
prerelease: false
|
218
218
|
version_requirements: !ruby/object:Gem::Requirement
|
219
219
|
requirements:
|
220
|
-
- - ~>
|
220
|
+
- - "~>"
|
221
221
|
- !ruby/object:Gem::Version
|
222
222
|
version: '0.8'
|
223
223
|
- !ruby/object:Gem::Dependency
|
224
224
|
name: raygun4ruby
|
225
225
|
requirement: !ruby/object:Gem::Requirement
|
226
226
|
requirements:
|
227
|
-
- - ~>
|
227
|
+
- - "~>"
|
228
228
|
- !ruby/object:Gem::Version
|
229
229
|
version: '1.1'
|
230
230
|
type: :development
|
231
231
|
prerelease: false
|
232
232
|
version_requirements: !ruby/object:Gem::Requirement
|
233
233
|
requirements:
|
234
|
-
- - ~>
|
234
|
+
- - "~>"
|
235
235
|
- !ruby/object:Gem::Version
|
236
236
|
version: '1.1'
|
237
237
|
- !ruby/object:Gem::Dependency
|
238
238
|
name: airbrake
|
239
239
|
requirement: !ruby/object:Gem::Requirement
|
240
240
|
requirements:
|
241
|
-
- - ~>
|
241
|
+
- - "~>"
|
242
242
|
- !ruby/object:Gem::Version
|
243
243
|
version: '4.3'
|
244
244
|
type: :development
|
245
245
|
prerelease: false
|
246
246
|
version_requirements: !ruby/object:Gem::Requirement
|
247
247
|
requirements:
|
248
|
-
- - ~>
|
248
|
+
- - "~>"
|
249
249
|
- !ruby/object:Gem::Version
|
250
250
|
version: '4.3'
|
251
251
|
- !ruby/object:Gem::Dependency
|
252
252
|
name: le
|
253
253
|
requirement: !ruby/object:Gem::Requirement
|
254
254
|
requirements:
|
255
|
-
- - ~>
|
255
|
+
- - "~>"
|
256
256
|
- !ruby/object:Gem::Version
|
257
257
|
version: '2.6'
|
258
258
|
type: :development
|
259
259
|
prerelease: false
|
260
260
|
version_requirements: !ruby/object:Gem::Requirement
|
261
261
|
requirements:
|
262
|
-
- - ~>
|
262
|
+
- - "~>"
|
263
263
|
- !ruby/object:Gem::Version
|
264
264
|
version: '2.6'
|
265
265
|
description: Simplify the implementation of consistent services within an API-based
|
@@ -495,17 +495,17 @@ require_paths:
|
|
495
495
|
- lib
|
496
496
|
required_ruby_version: !ruby/object:Gem::Requirement
|
497
497
|
requirements:
|
498
|
-
- -
|
498
|
+
- - ">="
|
499
499
|
- !ruby/object:Gem::Version
|
500
500
|
version: '2.1'
|
501
501
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
502
502
|
requirements:
|
503
|
-
- -
|
503
|
+
- - ">="
|
504
504
|
- !ruby/object:Gem::Version
|
505
505
|
version: '0'
|
506
506
|
requirements: []
|
507
507
|
rubyforge_project:
|
508
|
-
rubygems_version: 2.
|
508
|
+
rubygems_version: 2.2.5
|
509
509
|
signing_key:
|
510
510
|
specification_version: 4
|
511
511
|
summary: Opinionated APIs
|