vorpal 1.0.1 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +72 -56
  3. data/lib/vorpal/aggregate_mapper.rb +11 -0
  4. data/lib/vorpal/config/class_config.rb +71 -0
  5. data/lib/vorpal/configs.rb +9 -66
  6. data/lib/vorpal/driver/postgresql.rb +39 -3
  7. data/lib/vorpal/dsl/config_builder.rb +12 -50
  8. data/lib/vorpal/dsl/configuration.rb +131 -42
  9. data/lib/vorpal/dsl/defaults_generator.rb +14 -4
  10. data/lib/vorpal/engine.rb +18 -5
  11. data/lib/vorpal/identity_map.rb +15 -14
  12. data/lib/vorpal/version.rb +1 -1
  13. data/vorpal.gemspec +9 -9
  14. metadata +36 -88
  15. data/.editorconfig +0 -13
  16. data/.envrc +0 -4
  17. data/.gitignore +0 -16
  18. data/.rspec +0 -1
  19. data/.yardopts +0 -1
  20. data/Appraisals +0 -34
  21. data/Gemfile +0 -4
  22. data/Rakefile +0 -10
  23. data/bin/appraisal +0 -29
  24. data/bin/rake +0 -29
  25. data/bin/rspec +0 -29
  26. data/gemfiles/rails_4_1.gemfile +0 -11
  27. data/gemfiles/rails_4_1.gemfile.lock +0 -92
  28. data/gemfiles/rails_4_2.gemfile +0 -11
  29. data/gemfiles/rails_4_2.gemfile.lock +0 -90
  30. data/gemfiles/rails_5_0.gemfile +0 -11
  31. data/gemfiles/rails_5_0.gemfile.lock +0 -88
  32. data/gemfiles/rails_5_1.gemfile +0 -11
  33. data/gemfiles/rails_5_1.gemfile.lock +0 -88
  34. data/gemfiles/rails_5_2.gemfile +0 -11
  35. data/gemfiles/rails_5_2.gemfile.lock +0 -88
  36. data/spec/helpers/db_helpers.rb +0 -66
  37. data/spec/helpers/profile_helpers.rb +0 -26
  38. data/spec/integration_spec_helper.rb +0 -36
  39. data/spec/unit_spec_helper.rb +0 -0
  40. data/spec/vorpal/acceptance/aggregate_mapper_spec.rb +0 -911
  41. data/spec/vorpal/integration/driver/postgresql_spec.rb +0 -42
  42. data/spec/vorpal/performance/performance_spec.rb +0 -235
  43. data/spec/vorpal/unit/configs_spec.rb +0 -117
  44. data/spec/vorpal/unit/db_loader_spec.rb +0 -103
  45. data/spec/vorpal/unit/dsl/config_builder_spec.rb +0 -18
  46. data/spec/vorpal/unit/dsl/defaults_generator_spec.rb +0 -75
  47. data/spec/vorpal/unit/identity_map_spec.rb +0 -62
  48. data/spec/vorpal/unit/loaded_objects_spec.rb +0 -22
  49. data/spec/vorpal/unit/util/string_utils_spec.rb +0 -25
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d2bd60707eb3762666ed0ada9c456b5d54fbc73ca5d56eda9febd1c540ffc754
4
- data.tar.gz: a17bf33069fe5d44d2a9577da2d7e5457a18ab0703b7dfe14d67fc22f6f7d76c
3
+ metadata.gz: 83737ac0a684cae35b5e849cba0c7851234dd83644ec04458e2c3c49ae0213f1
4
+ data.tar.gz: 8ac93c1267e080bc874dc2526d7b9f2c5abc1635c579d0f4be7cbd9af4fd7fc9
5
5
  SHA512:
