db_fuel 1.0.0 → 1.1.0.pre.alpha

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: 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: