db_fuel 1.0.0 → 1.1.0.pre.alpha

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: 181a57c237b43052c5aa968663e4d3d6d8e6141a3affaf5b0de1ad4147e0725c
4
- data.tar.gz: 817df3889c7c39f228cc2a860e19cd1cefb97f9ba23697972e04aa163413b359
3
+ metadata.gz: fb563756df642d23e7d268cf0e7b35735300ddfc84da31e0ee11d7e138d41584
4
+ data.tar.gz: f5720ed0f4649ba82d0c233cf9ccbc4aff5d54d8d9c4267c830467f43f85cdf3
5
5
  SHA512:
6
- metadata.gz: d8bdbd205ee2c03f2954057f199c58e7d94d2ee64ebcc3735bb4a380dea175be7eb0ed9ac05e1dff12f985f8831e2bb15d76227d61bea0c4e3091b4185cb47d8
7
- data.tar.gz: f03df133b8563d7e035654ccaab7c4789a818683dd68a4bf5471d4d7261ea53fcddf989ab5c5e91cbfd3bbc57cb1ed75ab86ec355496571aeb5b816ff4a19f35
6
+ metadata.gz: 16c63b0939d36f32cfc72baeefc20fbb88e7285d5c5e8266d337f55362909a6f751f7f2b1c535f6c231a11adba2693807c68e6af1b4d5eaaf4b14a9213589d01
7
+ data.tar.gz: a5143b01108dc065d98a5e085da06c8e82a5f0c084a0097128219e8c49542818a8ce754ef1af79be23d6493f259f745657bba4e74051cf856264fbe87a76dfde
@@ -1,3 +1,10 @@
1
+ # 1.1.0 (TBD)
2
+
3
+ New Jobs:
4
+
5
+ * db_fuel/active_record/insert
6
+ * db_fuel/active_record/update
7
+
1
8
  # 1.0.0 (November 18th, 2020)
2
9
 
3
10
  Initial implementation. Includes jobs:
data/README.md CHANGED
@@ -22,11 +22,39 @@ bundle add db_fuel
22
22
 
23
23
  Refer to the [Burner](https://github.com/bluemarblepayroll/burner) library for more specific information on how Burner works. This section will just focus on what this library directly adds.
24
24
 
25
+ ### ActiveRecord Jobs
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.
29
+
30
+ ### Dbee Jobs
31
+
25
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.
26
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.
27
34
 
28
35
  ## Examples
29
36
 
37
+ In all the examples we will assume we have the following schema:
38
+
39
+ ````ruby
40
+ ActiveRecord::Schema.define do
41
+ create_table :statuses do |t|
42
+ t.string :code, null: false, limit: 25
43
+ t.integer :priority, null: false, default: 0
44
+ t.timestamps
45
+ end
46
+
47
+ create_table :patients do |t|
48
+ t.string :chart_number
49
+ t.string :first_name
50
+ t.string :middle_name
51
+ t.string :last_name
52
+ t.references :status
53
+ t.timestamps
54
+ end
55
+ end
56
+ ````
57
+
30
58
  ### Querying the Database
31
59
 
32
60
  The `db_fuel/dbee/query` job can be utilized to process a SQL query and store the results in a Burner::Payload register.
@@ -53,8 +81,7 @@ pipeline = {
53
81
  },
54
82
  register: :patients
55
83
  }
56
- ],
57
- steps: %w[retrieve_patients]
84
+ ]
58
85
  }
59
86
 
60
87
  payload = Burner::Payload.new
@@ -105,8 +132,7 @@ pipeline = {
105
132
  key: :fname,
106
133
  key_path: :first_name
107
134
  }
108
- ],
109
- steps: %w[load_first_names retrieve_patients]
135
+ ]
110
136
  }
111
137
 
112
138
  payload = Burner::Payload.new
@@ -122,7 +148,95 @@ payload['patients'] # array in form of: [ { "id" => 1, "first_name" => "Somethin
122
148
 
123
149
  The only difference between the query and range jobs should be the latter is limited based on the incoming first names.
124
150
 
