amplify_syndication 0.2.0 → 0.2.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c904621a09cb44ba73a694784f8f3ca99361b845f4f8f79e9c07d284b32f1f7c
4
- data.tar.gz: '01094a0769aa29721d17bf14567daa765debbb580a9456913139459ec25d2bb1'
3
+ metadata.gz: 6b508d426a6c47cead51ad5c59d84322c749a60dc4155add4454a53a388e38e2
4
+ data.tar.gz: 83b82be51f0a21440dfb5ab571a28f11bbbd53e7d23de8ca6b9570a0e6aa9813
5
5
  SHA512:
6
- metadata.gz: 82ac0a3a1682ed77be6487b41be10b41b1f48efb9fed201ee1c5c9935dbd2d965c5e68a199b4103fb1f2ae5f0b2f3838dcdb34532be28536ff27a5e299100449
7
- data.tar.gz: 42309083ecdf14c0112dad8d8d36438daf591648ccb3bd374ba5925a8ec8d588a23224fe166b5bd4c59bde993f25eed79b2e033237039f43e3e5c366b7933448
6
+ metadata.gz: b8ed56083a3c0eab1bcab901a10d8ea6946d0be4c36171387c19a68f23b389580378b6f821629d0197bedd63ddf8c9636b320c97ca85416c59a22795ca00f7fc
7
+ data.tar.gz: 625a208f80f0e38a23a03f75aa2fe7d86bd7a569ab79a0882e5500c4c8376ac1cf6b3ded8547ca48c677efe03a4fce7cef895212c3f80f31e5f86b8511ba9e95
data/README.md CHANGED
@@ -153,6 +153,24 @@ puts recent_media
153
153
 
154
154
  ---
155
155
 
