fmrest 0.7.1 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +55 -8
- data/lib/fmrest/spyke/model/orm.rb +2 -1
- data/lib/fmrest/spyke/relation.rb +73 -0
- data/lib/fmrest/spyke/spyke_formatter.rb +46 -9
- data/lib/fmrest/v1/connection.rb +4 -4
- data/lib/fmrest/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5873c0192a2b166cfef71b33c521665dd6953660bd9d23d3ef728b89c55e886f
|
4
|
+
data.tar.gz: 7615faa115cc7506d3a472d2fac70ea293bd3dbec1e03bb3c19f0faca51b7c9d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b627994fa66842be8200692895077649837a7b343c337aaf5762926c633de02baf14ba6428cc33b8b3cc0d0314d7d62a4f6cf16173edb09d32ba8218c533d7d6
|
7
|
+
data.tar.gz: 2cd9e7f7003f9ec05120587f50ee892076d8e02045a6b18e9311276792bf831dee6c4434969990b17f4f5898f95040da0614aabbdb5940fa8906f3c507f7f0e8
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
## Changelog
|
2
2
|
|
3
|
+
### 0.8.0
|
4
|
+
|
5
|
+
* Improved metadata when using `FmRest::Spyke::Model`. Metadata now uses
|
6
|
+
Struct/OpenStruct, so properties are accessible through `.property`, as well
|
7
|
+
as `[:property]`
|
8
|
+
* Implemented `.find_in_batches` and `.find_each` for `FmRest::Spyke::Model`
|
9
|
+
|
3
10
|
### 0.7.1
|
4
11
|
|
5
12
|
* Made sure `Model.find_one` and `Model.find_some` work without needing to call
|
data/README.md
CHANGED
@@ -195,7 +195,7 @@ FmRest.token_store = FmRest::TokenStore::Redis.new(redis: Redis.new, prefix: "my
|
|
195
195
|
FmRest.token_store = FmRest::TokenStore::Redis.new(prefix: "my-fancy-prefix:", host: "10.0.1.1", port: 6380, db: 15)
|
196
196
|
```
|
197
197
|
|
198
|
-
|
198
|
+
NOTE: redis-rb is not included as a gem dependency of fmrest-ruby, so you'll
|
199
199
|
have to add it to your Gemfile.
|
200
200
|
|
201
201
|
### Moneta
|
@@ -232,7 +232,7 @@ FmRest.token_store = FmRest::TokenStore::Moneta.new(
|
|
232
232
|
)
|
233
233
|
```
|
234
234
|
|
235
|
-
|
235
|
+
NOTE: the moneta gem is not included as a dependency of fmrest-ruby, so
|
236
236
|
you'll have to add it to your Gemfile.
|
237
237
|
|
238
238
|
|
@@ -555,7 +555,7 @@ Honeybee.limit(10)
|
|
555
555
|
```
|
556
556
|
|
557
557
|
NOTE: You can also set a default limit value for a model class, see
|
558
|
-
[
|
558
|
+
[other notes on querying](#other-notes-on-querying).
|
559
559
|
|
560
560
|
You can also use `.limit` to set limits on portals:
|
561
561
|
|
@@ -727,15 +727,15 @@ the scope object:
|
|
727
727
|
Honeybee.limit(10).sort(:name).find_some # => [<Honeybee...>, ...]
|
728
728
|
```
|
729
729
|
|
730
|
-
If you want just a single result you can use `.
|
730
|
+
If you want just a single result you can use `.first` instead (this will
|
731
731
|
force `.limit(1)`):
|
732
732
|
|
733
733
|
```ruby
|
734
|
-
Honeybee.query(name: "Hutch").
|
734
|
+
Honeybee.query(name: "Hutch").first # => <Honeybee...>
|
735
735
|
```
|
736
736
|
|
737
737
|
If you know the id of the record you should use `.find(id)` instead of
|
738
|
-
`.query(id: id).
|
738
|
+
`.query(id: id).first` (so that the sent request is
|
739
739
|
`GET ../:layout/records/:id` instead of `POST ../:layout/_find`).
|
740
740
|
|
741
741
|
```ruby
|
@@ -746,6 +746,52 @@ Note also that if you use `.find(id)` your `.query()` parameters (as well as
|
|
746
746
|
limit, offset and sort parameters) will be discarded as they're not supported
|
747
747
|
by the single record endpoint.
|
748
748
|
|
749
|
+
|
750
|
+
### Finding records in batches
|
751
|
+
|
752
|
+
Sometimes you want to iterate over a very large number of records to do some
|
753
|
+
processing, but requesting them all at once would result in one huge request to
|
754
|
+
the Data API, and loading too many records in memory all at once.
|
755
|
+
|
756
|
+
To mitigate this problem you can use `.find_in_batches` and `.find_each`. If
|
757
|
+
you've used ActiveRecord you're probably familiar with how they operate:
|
758
|
+
|
759
|
+
```ruby
|
760
|
+
# Find records in batches of 100 each
|
761
|
+
Honeybee.query(hive: "Queensville").find_in_batches(batch_size: 100) do |batch|
|
762
|
+
dispatch_bees(batch)
|
763
|
+
end
|
764
|
+
|
765
|
+
# Iterate over all records using batches
|
766
|
+
Honeybee.query(hive: "Queensville").find_each(batch_size: 100) do |bee|
|
767
|
+
bee.dispatch
|
768
|
+
end
|
769
|
+
```
|
770
|
+
|
771
|
+
`.find_in_batches` yields collections of records (batches), while `.find_each`
|
772
|
+
yields individual records, but using batches behind the scenes.
|
773
|
+
|
774
|
+
Both methods accept a block-less form in which case they return an
|
775
|
+
`Enumerator`:
|
776
|
+
|
777
|
+
```ruby
|
778
|
+
batch_enum = Honeybee.find_in_batches
|
779
|
+
|
780
|
+
batch = batch_enum.next # => Spyke::Collection
|
781
|
+
|
782
|
+
batch_enum.each do |batch|
|
783
|
+
process_batch(batch)
|
784
|
+
end
|
785
|
+
|
786
|
+
record_enum = Honeybee.find_each
|
787
|
+
|
788
|
+
record_enum.next # => Honeybee
|
789
|
+
```
|
790
|
+
|
791
|
+
NOTE: By its nature, batch processing is subject to race conditions if other
|
792
|
+
processes are modifying the database.
|
793
|
+
|
794
|
+
|
749
795
|
### Container fields
|
750
796
|
|
751
797
|
You can define container fields on your model class with `container`:
|
@@ -783,6 +829,7 @@ bee.photo.upload(filename_or_io) # Upload a file to the container
|
|
783
829
|
* `:content_type` - The MIME content type to use (defaults to
|
784
830
|
`application/octet-stream`)
|
785
831
|
|
832
|
+
|
786
833
|
### Script execution
|
787
834
|
|
788
835
|
The Data API allows running scripts as part of many types of requests.
|
@@ -870,7 +917,7 @@ separately, under their matching key.
|
|
870
917
|
```ruby
|
871
918
|
bee.save(script: { presort: "My Presort Script", after: "My Script" })
|
872
919
|
|
873
|
-
Honeybee.last_request_metadata
|
920
|
+
Honeybee.last_request_metadata.script
|
874
921
|
# => { after: { result: "oh hi", error: "0" }, presort: { result: "lo", error: "0" } }
|
875
922
|
```
|
876
923
|
|
@@ -884,7 +931,7 @@ is performed on that scope.
|
|
884
931
|
|
885
932
|
```ruby
|
886
933
|
# Find one Honeybee record executing a presort and after script
|
887
|
-
Honeybee.script(presort: ["My Presort Script", "parameter"], after: "My Script").
|
934
|
+
Honeybee.script(presort: ["My Presort Script", "parameter"], after: "My Script").first
|
888
935
|
```
|
889
936
|
|
890
937
|
The model class' `.last_request_metadata` will be set in case you need to get the result.
|
@@ -24,7 +24,8 @@ module FmRest
|
|
24
24
|
# Methods delegated to FmRest::Spyke::Relation
|
25
25
|
delegate :limit, :offset, :sort, :order, :query, :omit, :portal,
|
26
26
|
:portals, :includes, :with_all_portals, :without_portals,
|
27
|
-
:script, :find_one, :
|
27
|
+
:script, :find_one, :first, :any, :find_some,
|
28
|
+
:find_in_batches, :find_each, to: :all
|
28
29
|
|
29
30
|
def all
|
30
31
|
# Use FmRest's Relation instead of Spyke's vanilla one
|
@@ -190,6 +190,79 @@ module FmRest
|
|
190
190
|
rescue ::Spyke::ConnectionError => error
|
191
191
|
fallback_or_reraise(error, default: nil)
|
192
192
|
end
|
193
|
+
alias_method :first, :find_one
|
194
|
+
alias_method :any, :find_one
|
195
|
+
|
196
|
+
# Yields each batch of records that was found by the find options.
|
197
|
+
#
|
198
|
+
# NOTE: By its nature, batch processing is subject to race conditions if
|
199
|
+
# other processes are modifying the database
|
200
|
+
#
|
201
|
+
# @param batch_size [Integer] Specifies the size of the batch.
|
202
|
+
# @return [Enumerator] if called without a block.
|
203
|
+
def find_in_batches(batch_size: 1000)
|
204
|
+
unless block_given?
|
205
|
+
return to_enum(:find_in_batches, batch_size: batch_size) do
|
206
|
+
total = limit(1).find_some.metadata.data_info.found_count
|
207
|
+
(total - 1).div(batch_size) + 1
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
offset = 1 # DAPI offset is 1-based
|
212
|
+
|
213
|
+
loop do
|
214
|
+
relation = offset(offset).limit(batch_size)
|
215
|
+
|
216
|
+
records = relation.find_some
|
217
|
+
|
218
|
+
yield records if records.length > 0
|
219
|
+
|
220
|
+
break if records.length < batch_size
|
221
|
+
|
222
|
+
# Save one iteration if the total is a multiple of batch_size
|
223
|
+
if found_count = records.metadata.data_info && records.metadata.data_info.found_count
|
224
|
+
break if found_count == (offset - 1) + batch_size
|
225
|
+
end
|
226
|
+
|
227
|
+
offset += batch_size
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
# Looping through a collection of records from the database (using the
|
232
|
+
# #all method, for example) is very inefficient since it will fetch and
|
233
|
+
# instantiate all the objects at once.
|
234
|
+
#
|
235
|
+
# In that case, batch processing methods allow you to work with the
|
236
|
+
# records in batches, thereby greatly reducing memory consumption and be
|
237
|
+
# lighter on the Data API server.
|
238
|
+
#
|
239
|
+
# The find_each method uses #find_in_batches with a batch size of 1000
|
240
|
+
# (or as specified by the :batch_size option).
|
241
|
+
#
|
242
|
+
# NOTE: By its nature, batch processing is subject to race conditions if
|
243
|
+
# other processes are modifying the database
|
244
|
+
#
|
245
|
+
# @param (see #find_in_batches)
|
246
|
+
# @example
|
247
|
+
# Person.find_each do |person|
|
248
|
+
# person.greet
|
249
|
+
# end
|
250
|
+
#
|
251
|
+
# Person.query(name: "==Mitch").find_each do |person|
|
252
|
+
# person.say_hi
|
253
|
+
# end
|
254
|
+
# @return (see #find_in_batches)
|
255
|
+
def find_each(batch_size: 1000)
|
256
|
+
unless block_given?
|
257
|
+
return to_enum(:find_each, batch_size: batch_size) do
|
258
|
+
limit(1).find_some.metadata.data_info.found_count
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
find_in_batches(batch_size: batch_size) do |records|
|
263
|
+
records.each { |r| yield r }
|
264
|
+
end
|
265
|
+
end
|
193
266
|
|
194
267
|
protected
|
195
268
|
|
@@ -1,9 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "json"
|
4
|
+
require "ostruct"
|
4
5
|
|
5
6
|
module FmRest
|
6
7
|
module Spyke
|
8
|
+
# Metadata class to be passed to Spyke::Collection#metadata
|
9
|
+
class Metadata < Struct.new(:messages, :script, :data_info)
|
10
|
+
alias_method :scripts, :script
|
11
|
+
end
|
12
|
+
|
13
|
+
class DataInfo < OpenStruct
|
14
|
+
def total_record_count; totalRecordCount; end
|
15
|
+
def found_count; foundCount; end
|
16
|
+
def returned_count; returnedCount; end
|
17
|
+
end
|
18
|
+
|
7
19
|
# Response Faraday middleware for converting FM API's response JSON into
|
8
20
|
# Spyke's expected format
|
9
21
|
class SpykeFormatter < ::Faraday::Response::Middleware
|
@@ -77,36 +89,61 @@ module FmRest
|
|
77
89
|
|
78
90
|
# @param json [Hash]
|
79
91
|
# @param include_errors [Boolean]
|
80
|
-
# @return [
|
92
|
+
# @return [FmRest::Spyke::Metadata] the skeleton structure for a
|
93
|
+
# Spyke-formatted response
|
81
94
|
def build_base_hash(json, include_errors = false)
|
82
95
|
{
|
83
|
-
metadata:
|
84
|
-
|
96
|
+
metadata: Metadata.new(
|
97
|
+
prepare_messages(json),
|
98
|
+
prepare_script_results(json),
|
99
|
+
prepare_data_info(json)
|
100
|
+
).freeze,
|
101
|
+
errors: include_errors ? prepare_errors(json) : {}
|
85
102
|
}
|
86
103
|
end
|
87
104
|
|
88
105
|
# @param json [Hash]
|
89
|
-
# @return [
|
106
|
+
# @return [Array<OpenStruct>] the skeleton structure for a
|
107
|
+
# Spyke-formatted response
|
108
|
+
def prepare_messages(json)
|
109
|
+
return [] unless json[:messages]
|
110
|
+
json[:messages].map { |m| OpenStruct.new(m).freeze }.freeze
|
111
|
+
end
|
112
|
+
|
113
|
+
# @param json [Hash]
|
114
|
+
# @return [OpenStruct] the script(s) execution results for Spyke metadata
|
115
|
+
# format
|
90
116
|
def prepare_script_results(json)
|
91
117
|
results = {}
|
92
118
|
|
93
119
|
[:prerequest, :presort].each do |s|
|
94
120
|
if json[:response][:"scriptError.#{s}"]
|
95
|
-
results[s] =
|
121
|
+
results[s] = OpenStruct.new(
|
96
122
|
result: json[:response][:"scriptResult.#{s}"],
|
97
123
|
error: json[:response][:"scriptError.#{s}"]
|
98
|
-
|
124
|
+
).freeze
|
99
125
|
end
|
100
126
|
end
|
101
127
|
|
102
128
|
if json[:response][:scriptError]
|
103
|
-
results[:after] =
|
129
|
+
results[:after] = OpenStruct.new(
|
104
130
|
result: json[:response][:scriptResult],
|
105
131
|
error: json[:response][:scriptError]
|
106
|
-
|
132
|
+
).freeze
|
107
133
|
end
|
108
134
|
|
109
|
-
results
|
135
|
+
results.present? ? OpenStruct.new(results).freeze : nil
|
136
|
+
end
|
137
|
+
|
138
|
+
# @param json [Hash]
|
139
|
+
# @return [OpenStruct] the script(s) execution results for
|
140
|
+
# Spyke metadata format
|
141
|
+
def prepare_data_info(json)
|
142
|
+
data_info = json[:response] && json[:response][:dataInfo]
|
143
|
+
|
144
|
+
return nil unless data_info.present?
|
145
|
+
|
146
|
+
DataInfo.new(data_info).freeze
|
110
147
|
end
|
111
148
|
|
112
149
|
# @param json [Hash]
|
data/lib/fmrest/v1/connection.rb
CHANGED
@@ -31,10 +31,6 @@ module FmRest
|
|
31
31
|
conn.request :multipart
|
32
32
|
conn.request :json
|
33
33
|
|
34
|
-
if options[:log]
|
35
|
-
conn.response :logger, nil, bodies: true, headers: true
|
36
|
-
end
|
37
|
-
|
38
34
|
# Allow overriding the default response middleware
|
39
35
|
if block_given?
|
40
36
|
yield conn, options
|
@@ -43,6 +39,10 @@ module FmRest
|
|
43
39
|
conn.response :json
|
44
40
|
end
|
45
41
|
|
42
|
+
if options[:log]
|
43
|
+
conn.response :logger, nil, bodies: true, headers: true
|
44
|
+
end
|
45
|
+
|
46
46
|
conn.adapter Faraday.default_adapter
|
47
47
|
end
|
48
48
|
end
|
data/lib/fmrest/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fmrest
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pedro Carbajal
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-05-
|
11
|
+
date: 2020-05-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|