6
- metadata.gz: 8a9b2b133c7db7523153b290d3422e84db785856a12d37d863608f017366dd0f06ddad3e4f9b261e28a6c7cd66a72dc54c52b05425715146165d7573118b84bc
7
- data.tar.gz: da8970a533f9fce8e3cd3b3bcdd2464abd2b6c8fa0cb06fd8ced941d6c61115cea3df3523679a01722dcd7e641503628b76140bd553b0e167623021bd7e937fe
6
+ metadata.gz: 959ef178a6479c47053c2d100c323a48134fdcf7c63479c21afff0cc7cb91f2a198962013e5b324c7a8a4b1946e19332b15198c957c3e1b097e7734036b2ab09
7
+ data.tar.gz: 2783c22ba9362a9285e4e80369565a8be0695feb910325f77233b43060826dfffafd03e74c2c4cdcb554e6246eba1e42441b7b2f94d7ba0748cda140effee044
data/README.md CHANGED
@@ -1,16 +1,13 @@
1
- # Vorpal [![Build Status](https://travis-ci.org/nulogy/vorpal.svg?branch=master)](https://travis-ci.org/nulogy/vorpal) [![Code Climate](https://codeclimate.com/github/nulogy/vorpal/badges/gpa.svg)](https://codeclimate.com/github/nulogy/vorpal)
1
+ # Vorpal [![Build Status](https://travis-ci.com/nulogy/vorpal.svg?branch=main)](https://travis-ci.com/nulogy/vorpal) [![Code Climate](https://codeclimate.com/github/nulogy/vorpal/badges/gpa.svg)](https://codeclimate.com/github/nulogy/vorpal) [![Code Coverage](https://codecov.io/gh/nulogy/vorpal/branch/main/graph/badge.svg)](https://codecov.io/gh/nulogy/vorpal/branch/main)
2
2
 
3
3
  Separate your domain model from your persistence mechanism. Some problems call for a really sharp tool.
4
4
 
5
-
6
- > One, two! One, two! and through and through
7
-
8
- > The vorpal blade went snicker-snack!
9
-
10
- > He left it dead, and with its head
11
-
5
+ > One, two! One, two! and through and through<br/>
6
+ > The vorpal blade went snicker-snack!<br/>
7
+ > He left it dead, and with its head<br/>
12
8
  > He went galumphing back.
13
9
 