156
+ ## Lookups
157
+
158
+ ### Fetch all lookups (simple)
159
+
160
+ Fetch the entire `Lookup` table into memory:
161
+
162
+ ```ruby
163
+ api = AmplifySyndication::API.new
164
+
165
+ lookups = api.fetch_all_lookups(
166
+ batch_size: 100,
167
+ sleep_seconds: 2 # optional throttle between API calls
168
+ )
169
+
170
+ puts "Loaded #{lookups.size} lookup rows"
171
+
172
+ ---
173
+
156
174
  ## Error Handling
157
175
 
158
176
  If an API call fails, the gem raises a StandardError with details of the HTTP response:
@@ -1,15 +1,22 @@
1
+ require "uri"
2
+
1
3
  module AmplifySyndication
2
4
  class API
3
5
  def initialize(client = Client.new)
4
6
  @client = client
5
7
  end
6
8
 
9
+ # === Metadata ===
10
+
7
11
  # Fetch metadata
8
12
  def fetch_metadata
9
13
  @client.get("$metadata?$format=json")
10
14
  end
11
15
 
12
- # Fetch property fields
16
+ # === Field helpers ===
17
+
18
+ # Fetch all Field records for the Property resource in a single call
19
+ # (still paginated under the hood).
13
20
  def fetch_property_fields(batch_size: 50, sleep_seconds: 10)
14
21
  offset = 0
15
22
  fields = []
@@ -17,57 +24,83 @@ module AmplifySyndication
17
24
  loop do
18
25
  query_options = {
19
26
  "$filter" => "ResourceName eq 'Property'",
20
- "$top" => batch_size,
21
- "$skip" => offset
27
+ "$top" => batch_size,
28
+ "$skip" => offset
22
29
  }.compact
30
+
23
31
  response = fetch_with_options("Field", query_options)
24
- batch = response["value"]
32
+ batch = response["value"] || []
25
33
  break if batch.empty?
26
34
 
27
35
  fields.concat(batch)
28
-
29
36
  offset += batch_size
30
37
 
31
- # Safety sleep between API calls
32
38
  sleep(sleep_seconds) if sleep_seconds.positive?
33
39
  end
34
40
 
35
41
  fields
36
42
  end
37
43
 
38
- def fetch_all_lookups(batch_size: 50, sleep_seconds: 10, filter: nil)
39
- offset = 0
40
- values = []
44
+ # === Lookup helpers ===
41
45
 
42
- loop do
43
- query_options = {
44
- "$top" => batch_size,
45
- "$skip" => offset
46
- }
47
-
48
- # Apply filter only when provided
49
- if filter.present?
50
- combined_filter =
51
- filter.is_a?(Array) ? filter.join(" and ") : filter
52
- query_options["$filter"] = combined_filter
53
- end
46
+ # Fetch a single Lookup page (batch) with optional filter.
47
+ #
48
+ # filter can be:
49
+ # - a String: "LookupStatus eq 'Active'"
50
+ # - an Array of strings: ["LookupStatus eq 'Active'", "LookupName eq 'PropertyType'"]
51
+ def fetch_lookup_batch(skip:, top: 50, filter: nil)
52
+ query_options = {
53
+ "$top" => top,
54
+ "$skip" => skip
55
+ }
54
56
 
55
- response = fetch_with_options("Lookup", query_options)
56
- batch = response["value"] || []
57
- break if batch.empty?
57
+ if filter
58
+ combined_filter =
59
+ filter.is_a?(Array) ? filter.join(" and ") : filter
60
+ query_options["$filter"] = combined_filter
61
+ end
58
62
 
59
- values.concat(batch)
63
+ response = fetch_with_options("Lookup", query_options)
64
+ response["value"] || []
65
+ end
60
66
 
61
- offset += batch_size
67
+ # Iterate over Lookup records in batches, yielding each batch.
68
+ #
69
+ # Example:
70
+ # api.each_lookup_batch(batch_size: 100, filter: "LookupStatus eq 'Active'") do |batch|
71
+ # batch.each { |row| VowLookup.upsert_from_row(row) }
72
+ # end
73
+ def each_lookup_batch(batch_size: 50, sleep_seconds: 10, filter: nil)
74
+ skip = 0
75
+
76
+ loop do
77
+ batch = fetch_lookup_batch(skip: skip, top: batch_size, filter: filter)
78
+ break if batch.empty?
62
79
 
63
- # Safety sleep between API calls
80
+ yield(batch) if block_given?
81
+
82
+ skip += batch_size
64
83
  sleep(sleep_seconds) if sleep_seconds.positive?
65
84
  end
85
+ end
66
86
 
67
- values
87
+ # Fetch all Lookup rows into memory (simple usage).
88
+ #
89
+ # For long-running syncs, prefer each_lookup_batch so your app can
90
+ # handle persistence/checkpointing per batch.
91
+ def fetch_all_lookups(batch_size: 50, sleep_seconds: 10, filter: nil)
92
+ results = []
93
+
94
+ each_lookup_batch(batch_size: batch_size,
95
+ sleep_seconds: sleep_seconds,
96
+ filter: filter) do |batch|
97
+ results.concat(batch)
98
+ end
99
+
100
+ results
68
101
  end
69
102
 
70
- # get lookup name values
103
+ # Get all rows for a single LookupName (convenience helper).
71
104
  def lookup(lookup_name, batch_size: 50, sleep_seconds: 10)
72
105
  offset = 0
73
106
  values = []
@@ -75,29 +108,31 @@ module AmplifySyndication
75
108
  loop do
76
109
  query_options = {
77
110
  "$filter" => "LookupName eq '#{lookup_name}'",
78
- "$top" => batch_size,
79
- "$skip" => offset
111
+ "$top" => batch_size,
112
+ "$skip" => offset
80
113
  }.compact
114
+
81
115
  response = fetch_with_options("Lookup", query_options)
82
- batch = response["value"]
116
+ batch = response["value"] || []
83
117
  break if batch.empty?
84
118
 
85
119
  values.concat(batch)
86
120
  offset += batch_size
87
121
 
88
- # Safety sleep between API calls
89
122
  sleep(sleep_seconds) if sleep_seconds.positive?
90
123
  end
91
124
 
92
125
  values
93
126
  end
94
127
 
95
- # Fetch basic property data
128
+ # === Property helpers ===
129
+
130
+ # Fetch basic property data (simple test helper)
96
131
  def fetch_property_data(limit = 1)
97
132
  @client.get("Property", "$top" => limit)
98
133
  end
99
134
 
100
- # Fetch data with query options
135
+ # Fetch data with query options against an arbitrary resource
101
136
  def fetch_with_options(resource, query_options = {})
102
137
  @client.get_with_options(resource, query_options)
103
138
  end
@@ -112,6 +147,7 @@ module AmplifySyndication
112
147
  "$skip" => skip,
113
148
  "$count" => count
114
149
  }.compact
150
+
115
151
  fetch_with_options("Property", query_options)
116
152
  end
117
153
 
@@ -120,82 +156,142 @@ module AmplifySyndication
120
156
  fetch_filtered_properties(count: "true", top: 0)
121
157
  end
122
158
 
123
- ### Replication Methods ###
159
+ # === Replication helpers ===
160
+ #
161
+ # These three methods give you flexible control:
162
+ #
163
+ # - fetch_initial_download_batch -> single page
164
+ # - each_initial_download_batch -> yields per page
165
+ # - perform_initial_download -> fetch everything into memory
124
166
 
125
- # Perform initial download for replication
126
- def perform_initial_download(
167
+ # Build and fetch a single replication batch for a resource.
168
+ def fetch_initial_download_batch(
127
169
  resource: "Property",
128
170
  batch_size: 100,
129
171
  fields: ["ModificationTimestamp", "ListingKey"],
130
172
  filter: nil,
131
- sleep_seconds: 10,
132
- checkpoint: { last_timestamp: "1970-01-01T00:00:00Z", last_key: 0 },
173
+ checkpoint: { last_timestamp: "1970-01-01T00:00:00Z", last_key: 0 }
133
174
  )
134
- puts "Starting initial download..."
135
- all_records = [] # Array to collect all records
175
+ encoded_ts = URI.encode_www_form_component(checkpoint[:last_timestamp])
136
176
 
137
- loop do
138
- puts "Fetching batch with timestamp > #{checkpoint[:last_timestamp]} and key > #{checkpoint[:last_key]}..."
177
+ # checkpoint filter: everything strictly after the last (timestamp, key) pair
178
+ checkpoint_filter = "(ModificationTimestamp gt #{encoded_ts}) " \
179
+ "or (ModificationTimestamp eq #{encoded_ts} and ListingKey gt '#{checkpoint[:last_key]}')"
139
180
 
140
- # Build batch filter
141
- batch_filter = []
142
- batch_filter << "#{filter}" if filter
143
- batch_filter << "(ModificationTimestamp gt #{URI.encode_www_form_component(checkpoint[:last_timestamp])})"
144
- batch_filter << "or (ModificationTimestamp eq #{URI.encode_www_form_component(checkpoint[:last_timestamp])} and ListingKey gt '#{checkpoint[:last_key]}')"
145
- batch_filter = batch_filter.join(" ")
181
+ conditions = []
182
+ conditions << "(#{filter})" if filter
183
+ conditions << "(#{checkpoint_filter})"
146
184
 
147
- # Query options
148
- query_options = {
149
- "$select" => fields.join(","),
150
- "$filter" => batch_filter,
151
- "$orderby" => "ModificationTimestamp,ListingKey",
152
- "$top" => batch_size
153
- }
185
+ query_options = {
186
+ "$select" => fields.join(","),
187
+ "$filter" => conditions.join(" and "),
188
+ "$orderby" => "ModificationTimestamp,ListingKey",
189
+ "$top" => batch_size
190
+ }
191
+
192
+ response = fetch_with_options(resource, query_options)
193
+ response["value"] || []
194
+ end
154
195
 
155
- # Debugging: Print the full query options
156
- puts "Query options: #{query_options.inspect}"
196
+ # Iterate over replication batches, yielding [batch, checkpoint].
197
+ #
198
+ # You can persist checkpoint per batch to resume later if something fails.
199
+ def each_initial_download_batch(
200
+ resource: "Property",
201
+ batch_size: 100,
202
+ fields: ["ModificationTimestamp", "ListingKey"],
203
+ filter: nil,
204
+ sleep_seconds: 10,
205
+ checkpoint: { last_timestamp: "1970-01-01T00:00:00Z", last_key: 0 }
206
+ )
207
+ loop do
208
+ batch = fetch_initial_download_batch(
209
+ resource: resource,
210
+ batch_size: batch_size,
211
+ fields: fields,
212
+ filter: filter,
213
+ checkpoint: checkpoint
214
+ )
157
215
 
158
- # Send request
159
- response = fetch_with_options(resource, query_options)
160
- records = response["value"]
161
- break if records.empty?
216
+ break if batch.empty?
162
217
 
163
- # Collect batch records
164
- all_records.concat(records)
218
+ yield(batch, checkpoint) if block_given?
165
219
 
166
- # Update checkpoint with the last record in the batch
167
- last_record = records.last
220
+ # Update checkpoint automatically based on the last record in the batch
221
+ last_record = batch.last
168
222
  checkpoint[:last_timestamp] = last_record["ModificationTimestamp"]
169
- checkpoint[:last_key] = last_record["ListingKey"]
223
+ checkpoint[:last_key] = last_record["ListingKey"]
170
224
 
171
- # Stop if the number of records is less than the batch size
172
- break if records.size < batch_size
225
+ break if batch.size < batch_size
173
226
 
174
- # Safety sleep between API calls
175
227
  sleep(sleep_seconds) if sleep_seconds.positive?
176
228
  end
177
-
178
- puts "Initial download complete."
179
- all_records # Return the collected records
180
229
  end
181
230
 
182
- # Fetch updates since the last checkpoint
183
- def fetch_updates(
184
- resource: "Property",
185
- batch_size: 100,
186
- fields: ["ModificationTimestamp", "ListingKey"],
187
- filter: nil,
188
- checkpoint: { last_timestamp: "1970-01-01T00:00:00Z", last_key: 0 }
189
- )
190
- perform_initial_download(
231
+ # Perform initial download for replication, buffering all results
232
+ # into memory (simple usage).
233
+ #
234
+ # For large datasets, prefer each_initial_download_batch.
235
+ def perform_initial_download(
236
+ resource: "Property",
237
+ batch_size: 100,
238
+ fields: ["ModificationTimestamp", "ListingKey"],
239
+ filter: nil,
240
+ sleep_seconds: 10,
241
+ checkpoint: { last_timestamp: "1970-01-01T00:00:00Z", last_key: 0 }
242
+ )
243
+ results = []
244
+
245
+ puts "Starting initial download..."
246
+
247
+ each_initial_download_batch(
191
248
  resource: resource,
192
249
  batch_size: batch_size,
193
250
  fields: fields,
194
251
  filter: filter,
252
+ sleep_seconds: sleep_seconds,
195
253
  checkpoint: checkpoint
196
- ) do |batch|
197
- # Process updates
198
- yield(batch) if block_given?
254
+ ) do |batch, _checkpoint|
255
+ results.concat(batch)
256
+ end
257
+
258
+ puts "Initial download complete."
259
+ results
260
+ end
261
+
262
+ # Fetch updates since the last checkpoint.
263
+ #
264
+ # If a block is given, yields each batch; otherwise returns all
265
+ # updates in a single array (same as perform_initial_download).
266
+ def fetch_updates(
267
+ resource: "Property",
268
+ batch_size: 100,
269
+ fields: ["ModificationTimestamp", "ListingKey"],
270
+ filter: nil,
271
+ checkpoint: { last_timestamp: "1970-01-01T00:00:00Z", last_key: 0 },
272
+ sleep_seconds: 10
273
+ )
274
+ if block_given?
275
+ each_initial_download_batch(
276
+ resource: resource,
277
+ batch_size: batch_size,
278
+ fields: fields,
279
+ filter: filter,
280
+ sleep_seconds: sleep_seconds,
281
+ checkpoint: checkpoint
282
+ ) do |batch, cp|
283
+ yield(batch, cp)
284
+ end
285
+ nil
286
+ else
287
+ perform_initial_download(
288
+ resource: resource,
289
+ batch_size: batch_size,
290
+ fields: fields,
291
+ filter: filter,
292
+ sleep_seconds: sleep_seconds,
293
+ checkpoint: checkpoint
294
+ )
199
295
  end
200
296
  end
201
297
 
@@ -206,7 +302,7 @@ module AmplifySyndication
206
302
  @client.get(endpoint)
207
303
  end
208
304
 
209
- ### Media Methods ###
305
+ # === Media helpers ===
210
306
 
211
307
  # Fetch a media record by MediaKey
212
308
  def fetch_media_by_key(media_key)
@@ -222,22 +318,25 @@ module AmplifySyndication
222
318
  batch_size: 100
223
319
  )
224
320
  query_options = {
225
- "$filter" => "#{filter} and ModificationTimestamp ge #{modification_date}",
321
+ "$filter" => "#{filter} and ModificationTimestamp ge #{modification_date}",
226
322
  "$orderby" => orderby,
227
- "$top" => batch_size
323
+ "$top" => batch_size
228
324
  }
325
+
229
326
  fetch_with_options("Media", query_options)
230
327
  end
231
328
 
232
329
  # Fetch media by ResourceName and ResourceRecordKey
233
330
  def fetch_media_by_resource(resource_name, resource_key, batch_size = 100)
234
331
  filter = "ResourceRecordKey eq '#{resource_key}' and ResourceName eq '#{resource_name}'"
332
+
235
333
  query_options = {
236
- "$filter" => filter,
334
+ "$filter" => filter,
237
335
  "$orderby" => "ModificationTimestamp,MediaKey",
238
- "$top" => batch_size
336
+ "$top" => batch_size
239
337
  }
338
+
240
339
  fetch_with_options("Media", query_options)
241
340
  end
242
341
  end
243
- end
342
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AmplifySyndication
4
- VERSION = "0.2.0"
4
+ VERSION = "0.2.2"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: amplify_syndication
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen Higgins