fmrest 0.7.1 → 0.8.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 +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
|