vorpal 1.0.1 → 1.2.1

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.
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",