151
+ ### Updating the Database
152
+
153
+ #### Inserting Records
154
+
155
+ We can deal with persistence using the db_fuel/active_record/* jobs. In order to insert new records we can use the `db_fuel/active_record/insert` job. For example:
156
+
157
+ ````ruby
158
+ pipeline = {
159
+ jobs: [
160
+ {
161
+ name: :load_patients,
162
+ type: 'b/value/static',
163
+ register: :patients,
164
+ value: [
165
+ { chart_number: 'B0001', first_name: 'Bugs', last_name: 'Bunny' },
166
+ { chart_number: 'B0002', first_name: 'Babs', last_name: 'Bunny' }
167
+ ]
168
+ },
169
+ {
170
+ name: 'insert_patients',
171
+ type: 'db_fuel/active_record/insert',
172
+ register: :patients,
173
+ attributes: [
174
+ { key: :chart_number },
175
+ { key: :first_name },
176
+ { key: :last_name }
177
+ ],
178
+ table_name: 'patients',
179
+ primary_key: {
180
+ key: :id
181
+ }
182
+ }
183
+ ]
184
+ }
185
+
186
+ payload = Burner::Payload.new
187
+
188
+ Burner::Pipeline.make(pipeline).execute(payload: payload)
189
+ ````
190
+
191
+ There should now be two new patients, AB0 and AB1, present in the table `patients`.
192
+
193
+ Notes:
194
+
195
+ * Since we specified the `primary_key`, the records' `id` attributes should be set to their respective primary key values.
196
+ * Set `debug: true` to print out each INSERT statement in the output (not for production use.)
197
+
198
+ #### Updating Records
199
+
200
+ Let's say we now want to update those records' last names:
201
+
202
+ ````ruby
203
+ pipeline = {
204
+ jobs: [
205
+ {
206
+ name: :load_patients,
207
+ type: 'b/value/static',
208
+ register: :patients,
209
+ value: [
210
+ { chart_number: 'B0001', last_name: 'Fox' },
211
+ { chart_number: 'B0002', last_name: 'Smurf' }
212
+ ]
213
+ },
214
+ {
215
+ name: 'update_patients',
216
+ type: 'db_fuel/active_record/update',
217
+ register: :patients,
218
+ attributes: [
219
+ { key: :last_name }
220
+ ],
221
+ table_name: 'patients',
222
+ unique_keys: [
223
+ { key: :chart_number }
224
+ ]
225
+ }
226
+ ]
227
+ }
228
+
229
+ payload = Burner::Payload.new
230
+
231
+ Burner::Pipeline.make(pipeline).execute(payload: payload)
232
+ ````
233
+
234
+ Each database record should have been updated with their new respective last names.
235
+
236
+ Notes:
125
237
 
238
+ * The unique_keys translate to WHERE clauses.
239
+ * Set `debug: true` to print out each UPDATE statement in the output (not for production use.)
126
240
  ## Contributing
127
241
 
128
242
  ### Development Environment Configuration
@@ -42,7 +42,7 @@ Gem::Specification.new do |s|
42
42
 
43
43
  s.add_dependency('activerecord', activerecord_version)
44
44
  s.add_dependency('acts_as_hashable', '~>1.2')
45
- s.add_dependency('burner', '~>1.0')
45
+ s.add_dependency('burner', '~>1.2')
46
46
  s.add_dependency('dbee', '~>2.1')
47
47
  s.add_dependency('dbee-active_record', '~>2.1')
48
48
  s.add_dependency('objectable', '~>1.0')
@@ -14,4 +14,7 @@ require 'dbee'
14
14
  require 'dbee/providers/active_record_provider'
15
15
  require 'objectable'
16
16
 
17
+ # General purpose classes used by the main job classes.
18
+ require_relative 'db_fuel/modeling'
19
+
17
20
  require_relative 'db_fuel/library'
@@ -7,8 +7,19 @@
7
7
  # LICENSE file in the root directory of this source tree.
8
8
  #
9
9
 
10
+ require_relative 'library/active_record/insert'
11
+ require_relative 'library/active_record/update'
12
+
10
13
  require_relative 'library/dbee/query'
11
14
  require_relative 'library/dbee/range'
12
15
 
13
- Burner::Jobs.register('db_fuel/dbee/query', DbFuel::Library::Dbee::Query)
14
- Burner::Jobs.register('db_fuel/dbee/range', DbFuel::Library::Dbee::Range)
16
+ module Burner
17
+ # Open up Burner::Jobs and add registrations for this libraries jobs.
18
+ class Jobs
19
+ register 'db_fuel/active_record/insert', DbFuel::Library::ActiveRecord::Insert
20
+ register 'db_fuel/active_record/update', DbFuel::Library::ActiveRecord::Update
21
+
22
+ register 'db_fuel/dbee/query', DbFuel::Library::Dbee::Query
23
+ register 'db_fuel/dbee/range', DbFuel::Library::Dbee::Range
24
+ end
25
+ end
@@ -0,0 +1,79 @@
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
+ module Library
12
+ module ActiveRecord
13
+ # This job can take the objects in a register and insert them into a database table.
14
+ #
15
+ # Expected Payload[register] input: array of objects
16
+ # Payload[register] output: array of objects.
17
+ class Base < Burner::JobWithRegister
18
+ CREATED_AT = :created_at
19
+ NOW_TYPE = 'r/value/now'
20
+ UPDATED_AT = :updated_at
21
+
22
+ attr_reader :arel_table,
23
+ :attribute_renderers,
24
+ :debug,
25
+ :resolver
26
+
27
+ def initialize(
28
+ name:,
29
+ table_name:,
30
+ attributes: [],
31
+ debug: false,
32
+ register: Burner::DEFAULT_REGISTER,
33
+ separator: ''
34
+ )
35
+ super(name: name, register: register)
36
+
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
+ @resolver = Objectable.resolver(separator: separator)
42
+
43
+ @attribute_renderers = Burner::Modeling::Attribute
44
+ .array(attributes)
45
+ .map { |a| Burner::Modeling::AttributeRenderer.new(a, resolver) }
46
+ end
47
+
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
58
+
59
+ def debug_detail(output, message)
60
+ return unless debug
61
+
62
+ output.detail(message)
63
+ 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
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,103 @@
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
+ require_relative 'base'
11
+
12
+ module DbFuel
13
+ module Library
14
+ module ActiveRecord
15
+ # This job can take the objects in a register and insert them into a database table.
16
+ #
17
+ # Expected Payload[register] input: array of objects
18
+ # Payload[register] output: array of objects.
19
+ class Insert < Base
20
+ attr_reader :primary_key
21
+
22
+ # Arguments:
23
+ # name [required]: name of the job within the Burner::Pipeline.
24
+ #
25
+ # table_name [required]: name of the table to use for the INSERT statements.
26
+ #
27
+ # attributes: Used to specify which object properties to put into the
28
+ # SQL statement and also allows for one last custom transformation
29
+ # pipeline, in case the data calls for sql-specific transformers
30
+ # before insertion.
31
+ #
32
+ # debug: If debug is set to true (defaults to false) then the SQL statements and
33
+ # returned objects will be printed in the output. Only use this option while
34
+ # debugging issues as it will fill up the output with (potentially too much) data.
35
+ #
36
+ # primary_key: If primary_key is present then it will be used to set the object's
37
+ # property to the returned primary key from the INSERT statement.
38
+ #
39
+ # separator: Just like other jobs with a 'separator' option, if the objects require
40
+ # key-path notation or nested object support, you can set the separator
41
+ # to something non-blank (like a period for notation in the
42
+ # form of: name.first).
43
+ #
44
+ # timestamps: If timestamps is true (default behavior) then both created_at
45
+ # and updated_at columns will automatically have their values set
46
+ # to the current UTC timestamp.
47
+ def initialize(
48
+ name:,
49
+ table_name:,
50
+ attributes: [],
51
+ debug: false,
52
+ primary_key: nil,
53
+ register: Burner::DEFAULT_REGISTER,
54
+ separator: '',
55
+ timestamps: true
56
+ )
57
+ explicit_attributes = Burner::Modeling::Attribute.array(attributes)
58
+
59
+ attributes = timestamps ? timestamp_attributes + explicit_attributes : explicit_attributes
60
+
61
+ super(
62
+ name: name,
63
+ table_name: table_name,
64
+ attributes: attributes,
65
+ debug: debug,
66
+ register: register,
67
+ separator: separator
68
+ )
69
+
70
+ @primary_key = Modeling::KeyedColumn.make(primary_key, nullable: true)
71
+
72
+ freeze
73
+ end
74
+
75
+ def perform(output, payload)
76
+ payload[register] = array(payload[register])
77
+
78
+ payload[register].each do |row|
79
+ arel_row = make_arel_row(transform(row, payload.time))
80
+ insert_manager = ::Arel::InsertManager.new.insert(arel_row)
81
+
82
+ debug_detail(output, "Insert Statement: #{insert_manager.to_sql}")
83
+
84
+ id = ::ActiveRecord::Base.connection.insert(insert_manager)
85
+
86
+ resolver.set(row, primary_key.key, id) if primary_key
87
+
88
+ debug_detail(output, "Insert Return: #{row}")
89
+ end
90
+ end
91
+
92
+ private
93
+
94
+ def timestamp_attributes
95
+ [
96
+ timestamp_attribute(CREATED_AT),
97
+ timestamp_attribute(UPDATED_AT)
98
+ ]
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,124 @@
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
+ require_relative 'base'
11
+
12
+ module DbFuel
13
+ module Library
14
+ module ActiveRecord
15
+ # This job can take the objects in a register and updates them within database table.
16
+ # The attributes translate to SQL SET clauses and the unique_keys translate to
17
+ # WHERE clauses.
18
+ #
19
+ # Expected Payload[register] input: array of objects
20
+ # Payload[register] output: array of objects.
21
+ class Update < Base
22
+ attr_reader :unique_keys
23
+
24
+ # Arguments:
25
+ # name [required]: name of the job within the Burner::Pipeline.
26
+ #
27
+ # table_name [required]: name of the table to use for the INSERT statements.
28
+ #
29
+ # attributes: Used to specify which object properties to put into the
30
+ # SQL statement and also allows for one last custom transformation
31
+ # pipeline, in case the data calls for sql-specific transformers
32
+ # before mutation.
33
+ #
34
+ # debug: If debug is set to true (defaults to false) then the SQL statements and
35
+ # returned objects will be printed in the output. Only use this option while
36
+ # debugging issues as it will fill up the output with (potentially too much) data.
37
+ #
38
+ # separator: Just like other jobs with a 'separator' option, if the objects require
39
+ # key-path notation or nested object support, you can set the separator
40
+ # to something non-blank (like a period for notation in the
41
+ # form of: name.first).
42
+ #
43
+ # timestamps: If timestamps is true (default behavior) then the updated_at column will
44
+ # automatically have its value set to the current UTC timestamp.
45
+ #
46
+ # unique_keys: Each key will become a WHERE clause in order to only update specific
47
+ # records.
48
+ def initialize(
49
+ name:,
50
+ table_name:,
51
+ attributes: [],
52
+ debug: false,
53
+ register: Burner::DEFAULT_REGISTER,
54
+ separator: '',
55
+ timestamps: true,
56
+ unique_keys: []
57
+ )
58
+ explicit_attributes = Burner::Modeling::Attribute.array(attributes)
59
+
60
+ attributes = timestamps ? timestamp_attributes + explicit_attributes : explicit_attributes
61
+
62
+ super(
63
+ name: name,
64
+ table_name: table_name,
65
+ attributes: attributes,
66
+ debug: debug,
67
+ register: register,
68
+ separator: separator
69
+ )
70
+
71
+ @unique_keys = Modeling::KeyedColumn.array(unique_keys)
72
+
73
+ freeze
74
+ end
75
+
76
+ def perform(output, payload)
77
+ total_rows_affected = 0
78
+
79
+ payload[register] = array(payload[register])
80
+
81
+ payload[register].each do |row|
82
+ update_manager = make_update_manager(row, payload.time)
83
+
84
+ debug_detail(output, "Update Statement: #{update_manager.to_sql}")
85
+
86
+ rows_affected = ::ActiveRecord::Base.connection.update(update_manager)
87
+
88
+ debug_detail(output, "Individual Rows Affected: #{rows_affected}")
89
+
90
+ total_rows_affected += rows_affected
91
+ end
92
+
93
+ output.detail("Total Rows Affected: #{total_rows_affected}")
94
+ end
95
+
96
+ private
97
+
98
+ def make_update_manager(row, time)
99
+ arel_row = make_arel_row(transform(row, time))
100
+ unique_values = make_unique_column_values(row)
101
+ update_manager = ::Arel::UpdateManager.new.set(arel_row).table(arel_table)
102
+
103
+ apply_where(unique_values, update_manager)
104
+ end
105
+
106
+ def make_unique_column_values(row)
107
+ unique_keys.each_with_object({}) do |unique_key, memo|
108
+ memo[unique_key.column] = resolver.get(row, unique_key.key)
109
+ end
110
+ end
111
+
112
+ def apply_where(hash, manager)
113
+ (hash || {}).inject(manager) do |memo, (key, value)|
114
+ memo.where(arel_table[key].eq(value))
115
+ end
116
+ end
117
+
118
+ def timestamp_attributes
119
+ [timestamp_attribute(UPDATED_AT)]
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,10 @@
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
+ require_relative 'modeling/keyed_column'
@@ -0,0 +1,30 @@
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
+ module Modeling
12
+ # Connects a hash key to a sql column. By default if a column is not given then its
13
+ # key will be used for both. The general use case for this is for mapping objects
14
+ # to sql and sql to objects.
15
+ class KeyedColumn
16
+ acts_as_hashable
17
+
18
+ attr_reader :column, :key
19
+
20
+ def initialize(key:, column: '')
21
+ raise ArgumentError, 'key is required' if key.blank?
22
+
23
+ @column = column.present? ? column.to_s : key.to_s
24
+ @key = key.to_s
25
+
26
+ freeze
27
+ end
28
+ end
29
+ end
30
+ end
@@ -8,5 +8,5 @@
8
8
  #
9
9
 
10
10
  module DbFuel
11
- VERSION = '1.0.0'
11
+ VERSION = '1.1.0-alpha'
12
12
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: db_fuel
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0.pre.alpha
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthew Ruggio
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-11-18 00:00:00.000000000 Z
11
+ date: 2020-11-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -50,14 +50,14 @@ dependencies:
50
50
  requirements:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
- version: '1.0'
53
+ version: '1.2'
54
54
  type: :runtime
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - "~>"
59
59
  - !ruby/object:Gem::Version
60
- version: '1.0'
60
+ version: '1.2'
61
61
  - !ruby/object:Gem::Dependency
62
62
  name: dbee
63
63
  requirement: !ruby/object:Gem::Requirement
@@ -237,9 +237,14 @@ files:
237
237
  - exe/.gitkeep
238
238
  - lib/db_fuel.rb
239
239
  - lib/db_fuel/library.rb
240
+ - lib/db_fuel/library/active_record/base.rb
241
+ - lib/db_fuel/library/active_record/insert.rb
242
+ - lib/db_fuel/library/active_record/update.rb
240
243
  - lib/db_fuel/library/dbee/base.rb
241
244
  - lib/db_fuel/library/dbee/query.rb
242
245
  - lib/db_fuel/library/dbee/range.rb
246
+ - lib/db_fuel/modeling.rb
247
+ - lib/db_fuel/modeling/keyed_column.rb
243
248
  - lib/db_fuel/version.rb
244
249
  homepage: https://github.com/bluemarblepayroll/db_fuel
245
250
  licenses:
@@ -261,9 +266,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
261
266
  version: '2.5'
262
267
  required_rubygems_version: !ruby/object:Gem::Requirement
263
268
  requirements:
264
- - - ">="
269
+ - - ">"
265
270
  - !ruby/object:Gem::Version
266
- version: '0'
271
+ version: 1.3.1
267
272
  requirements: []
268
273
  rubygems_version: 3.0.3
269
274
  signing_key: