db_fuel 1.1.0.pre.alpha → 1.2.1.pre.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fb563756df642d23e7d268cf0e7b35735300ddfc84da31e0ee11d7e138d41584
4
- data.tar.gz: f5720ed0f4649ba82d0c233cf9ccbc4aff5d54d8d9c4267c830467f43f85cdf3
3
+ metadata.gz: 0a93b59424a5cf6866f1061785f41a0c49c1bcc707ce84a6652f481a283aab64
4
+ data.tar.gz: 3b7a65c38a5cc24ee844412ef6cb1bc3b2bc2a33c9afd564196e1b1f739108b7
5
5
  SHA512:
6
- metadata.gz: 16c63b0939d36f32cfc72baeefc20fbb88e7285d5c5e8266d337f55362909a6f751f7f2b1c535f6c231a11adba2693807c68e6af1b4d5eaaf4b14a9213589d01
7
- data.tar.gz: a5143b01108dc065d98a5e085da06c8e82a5f0c084a0097128219e8c49542818a8ce754ef1af79be23d6493f259f745657bba4e74051cf856264fbe87a76dfde
6
+ metadata.gz: f5bc7c2644584e1f8e0412e0215bef34b6431b085e18fba822723e94ab0baea9c7a97a25d4970dc9169a2adc23f7818d618f48f8ab2f9d7ddb9f5a5914ecd356
7
+ data.tar.gz: 02d4f517420b8aa3331d99a69dadd39faa7b53382e89f153467eaea3d74a52141e75c000cba62c090e8e0aaa2fb2d293908160b016b242e35d483f396e37b542
@@ -1,6 +1,7 @@
1
1
  AllCops:
2
2
  TargetRubyVersion: 2.5
3
3
  NewCops: enable
4
+ SuggestExtensions: false
4
5
 
5
6
  Layout/LineLength:
6
7
  Max: 100
@@ -8,7 +9,7 @@ Layout/LineLength:
8
9
  - db_fuel.gemspec
9
10
 
10
11
  Metrics/BlockLength:
11
- ExcludedMethods:
12
+ IgnoredMethods:
12
13
  - let
13
14
  - it
14
15
  - describe
@@ -0,0 +1 @@
1
+ ruby 2.6.6
@@ -1,7 +1,8 @@
1
- # 1.1.0 (TBD)
1
+ # 1.1.0 (Decmeber 1st, 2020)
2
2
 
3
3
  New Jobs:
4
4
 
5
+ * db_fuel/active_record/find_or_insert
5
6
  * db_fuel/active_record/insert
6
7
  * db_fuel/active_record/update
7
8
 
