hoodoo 1.6.1 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|