10
+ \- [Jabberwocky](https://www.poetryfoundation.org/poems/42916/jabberwocky) by Lewis Carroll
14
11
 
15
12
  ## Overview
16
13
  Vorpal is a [Data Mapper](http://martinfowler.com/eaaCatalog/dataMapper.html)-style ORM (object relational mapper) framelet that persists POROs (plain old Ruby objects) to a relational DB. It has been heavily influenced by concepts from [Domain Driven Design](http://www.infoq.com/minibooks/domain-driven-design-quickly).
@@ -48,27 +45,21 @@ Or install it yourself as:
48
45
  Start with a domain model of POROs and AR::Base objects that form an aggregate:
49
46
 
50
47
  ```ruby
51
- class Tree; end
52
-
53
48
  class Branch
54
- include Virtus.model
55
-
56
- attribute :id, Integer
57
- attribute :length, Decimal
58
- attribute :diameter, Decimal
59
- attribute :tree, Tree
49
+ attr_accessor :id
50
+ attr_accessor :length
51
+ attr_accessor :diameter
52
+ attr_accessor :tree
60
53
  end
61
54
 
62
- class Gardener < ActiveRecord::Base
55
+ class Gardener
63
56
  end
64
57
 
65
58
  class Tree
66
- include Virtus.model
67
-
68
- attribute :id, Integer
69
- attribute :name, String
70
- attribute :gardener, Gardener
71
- attribute :branches, Array[Branch]
59
+ attr_accessor :id
60
+ attr_accessor :name
61
+ attr_accessor :gardener
62
+ attr_accessor :branches
72
63
  end
73
64
  ```
74
65
 
@@ -143,7 +134,8 @@ module TreeRepository
143
134
  end
144
135
  ```
145
136
 
146
- Here we've used the `owned` flag on the `belongs_to` from the Tree to the Gardener to show that the Gardener is on the aggregate boundary.
137
+ Here we've used the `owned: false` flag on the `belongs_to` from the Tree to the Gardener to show
138
+ that the Gardener is on the aggregate boundary.
147
139
 
148
140
  And use it:
149
141
 
@@ -164,9 +156,33 @@ TreeRepository.destroy(dead_tree)
164
156
  TreeRepository.destroy_by_id(dead_tree_id)
165
157
  ```
166
158
 
159
+ ### Ids
160
+
161
+ Vorpal by default will use auto-incrementing Integers from a DB sequence for ids. However, UUID v4 ids are also
162
+ supported:
163
+
164
+ ```ruby
165
+ Vorpal.define do
166
+ # UUID v4 id!
167
+ map Tree, primary_key_type: :uuid do
168
+ # ..
169
+ end
170
+
171
+ # Also a UUID v4 id, the Rails Way!
172
+ map Trunk, id: :uuid do
173
+ # ..
174
+ end
175
+
176
+ # If you feel the need to specify an auto-incrementing integer id.
177
+ map Branch, primary_key_type: :serial do
178
+ # ..
179
+ end
180
+ end
181
+ ```
182
+
167
183
  ## API Documentation
168
184
 
169
- http://rubydoc.info/github/nulogy/vorpal/master/frames
185
+ http://rubydoc.info/github/nulogy/vorpal/main/frames
170
186
 
171
187
  ## Caveats
172
188
  It also does not do some things that you might expect from other ORMs:
@@ -174,7 +190,7 @@ It also does not do some things that you might expect from other ORMs:
174
190
  1. No lazy loading of associations. This might sound like a big deal, but with [correctly designed aggregates](http://dddcommunity.org/library/vernon_2011/) it turns out not to be.
175
191
  1. No managing of transactions. It is the strong opinion of the authors that managing transactions is an application-level concern.
176
192
  1. No support for validations. Validations are not a persistence concern.
177
- 1. No AR-style callbacks. Use Infrastructure, Application, or Domain [services](http://martinfowler.com/bliki/EvansClassification.html) instead.
193
+ 1. No AR-style callbacks. Use [Infrastructure, Application, or Domain services](http://martinfowler.com/bliki/EvansClassification.html) instead.
178
194
  1. No has-many-through associations. Use two has-many associations to a join entity instead.
179
195
  1. The `id` attribute is reserved for database primary keys. If you have a natural key/id on your domain model, name it something that makes sense for your domain. It is the strong opinion of the authors that using natural keys as foreign keys is a bad idea. This mixes domain and persistence concerns.
180
196
 
@@ -183,13 +199,10 @@ It also does not do some things that you might expect from other ORMs:
183
199
  1. Only supports PostgreSQL.
184
200
 
185
201
  ## Future Enhancements
186
- * Aggregate updated_at.
187
- * Support for other DBMSs (no MySQL support until ids can be generated without inserting into a table!)
188
- * Support for other ORMs.
189
- * Value objects.
190
- * Remove dependency on ActiveRecord (optimistic locking? updated_at, created_at support? Data type conversions? TimeZone support?)
191
- * More efficient updates (use fewer queries.)
202
+ * Support for clients to set UUID-based ids.
192
203
  * Nicer DSL for specifying attributes that have different names in the domain model than in the DB.
204
+ * Aggregate updated_at.
205
+ * Better support for value objects.
193
206
 
194
207
  ## FAQ
195
208
 
@@ -205,11 +218,12 @@ It also does not do some things that you might expect from other ORMs:
205
218
 
206
219
  **A.** Create a method on a [Repository](http://martinfowler.com/eaaCatalog/repository.html)! They have full access to the DB/ORM so you can use [Arel](https://github.com/rails/arel) and go [crazy](http://asciicasts.com/episodes/239-activerecord-relation-walkthrough) or use direct SQL if you want.
207
220
 
208
- For example:
221
+ For example, use the [#query](https://rubydoc.info/github/nulogy/vorpal/main/Vorpal/AggregateMapper#query-instance_method) method on the [AggregateMapper](https://rubydoc.info/github/nulogy/vorpal/main/Vorpal/AggregateMapper) to access the underyling [ActiveRecordRelation](https://api.rubyonrails.org/classes/ActiveRecord/Relation.html):
209
222
 
210
223
  ```ruby
211
- def find_all
212
- @mapper.query.load_all # use the mapper to load all the aggregates
224
+ def find_special_ones
225
+ # use `load_all` or `load_one` to convert from ActiveRecord objects to domain POROs.
226
+ @mapper.query.where(special: true).load_all
213
227
  end
214
228
  ```
215
229
 
@@ -231,6 +245,10 @@ For example:
231
245
 
232
246
  **A.** You can use [ActiveModel::Serialization](http://api.rubyonrails.org/classes/ActiveModel/Serialization.html) or [ActiveModel::Serializers](https://github.com/rails-api/active_model_serializers) but they are not heartily recommended. The former is too coupled to the model and the latter is too coupled to Rails controllers. Vorpal uses [SimpleSerializer](https://github.com/nulogy/simple_serializer) for this purpose.
233
247
 
248
+ **Q.** Are `updated_at` and `created_at` supported?
249
+
250
+ **A.** Yes. If they exist on your database tables, they will behave exactly as if you were using vanilla ActiveRecord.
251
+
234
252
  ## Contributing
235
253
 
236
254
  1. Fork it ( https://github.com/nulogy/vorpal/fork )
@@ -239,33 +257,31 @@ For example:
239
257
  4. Push to the branch (`git push origin my-new-feature`)
240
258
  5. Create a new Pull Request
241
259
 
242
- ### Setup DirEnv
243
-
244
- Using this gem's bin stubs (contained in the `bin` dir) is much easier if [DirEnv](https://github.com/direnv/direnv) is installed.
245
-
246
- On OSX using ZSH DirEnv can be installed like so:
247
-
248
- 1. `brew install direnv`
249
- 2. `echo 'eval "$(direnv hook zsh)"' >> ~/.zshrc`
260
+ ## OSX Environment setup
250
261
 
251
- Please see the [DirEnv docs](https://direnv.net/) if your environment is different.
262
+ 1. Install [Homebrew](https://brew.sh/)
263
+ 2. Install [rbenv](https://github.com/rbenv/rbenv#installation) ([RVM](https://rvm.io/) can work too)
264
+ 3. Install [DirEnv](https://direnv.net/docs/installation.html) (`brew install direnv`)
265
+ 4. Install Docker Desktop Community Edition (`brew cask install docker`)
266
+ 5. Start Docker Desktop Community Edition (`CMD+space docker ENTER`)
267
+ 6. Install Ruby (`rbenv install 2.7.0`)
268
+ 7. Install PostgreSQL (`brew install postgresql`)
269
+ 8. Clone the repo (`git clone git@github.com:nulogy/vorpal.git`) and `cd` to the project root.
270
+ 8. Copy the contents of `gemfiles/rails_<version>.gemfile.lock` into a `Gemfile.lock` file
271
+ at the root of the project. (`cp gemfiles/rails_6_0.gemfile.lock gemfile.lock`)
272
+ 9. `bundle`
252
273
 
253
274
  ### Running Tests
254
275
 
255
- 1. Start a PostgreSQL server.
256
- 2. Either:
257
- * Create a DB user called `vorpal` with password `pass`. OR:
258
- * Modify `spec/helpers/db_helpers.rb`.
259
- 3. Run `rake` from the terminal.
276
+ 1. Start a PostgreSQL server using `docker-compose up`
277
+ 3. Run `rake` from the terminal to run all specs or `rspec <path to spec file>` to
278
+ run a single spec.
260
279
 
261
- ### Running Tests for the non-default versions of Rails
280
+ ### Running Tests for a specific version of Rails
262
281
 
263
- 1. Start a PostgreSQL server.
264
- 2. Either:
265
- * Create a DB user called `vorpal` with password `pass`. OR:
266
- * Modify `spec/helpers/db_helpers.rb`.
267
- 3. Run `appraisal <rails version> rake` from the terminal.
268
- * Where `<rails version>` is one of the options defined in the `./Appraisal` file.
282
+ 1. Start a PostgreSQL server using `docker-compose up`
283
+ 2. Run `appraisal rails-5-2 rake` from the terminal to run all specs or
284
+ `appraisal rails-5-2 rspec <path to spec file>` to run a single spec.
269
285
 
270
286
  Please see the [Appraisal gem docs](https://github.com/thoughtbot/appraisal) for more information.
271
287
 
@@ -84,6 +84,17 @@ module Vorpal
84
84
  @engine
85
85
  end
86
86
 
87
+ # Returns a 'Vorpal-aware' [ActiveRecord::Relation](https://api.rubyonrails.org/classes/ActiveRecord/Relation.html)
88
+ # for the ActiveRecord object underlying the domain entity mapped by this mapper.
89
+ #
90
+ # This method allows you to easily access the power of ActiveRecord::Relation to do more complex
91
+ # queries in your repositories.
92
+ #
93
+ # The ActiveRecord::Relation is 'Vorpal-aware' because it has the {#load_one} and {#load_many} methods
94
+ # mixed in so that you can get the POROs from your domain model instead of the ActiveRecord
95
+ # objects normally returned by ActiveRecord::Relation.
96
+ #
97
+ # @return [ActiveRecord::Relation]
87
98
  def query
88
99
  @engine.query(@domain_class)
89
100
  end
@@ -0,0 +1,71 @@
1
+ require 'equalizer'
2
+
3
+ module Vorpal
4
+ module Config
5
+ # @private
6
+ class ClassConfig
7
+ include Equalizer.new(:domain_class, :db_class)
8
+ attr_reader :serializer, :deserializer, :domain_class, :db_class, :primary_key_type, :local_association_configs
9
+ attr_accessor :has_manys, :belongs_tos, :has_ones
10
+
11
+ ALLOWED_PRIMARY_KEY_TYPE_OPTIONS = [:serial, :uuid]
12
+
13
+ def initialize(attrs)
14
+ @has_manys = []
15
+ @belongs_tos = []
16
+ @has_ones = []
17
+ @local_association_configs = []
18
+
19
+ @serializer = attrs[:serializer]
20
+ @deserializer = attrs[:deserializer]
21
+ @domain_class = attrs[:domain_class]
22
+ @db_class = attrs[:db_class]
23
+ @primary_key_type = attrs[:primary_key_type]
24
+ raise "Invalid primary_key_type: '#{@primary_key_type}'" unless ALLOWED_PRIMARY_KEY_TYPE_OPTIONS.include?(@primary_key_type)
25
+ end
26
+
27
+ def build_db_object(attributes)
28
+ db_class.new(attributes)
29
+ end
30
+
31
+ def set_db_object_attributes(db_object, attributes)
32
+ db_object.attributes = attributes
33
+ end
34
+
35
+ def get_db_object_attributes(db_object)
36
+ symbolize_keys(db_object.attributes)
37
+ end
38
+
39
+ def serialization_required?
40
+ domain_class.superclass.name != 'ActiveRecord::Base'
41
+ end
42
+
43
+ def serialize(object)
44
+ serializer.serialize(object)
45
+ end
46
+
47
+ def deserialize(db_object)
48
+ attributes = get_db_object_attributes(db_object)
49
+ serialization_required? ? deserializer.deserialize(domain_class.new, attributes) : db_object
50
+ end
51
+
52
+ def set_attribute(db_object, attribute, value)
53
+ db_object.send("#{attribute}=", value)
54
+ end
55
+
56
+ def get_attribute(db_object, attribute)
57
+ db_object.send(attribute)
58
+ end
59
+
60
+ private
61
+
62
+ def symbolize_keys(hash)
63
+ result = {}
64
+ hash.each_key do |key|
65
+ result[key.to_sym] = hash[key]
66
+ end
67
+ result
68
+ end
69
+ end
70
+ end
71
+ end
@@ -1,13 +1,13 @@
1
1
  require 'vorpal/util/hash_initialization'
2
2
  require 'vorpal/exceptions'
3
+ require 'vorpal/config/class_config'
3
4
  require 'equalizer'
4
5
 
5
6
  module Vorpal
6
7
  # @private
7
- class MasterConfig
8
- def initialize(class_configs)
9
- @class_configs = class_configs
10
- initialize_association_configs
8
+ class MainConfig
9
+ def initialize
10
+ @class_configs = []
11
11
  end
12
12
 
13
13
  def config_for(clazz)
@@ -20,7 +20,9 @@ module Vorpal
20
20
  @class_configs.detect { |conf| conf.db_class == db_object.class }
21
21
  end
22
22
 
23
- private
23
+ def add_class_config(class_config)
24
+ @class_configs << class_config
25
+ end
24
26
 
25
27
  def initialize_association_configs
26
28
  association_configs = {}
@@ -50,6 +52,8 @@ module Vorpal
50
52
  end
51
53
  end
52
54
 
55
+ private
56
+
53
57
  def build_association_config(association_configs, local_config, fk, fk_type)
54
58
  association_config = AssociationConfig.new(local_config, fk, fk_type)
55
59
  if association_configs[association_config]
@@ -126,67 +130,6 @@ module Vorpal
126
130
  end
127
131
  end
128
132
 
129
- # @private
130
- class ClassConfig
131
- include Equalizer.new(:domain_class, :db_class)
132
- attr_reader :serializer, :deserializer, :domain_class, :db_class, :local_association_configs
133
- attr_accessor :has_manys, :belongs_tos, :has_ones
134
-
135
- def initialize(attrs)
136
- @has_manys = []
137
- @belongs_tos = []
138
- @has_ones = []
139
- @local_association_configs = []
140
-
141
- attrs.each do |k,v|
142
- instance_variable_set("@#{k}", v)
143
- end
144
- end
145
-
146
- def build_db_object(attributes)
147
- db_class.new(attributes)
148
- end
149
-
150
- def set_db_object_attributes(db_object, attributes)
151
- db_object.attributes = attributes
152
- end
153
-
154
- def get_db_object_attributes(db_object)
155
- symbolize_keys(db_object.attributes)
156
- end
157
-
158
- def serialization_required?
159
- domain_class.superclass.name != 'ActiveRecord::Base'
160
- end
161
-
162
- def serialize(object)
163
- serializer.serialize(object)
164
- end
165
-
166
- def deserialize(db_object)
167
- attributes = get_db_object_attributes(db_object)
168
- serialization_required? ? deserializer.deserialize(domain_class.new, attributes) : db_object
169
- end
170
-
171
- def set_attribute(db_object, attribute, value)
172
- db_object.send("#{attribute}=", value)
173
- end
174
-
175
- def get_attribute(db_object, attribute)
176
- db_object.send(attribute)
177
- end
178
-
179
- private
180
-
181
- def symbolize_keys(hash)
182
- result = {}
183
- hash.each_key do |key|
184
- result[key.to_sym] = hash[key]
185
- end
186
- result
187
- end
188
- end
189
-
190
133
  # @private
191
134
  class ForeignKeyInfo
192
135
  include Equalizer.new(:fk_column, :fk_type_column, :fk_type)
@@ -9,7 +9,12 @@ module Vorpal
9
9
  end
10
10
 
11
11
  def insert(db_class, db_objects)
12
- if defined? ActiveRecord::Import
12
+ if ActiveRecord::VERSION::MAJOR >= 6
13
+ return if db_objects.empty?
14
+
15
+ update_timestamps_on_create(db_class, db_objects)
16
+ db_class.insert_all!(db_objects.map(&:attributes))
17
+ elsif defined? ActiveRecord::Import
13
18
  db_class.import(db_objects, validate: false)
14
19
  else
15
20
  db_objects.each do |db_object|
@@ -19,8 +24,15 @@ module Vorpal
19
24
  end
20
25
 
21
26
  def update(db_class, db_objects)
22
- db_objects.each do |db_object|
23
- db_object.save!(validate: false)
27
+ if ActiveRecord::VERSION::MAJOR >= 6
28
+ return if db_objects.empty?
29
+
30
+ update_timestamps_on_update(db_class, db_objects)
31
+ db_class.upsert_all(db_objects.map(&:attributes))
32
+ else
33
+ db_objects.each do |db_object|
34
+ db_object.save!(validate: false)
35
+ end
24
36
  end
25
37
  end
26
38
 
@@ -100,6 +112,30 @@ module Vorpal
100
112
 
101
113
  private
102
114
 
115
+ # Adapted from https://github.com/rails/rails/blob/614580270d7789e5275defc3da020ce27b3b2302/activerecord/lib/active_record/timestamp.rb#L99
116
+ def update_timestamps_on_create(db_class, db_objects)
117
+ return unless db_class.record_timestamps
118
+
119
+ current_time = db_class.current_time_from_proper_timezone
120
+ db_objects.each do |db_object|
121
+ db_class.all_timestamp_attributes_in_model.each do |column|
122
+ db_object.write_attribute(column, current_time) unless db_object.read_attribute(column)
123
+ end
124
+ end
125
+ end
126
+
127
+ #Adapted from https://github.com/rails/rails/blob/614580270d7789e5275defc3da020ce27b3b2302/activerecord/lib/active_record/timestamp.rb#L111
128
+ def update_timestamps_on_update(db_class, db_objects)
129
+ return unless db_class.record_timestamps
130
+
131
+ current_time = db_class.current_time_from_proper_timezone
132
+ db_objects.each do |db_object|
133
+ db_class.timestamp_attributes_for_update_in_model.each do |column|
134
+ db_object.write_attribute(column, current_time)
135
+ end
136
+ end
137
+ end
138
+
103
139
  def sequence_name(db_class)
104
140
  @sequence_names[db_class] ||= execute(
105
141
  "SELECT substring(column_default from '''(.*)''') FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = $1 AND column_name = 'id' LIMIT 1",