data/README.md CHANGED
@@ -24,13 +24,16 @@ Refer to the [Burner](https://github.com/bluemarblepayroll/burner) library for m
24
24
 
25
25
  ### ActiveRecord Jobs
26
26
 
27
- * **db_fuel/active_record/insert** [table_name, attributes, debug, primary_key, register, separator, timestamps]: This job can take the objects in a register and insert them into a database table. Attributes defines which object properties to convert to SQL. Refer to the class and constructor specification for more detail.
28
- * **db_fuel/active_record/update** [table_name, attributes, debug, register, separator, timestamps, unique_keys]: This job can take the objects in a register and updates them within a database table. Attributes defines which object properties to convert to SQL SET clauses while unique_keys translate to WHERE clauses. Refer to the class and constructor specification for more detail.
27
+ * **db_fuel/active_record/find_or_insert** [name, table_name, attributes, debug, primary_key, register, separator, timestamps, unique_attributes]: An extension of the `db_fuel/active_record/insert` job that adds an existence check before SQL insertion. The `unique_attributes` will be converted to WHERE clauses for performing the existence check.
28
+ * **db_fuel/active_record/insert** [name, table_name, attributes, debug, primary_key, register, separator, timestamps]: This job can take the objects in a register and insert them into a database table. If primary_key is specified then its key will be set to the primary key. Note that composite primary keys are not supported. Attributes defines which object properties to convert to SQL. Refer to the class and constructor specification for more detail.
29
+ * **db_fuel/active_record/update_all** [name, table_name, attributes, debug, register, separator, timestamps, unique_attributes]: This job can take the objects in a register and updates them within a database table. Attributes defines which object properties to convert to SQL SET clauses while unique_attributes translate to WHERE clauses. One or more records may be updated at a time. Refer to the class and constructor specification for more detail.
30
+ * **db_fuel/active_record/update** [name, table_name, attributes, debug, register, primary_key, separator, timestamps, unique_attributes]: This job can take the unique objects in a register and updates them within a database table. Attributes defines which object properties to convert to SQL SET clauses while unique_attributes translate to WHERE clauses to find the records to update. The primary_key is used to update the unique record. Only one record will be updated per statement. Refer to the class and constructor specification for more detail.
31
+ * **db_fuel/active_record/upsert** [name, table_name, attributes, debug, primary_key, register, separator, timestamps, unique_attributes]: This job can take the objects in a register and either inserts or updates them within a database table. Attributes defines which object properties to convert to SQL SET clauses while each key in unique_attributes become a WHERE clause in order to check for the existence of a specific record. The updated record will use the primary_key specified to perform the UPDATE operation. Note that composite primary keys are not supported. Refer to the class and constructor specification for more detail.
29
32
 
30
33
  ### Dbee Jobs
31
34
 
32
- * **db_fuel/dbee/query** [model, query, register]: Pass in a [Dbee](https://github.com/bluemarblepayroll/dbee) model and query and store the results in the specified register. Refer to the [Dbee](https://github.com/bluemarblepayroll/dbee) library directly on how to craft a model or query.
33
- * **db_fuel/dbee/range** [key, key_path, model, query, register, separator]: Similar to `db_fuel/dbee/query` with the addition of being able to grab a list of values from the register to use as a Dbee EQUALS/IN filter. This helps to dynamically limit the resulting record set. The key is used to specify where to grab the list of values, while the key_path will be used to craft the [Dbee equal's filter](https://github.com/bluemarblepayroll/dbee/blob/master/lib/dbee/query/filters/equals.rb). Separator is exposed in case nested object support is necessary.
35
+ * **db_fuel/dbee/query** [model, query, register, debug]: Pass in a [Dbee](https://github.com/bluemarblepayroll/dbee) model and query and store the results in the specified register. Refer to the [Dbee](https://github.com/bluemarblepayroll/dbee) library directly on how to craft a model or query.
36
+ * **db_fuel/dbee/range** [key, key_path, model, query, register, separator, debug]: Similar to `db_fuel/dbee/query` with the addition of being able to grab a list of values from the register to use as a Dbee EQUALS/IN filter. This helps to dynamically limit the resulting record set. The key is used to specify where to grab the list of values, while the key_path will be used to craft the [Dbee equal's filter](https://github.com/bluemarblepayroll/dbee/blob/master/lib/dbee/query/filters/equals.rb). Separator is exposed in case nested object support is necessary.
34
37
 
35
38
  ## Examples
36
39
 
@@ -95,6 +98,10 @@ If we were to inspect the contents of `payload` we should see the patient's resu
95
98
  payload['patients'] # array in form of: [ { "id" => 1, "first_name" => "Something" }, ... ]
96
99
  ````
97
100
 
101
+ Notes
102
+
103
+ * Set `debug: true` to print out SQL statement in the output (not for production use.)
104
+
98
105
  ### Limiting Result Sets
99
106
 
100
107
  The `db_fuel/dbee/query` does not provide a way to dynamically connect the query to existing data. You are free to put any Dbee query filters in the query declaration, but what if you would like to further limit this based on the knowledge of a range of values? The `db_fuel/dbee/range` job is meant to do exactly this. On the surface it is mainly an extension of the `db_fuel/dbee/query` job.
@@ -148,6 +155,10 @@ payload['patients'] # array in form of: [ { "id" => 1, "first_name" => "Somethin
148
155
 
149
156
  The only difference between the query and range jobs should be the latter is limited based on the incoming first names.
150
157
 
158
+ Notes
159
+
160
+ * Set `debug: true` to print out SQL statement in the output (not for production use.)
161
+
151
162
  ### Updating the Database
152
163
 
153
164
  #### Inserting Records
@@ -193,11 +204,55 @@ There should now be two new patients, AB0 and AB1, present in the table `patient
193
204
  Notes:
194
205
 
195
206
  * Since we specified the `primary_key`, the records' `id` attributes should be set to their respective primary key values.
207
+ * Composite primary keys are not currently supported.
196
208
  * Set `debug: true` to print out each INSERT statement in the output (not for production use.)
197
209
 
210
+ #### Inserting Only New Records
211
+
212
+ Another job `db_fuel/active_record/find_or_insert` allows for an existence check to performed each insertion. If a record is found then it will not insert the record. If `primary_key` is set then the existence check will also still set the primary key on the payload's respective object. Note that composite primary keys are not currently supported. We can build on the above insert example for only inserting new patients if their chart_number is unique:
213
+
214
+ ````ruby
215
+ pipeline = {
216
+ jobs: [
217
+ {
218
+ name: :load_patients,
219
+ type: 'b/value/static',
220
+ register: :patients,
221
+ value: [
222
+ { chart_number: 'B0001', first_name: 'Bugs', last_name: 'Bunny' },
223
+ { chart_number: 'B0002', first_name: 'Babs', last_name: 'Bunny' }
224
+ ]
225
+ },
226
+ {
227
+ name: 'insert_patients',
228
+ type: 'db_fuel/active_record/insert',
229
+ register: :patients,
230
+ attributes: [
231
+ { key: :chart_number },
232
+ { key: :first_name },
233
+ { key: :last_name }
234
+ ],
235
+ table_name: 'patients',
236
+ primary_key: {
237
+ key: :id
238
+ },
239
+ unique_attributes: [
240
+ { key: :chart_number }
241
+ ]
242
+ }
243
+ ]
244
+ }
245
+
246
+ payload = Burner::Payload.new
247
+
248
+ Burner::Pipeline.make(pipeline).execute(payload: payload)
249
+ ````
250
+
251
+ Now only records where the chart_number does not match an existing record will be inserted.
252
+
198
253
  #### Updating Records
199
254
 
200
- Let's say we now want to update those records' last names:
255
+ Let's say we now want to update these unique records' last names:
201
256
 
202
257
  ````ruby
203
258
  pipeline = {
@@ -219,7 +274,92 @@ pipeline = {
219
274
  { key: :last_name }
220
275
  ],
221
276
  table_name: 'patients',
222
- unique_keys: [
277
+ primary_key: {
278
+ key: :id
279
+ },
280
+ unique_attributes: [
281
+ { key: :chart_number }
282
+ ]
283
+ }
284
+ ]
285
+ }
286
+
287
+ payload = Burner::Payload.new
288
+
289
+ Burner::Pipeline.make(pipeline).execute(payload: payload)
290
+ ````
291
+
292
+ Each database record should have been updated with their new respective last names based on the primary key specified.
293
+
294
+ #### Updating All Records
295
+
296
+ Let's say we want to update those records' midddle names:
297
+
298
+ ````ruby
299
+ pipeline = {
300
+ jobs: [
301
+ {
302
+ name: :load_patients,
303
+ type: 'b/value/static',
304
+ register: :patients,
305
+ value: [
306
+ { chart_number: 'B0001', middle_name: 'Rabbit' },
307
+ { chart_number: 'C0001', middle_name: 'Elf' }
308
+ ]
309
+ },
310
+ {
311
+ name: 'update_patients',
312
+ type: 'db_fuel/active_record/update_all',
313
+ register: :patients,
314
+ attributes: [
315
+ { key: :last_name }
316
+ ],
317
+ table_name: 'patients',
318
+ unique_attributes: [
319
+ { key: :chart_number }
320
+ ]
321
+ }
322
+ ]
323
+ }
324
+
325
+ payload = Burner::Payload.new
326
+
327
+ Burner::Pipeline.make(pipeline).execute(payload: payload)
328
+ ````
329
+
330
+ Each database record should have been updated with their new respective middle names based on chart_number.
331
+
332
+ #### Upserting Records
333
+
334
+ Let's say we don't know if these chart_number values already exist or not.
335
+ So we want db_fuel to either insert a record if the chart_number doesn't exist or update the record if the chart_number already exists.
336
+
337
+ ````ruby
338
+ pipeline = {
339
+ jobs: [
340
+ {
341
+ name: :load_patients,
342
+ type: 'b/value/static',
343
+ register: :patients,
344
+ value: [
345
+ { chart_number: 'B0002', first_name: 'Babs', last_name: 'Bunny' },
346
+ { chart_number: 'B0003', first_name: 'Daffy', last_name: 'Duck' }
347
+ ]
348
+ },
349
+ {
350
+ name: 'update_patients',
351
+ type: 'db_fuel/active_record/upsert',
352
+ register: :patients,
353
+ attributes: [
354
+ { key: :chart_number },
355
+ { key: :first_name },
356
+ { key: :last_name }
357
+ ],
358
+ table_name: 'patients',
359
+ primary_key: {
360
+ key: :id
361
+ },
362
+ unique_attributes: [
223
363
  { key: :chart_number }
224
364
  ]
225
365
  }
@@ -231,11 +371,12 @@ payload = Burner::Payload.new
231
371
  Burner::Pipeline.make(pipeline).execute(payload: payload)
232
372
  ````
233
373
 
234
- Each database record should have been updated with their new respective last names.
374
+ Each database record should have been either inserted or updated with their corresponding values. In this case Babs' last name
375
+ was switched back to Bunny and a new record was created for Daffy Duck.
235
376
 
236
377
  Notes:
237
378
 
238
- * The unique_keys translate to WHERE clauses.
379
+ * The `unique_attributes` translate to WHERE clauses.
239
380
  * Set `debug: true` to print out each UPDATE statement in the output (not for production use.)
240
381
  ## Contributing
241
382
 
@@ -11,8 +11,8 @@ Gem::Specification.new do |s|
11
11
  This library adds database-centric jobs to the Burner library. Burner does not ship with database jobs out of the box.
12
12
  DESCRIPTION
13
13
 
14
- s.authors = ['Matthew Ruggio']
15
- s.email = ['mruggio@bluemarblepayroll.com']
14
+ s.authors = ['Matthew Ruggio', 'John Bosko']
15
+ s.email = ['mruggio@bluemarblepayroll.com', 'jbosko@bluemarblepayroll.com']
16
16
  s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
17
  s.bindir = 'exe'
18
18
  s.executables = %w[]
@@ -51,7 +51,7 @@ Gem::Specification.new do |s|
51
51
  s.add_development_dependency('pry', '~>0')
52
52
  s.add_development_dependency('rake', '~> 13')
53
53
  s.add_development_dependency('rspec', '~> 3.8')
54
- s.add_development_dependency('rubocop', '~>0.90.0')
54
+ s.add_development_dependency('rubocop', '~>1.7.0')
55
55
  s.add_development_dependency('simplecov', '~>0.18.5')
56
56
  s.add_development_dependency('simplecov-console', '~>0.7.0')
57
57
  s.add_development_dependency('sqlite3', '~>1')
@@ -17,4 +17,7 @@ require 'objectable'
17
17
  # General purpose classes used by the main job classes.
18
18
  require_relative 'db_fuel/modeling'
19
19
 
20
+ # Internal logic used across jobs.
21
+ require_relative 'db_fuel/db_provider'
22
+
20
23
  require_relative 'db_fuel/library'
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (c) 2020-present, Blue Marble Payroll, LLC
5
+ #
6
+ # This source code is licensed under the MIT license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+ #
9
+
10
+ module DbFuel
11
+ # Intermediate internal API for Arel/ActiveRecord. There is some overlap in job needs when
12
+ # it comes to the Arel interface so this class condenses down those needs into this class.
13
+ class DbProvider # :nodoc: all
14
+ attr_reader :arel_table
15
+
16
+ def initialize(table_name)
17
+ raise ArgumentError, 'table_name is required' if table_name.to_s.empty?
18
+
19
+ @arel_table = ::Arel::Table.new(table_name.to_s)
20
+
21
+ freeze
22
+ end
23
+
24
+ def first(object)
25
+ sql = first_sql(object)
26
+
27
+ ::ActiveRecord::Base.connection.exec_query(sql).first
28
+ end
29
+
30
+ def first_sql(object)
31
+ relation = arel_table.project(Arel.star).take(1)
32
+ manager = apply_where(object, relation)
33
+
34
+ manager.to_sql
35
+ end
36
+
37
+ def insert_sql(object)
38
+ insert_manager(object).to_sql
39
+ end
40
+
41
+ def insert(object)
42
+ manager = insert_manager(object)
43
+
44
+ ::ActiveRecord::Base.connection.insert(manager)
45
+ end
46
+
47
+ def update(set_object, where_object)
48
+ manager = update_manager(set_object, where_object)
49
+
50
+ ::ActiveRecord::Base.connection.update(manager)
51
+ end
52
+
53
+ def update_sql(set_object, where_object)
54
+ update_manager(set_object, where_object).to_sql
55
+ end
56
+
57
+ private
58
+
59
+ def update_manager(set_object, where_object)
60
+ arel_row = make_arel_row(set_object)
61
+ update_manager = ::Arel::UpdateManager.new.set(arel_row).table(arel_table)
62
+
63
+ apply_where(where_object, update_manager)
64
+ end
65
+
66
+ def apply_where(hash, manager)
67
+ (hash || {}).inject(manager) do |memo, (key, value)|
68
+ memo.where(arel_table[key].eq(value))
69
+ end
70
+ end
71
+
72
+ def insert_manager(object)
73
+ arel_row = make_arel_row(object)
74
+
75
+ ::Arel::InsertManager.new.insert(arel_row)
76
+ end
77
+
78
+ def make_arel_row(row)
79
+ row.map { |key, value| [arel_table[key], value] }
80
+ end
81
+ end
82
+ end
@@ -7,8 +7,11 @@
7
7
  # LICENSE file in the root directory of this source tree.
8
8
  #
9
9
 
10
+ require_relative 'library/active_record/find_or_insert'
10
11
  require_relative 'library/active_record/insert'
11
12
  require_relative 'library/active_record/update'
13
+ require_relative 'library/active_record/update_all'
14
+ require_relative 'library/active_record/upsert'
12
15
 
13
16
  require_relative 'library/dbee/query'
14
17
  require_relative 'library/dbee/range'
@@ -16,10 +19,13 @@ require_relative 'library/dbee/range'
16
19
  module Burner
17
20
  # Open up Burner::Jobs and add registrations for this libraries jobs.
18
21
  class Jobs
19
- register 'db_fuel/active_record/insert', DbFuel::Library::ActiveRecord::Insert
20
- register 'db_fuel/active_record/update', DbFuel::Library::ActiveRecord::Update
22
+ register 'db_fuel/active_record/find_or_insert', DbFuel::Library::ActiveRecord::FindOrInsert
23
+ register 'db_fuel/active_record/insert', DbFuel::Library::ActiveRecord::Insert
24
+ register 'db_fuel/active_record/update', DbFuel::Library::ActiveRecord::Update
25
+ register 'db_fuel/active_record/update_all', DbFuel::Library::ActiveRecord::UpdateAll
26
+ register 'db_fuel/active_record/upsert', DbFuel::Library::ActiveRecord::Upsert
21
27
 
22
- register 'db_fuel/dbee/query', DbFuel::Library::Dbee::Query
23
- register 'db_fuel/dbee/range', DbFuel::Library::Dbee::Range
28
+ register 'db_fuel/dbee/query', DbFuel::Library::Dbee::Query
29
+ register 'db_fuel/dbee/range', DbFuel::Library::Dbee::Range
24
30
  end
25
31
  end
@@ -19,10 +19,11 @@ module DbFuel
19
19
  NOW_TYPE = 'r/value/now'
20
20
  UPDATED_AT = :updated_at
21
21
 
22
- attr_reader :arel_table,
23
- :attribute_renderers,
22
+ attr_reader :attribute_renderers,
23
+ :db_provider,
24
24
  :debug,
25
- :resolver
25
+ :resolver,
26
+ :attribute_renderers_set
26
27
 
27
28
  def initialize(
28
29
  name:,
@@ -34,45 +35,20 @@ module DbFuel
34
35
  )
35
36
  super(name: name, register: register)
36
37
 
37
- @arel_table = ::Arel::Table.new(table_name.to_s)
38
- @debug = debug || false
39
-
40
- # set resolver first since make_attribute_renderers needs it.
41
38
  @resolver = Objectable.resolver(separator: separator)
42
-
43
- @attribute_renderers = Burner::Modeling::Attribute
44
- .array(attributes)
45
- .map { |a| Burner::Modeling::AttributeRenderer.new(a, resolver) }
39
+ @attribute_renderers_set = Modeling::AttributeRendererSet.new(attributes: attributes,
40
+ resolver: resolver)
41
+ @db_provider = DbProvider.new(table_name)
42
+ @debug = debug || false
46
43
  end
47
44
 
48
- private
49
-
50
- def timestamp_attribute(key)
51
- Burner::Modeling::Attribute.make(
52
- key: key,
53
- transformers: [
54
- { type: NOW_TYPE }
55
- ]
56
- )
57
- end
45
+ protected
58
46
 
59
47
  def debug_detail(output, message)
60
48
  return unless debug
61
49
 
62
50
  output.detail(message)
63
51
  end
64
-
65
- def make_arel_row(row)
66
- row.map { |key, value| [arel_table[key], value] }
67
- end
68
-
69
- def transform(row, time)
70
- attribute_renderers.each_with_object({}) do |attribute_renderer, memo|
71
- value = attribute_renderer.transform(row, time)
72
-
73
- resolver.set(memo, attribute_renderer.key, value)
74
- end
75
- end
76
52
  end
77
53
  end
78
54
  end