atomically 1.1.2 → 1.1.3

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,342 +1,342 @@
1
- # Atomically
2
-
3
- [![Gem Version](https://img.shields.io/gem/v/atomically.svg?style=flat)](https://rubygems.org/gems/atomically)
4
- [![Build Status](https://api.travis-ci.com/khiav223577/atomically.svg?branch=master)](https://travis-ci.com/khiav223577/atomically)
5
- [![RubyGems](http://img.shields.io/gem/dt/atomically.svg?style=flat)](https://rubygems.org/gems/atomically)
6
- [![Code Climate](https://codeclimate.com/github/khiav223577/atomically/badges/gpa.svg)](https://codeclimate.com/github/khiav223577/atomically)
7
- [![Test Coverage](https://codeclimate.com/github/khiav223577/atomically/badges/coverage.svg)](https://codeclimate.com/github/khiav223577/atomically/coverage)
8
-
9
- `atomically` is a Ruby Gem for you to write atomic query with ease.
10
-
11
- All methods are defined in `Atomically::QueryService` instead of defining in `ActiveRecord` directly, in order not to pollute the model instance.
12
-
13
- ## Supports
14
- - Ruby 2.2 ~ 2.7
15
- - Rails 3.2, 4.2, 5.0, 5.1, 5.2, 6.0
16
- - MySQL, PostgreSQL
17
-
18
- ## Table of contents
19
-
20
- 1. [Installation](#installation)
21
- 2. [Methods](#methods)
22
- - Relation Methods
23
- - [create_or_plus](#create_or_plus-columns-values-on_duplicate_update_columns-conflict_target)
24
- - [pay_all](#pay_all-hash-update_columns-primary_key-id)
25
- - [update_all](#update_all-expected_number-updates)
26
- - [update_all_and_get_ids](#update_all_and_get_ids-updates)
27
- - Model Methods
28
- - [update](#update-attrs-from-not_set)
29
- - [decrement_unsigned_counters](#decrement_unsigned_counters-counters)
30
- 3. [Development](#development)
31
- 4. [Contributing](#contributing)
32
- 5. [License](#license)
33
-
34
- ## Installation
35
-
36
- Add this line to your application's Gemfile:
37
-
38
- ```ruby
39
- gem 'atomically'
40
- ```
41
-
42
- And then execute:
43
-
44
- $ bundle
45
-
46
- Or install it yourself as:
47
-
48
- $ gem install atomically
49
-
50
- ## Methods
51
-
52
- Note: ActiveRecord validations and callbacks will **NOT** be triggered when calling below methods.
53
-
54
- ### create_or_plus _(columns, values, on_duplicate_update_columns, conflict_target:)_
55
-
56
- Import an array of records. When key is duplicate, plus the old value with new value.
57
- It is useful to add `items` to `user` when `user_items` may not exist. (Let `User` and `Item` are many-to-many relationship.)
58
-
59
- #### Parameters
60
-
61
- - First two args (`columns`, `values`) are the same with the [import](https://github.com/zdennis/activerecord-import#columns-and-arrays) method.
62
- - `on_duplicate_update_columns` - The column that will be updated on duplicate.
63
- - `conflict_target` - Needed only in pg. Specifies which columns have unique index.
64
-
65
- #### Example
66
-
67
- ```rb
68
- class User < ApplicationRecord
69
- has_many :user_items
70
- has_many :items, through: :user_items
71
- end
72
-
73
- class UserItem < ApplicationRecord
74
- belongs_to :user
75
- belongs_to :item
76
- end
77
-
78
- class Item < ApplicationRecord
79
- has_many :user_items
80
- has_many :users, through: :user_items
81
- end
82
-
83
- user = User.find(2)
84
- item1 = Item.find(1)
85
- item2 = Item.find(2)
86
- ```
87
-
88
- ```rb
89
- columns = [:user_id, :item_id, :quantity]
90
- values = [[user.id, item1.id, 3], [user.id, item2.id, 2]]
91
-
92
- # mysql
93
- UserItem.atomically.create_or_plus(columns, values, [:quantity])
94
-
95
- # pg
96
- UserItem.atomically.create_or_plus(columns, values, [:quantity], conflict_target: [:user_id, :item_id])
97
- ```
98
-
99
- before
100
-
101
- ![before](https://user-images.githubusercontent.com/4011729/67365648-95e89480-f5a4-11e9-8147-279385c6f442.png)
102
-
103
- after
104
-
105
- ![after](https://user-images.githubusercontent.com/4011729/67365653-97b25800-f5a4-11e9-8314-8e6ff8d2cd61.png)
106
-
107
-
108
- #### SQL queries
109
-
110
- ```sql
111
- # mysql
112
- INSERT INTO `user_items` (`user_id`,`item_id`,`quantity`,`created_at`,`updated_at`) VALUES
113
- (2,1,3,'2018-11-27 03:44:25','2018-11-27 03:44:25'),
114
- (2,2,2,'2018-11-27 03:44:25','2018-11-27 03:44:25')
115
- ON DUPLICATE KEY UPDATE
116
- `quantity` = `quantity` + VALUES(`quantity`)
117
-
118
- # pg
119
- INSERT INTO "user_items" ("user_id","item_id","quantity","created_at","updated_at") VALUES
120
- (2,1,3,'2018-11-27 03:44:25.847909','2018-11-27 03:44:25.847909'),
121
- (2,2,2,'2018-11-27 03:44:25.847909','2018-11-27 03:44:25.847909')
122
- ON CONFLICT (user_id, item_id) DO UPDATE SET
123
- "quantity" = "user_items"."quantity" + excluded."quantity" RETURNING "id"
124
- ```
125
-
126
- ---
127
- ### pay_all _(hash, update_columns, primary_key: :id)_
128
-
129
- Reduce the quantity of items and return how many rows and updated if all of them are enough.
130
- Do nothing and return zero if any of them is not enough.
131
-
132
- #### Parameters
133
-
134
- - `hash` - A hash contains the id of the models as keys and the amount to update the field by as values.
135
- - `update_columns` - The column that will be updated.
136
- - `primary_key` - Specify the column that `id`(the key of hash) refers to.
137
-
138
- #### Example
139
-
140
- ```rb
141
- user.user_items.atomically.pay_all({ item1.id => 4, item2.id => 3 }, [:quantity], primary_key: :item_id)
142
- # => 2 (if success)
143
- # => 0 (if some aren't enough)
144
- ```
145
-
146
- #### SQL queries
147
-
148
- ```sql
149
- UPDATE `user_items` SET `quantity` = `quantity` + (@change :=
150
- CASE `item_id`
151
- WHEN 1 THEN -4
152
- WHEN 2 THEN -3
153
- END)
154
- WHERE `user_items`.`user_id` = 1 AND (
155
- `user_items`.`item_id` = 1 AND (`quantity` >= 4) OR `user_items`.`item_id` = 2 AND (`quantity` >= 3)
156
- ) AND (
157
- (
158
- SELECT COUNT(*) FROM (
159
- SELECT `user_items`.* FROM `user_items`
160
- WHERE `user_items`.`user_id` = 1 AND (
161
- `user_items`.`item_id` = 1 AND (`quantity` >= 4) OR `user_items`.`item_id` = 2 AND (`quantity` >= 3)
162
- )
163
- ) subquery
164
- ) = 2
165
- )
166
- ```
167
-
168
- ---
169
- ### update_all _(expected_number, updates)_
170
-
171
- Behaves like [ActiveRecord::Relation#update_all](https://apidock.com/rails/ActiveRecord/Relation/update_all) but add an additional constrain that the number of affected rows equals to what you specify.
172
-
173
- #### Parameters
174
-
175
- - `expected_number` - The number of rows that you expect to be updated.
176
- - `updates` - A string, array, or hash representing the SET part of an SQL statement.
177
-
178
- #### Examples
179
- ```rb
180
- User.where(id: [5, 6]).atomically.update_all(2, name: '')
181
- # => 2 (success)
182
-
183
- User.where(id: [7, 8, 9]).atomically.update_all(2, name: '')
184
- # => 0 (fail)
185
- ```
186
-
187
- #### SQL queries
188
-
189
- ```sql
190
- # User.where(id: [7, 8, 9]).atomically.update_all(2, name: '')
191
- UPDATE `users` SET `users`.`name` = '' WHERE `users`.`id` IN (7, 8, 9) AND (
192
- (
193
- SELECT COUNT(*) FROM (
194
- SELECT `users`.* FROM `users` WHERE `users`.`id` IN (7, 8, 9)
195
- ) subquery
196
- ) = 2
197
- )
198
- ```
199
-
200
- ---
201
- ### update_all_and_get_ids _(updates)_
202
-
203
- Behaves like [ActiveRecord::Relation#update_all](https://apidock.com/rails/ActiveRecord/Relation/update_all), but return an array of updated records' id instead of the number of updated records.
204
-
205
-
206
- #### Parameters
207
-
208
- - `updates` - A string, array, or hash representing the SET part of an SQL statement.
209
-
210
- #### Example
211
-
212
- ```rb
213
- User.where(account: ['moon', 'wolf']).atomically.update_all_and_get_ids('money = money + 1')
214
- # => [254, 371] (array of updated user ids)
215
-
216
- User.where(account: ['moon', 'wolf']).update_all('money = money + 1')
217
- # => 2 (the number of updated records)
218
- ```
219
-
220
- #### SQL queries
221
-
222
- ```sql
223
- # mysql
224
- BEGIN
225
- SET @ids := NULL
226
- UPDATE `users` SET money = money + 1 WHERE `users`.`account` IN ('moon', 'wolf') AND ((SELECT @ids := CONCAT_WS(',', `users`.`id`, @ids)))
227
- SELECT @ids FROM DUAL
228
- COMMIT
229
-
230
- # pg
231
- UPDATE 'users' SET money = money + 1 RETURNING id
232
- ```
233
-
234
- ---
235
- ### update _(attrs, from: :not_set)_
236
-
237
- Updates the attributes of the model from the passed-in hash and saves the record. Return true if update successfully, false otherwise. This method can detect race condition and make sure the model is updated only once.
238
-
239
- The difference between this method and [ActiveRecord#update](https://apidock.com/rails/ActiveRecord/Persistence/update) is that it will add extra WHERE conditions to prevent race condition.
240
-
241
- #### Parameters
242
-
243
- - `attrs` - Same with the first parameter of [ActiveRecord#update](https://apidock.com/rails/ActiveRecord/Persistence/update)
244
- - `from` - The value before update. If not set, use the current attriutes of the model.
245
-
246
- #### Example
247
-
248
- ```rb
249
- class Arena < ApplicationRecord
250
- def atomically_close!
251
- atomically.update(closed_at: Time.now)
252
- end
253
-
254
- def close!
255
- update(closed_at: Time.now)
256
- end
257
- end
258
- ```
259
-
260
- Let `arena.closed_at` be nil.
261
-
262
- ```rb
263
- arena.atomically_close!
264
- # => true (if success)
265
- # => false (if race condition occurs)
266
- ```
267
-
268
- The return value can be used to prevent race condition and make sure some piece of code is executed only once.
269
-
270
- ```rb
271
- if arena.atomically_close!
272
- # Only one request can pass this check and execute the code here.
273
- # You can send rewards, calculate ranking, or fire background job here.
274
- # No need to worry about being invoked multiple times.
275
- do_something
276
- end
277
- ```
278
-
279
- #### SQL queries
280
-
281
-
282
- ```sql
283
- # arena.atomically_close!
284
- UPDATE `arenas` SET `arenas`.`closed_at` = '2018-11-27 03:44:25', `updated_at` = '2018-11-27 03:44:25'
285
- WHERE `arenas`.`id` = 1752 AND `arenas`.`closed_at` IS NULL
286
-
287
- # arena.close!
288
- UPDATE `arenas` SET `arenas`.`closed_at` = '2018-11-27 03:44:25', `updated_at` = '2018-11-27 03:44:25'
289
- WHERE `arenas`.`id` = 1752
290
- ```
291
-
292
- ---
293
- ### decrement_unsigned_counters _(counters)_
294
-
295
- Decrement numeric fields via a direct SQL update, and make sure that it will not become negative.
296
-
297
- Return true if update successfully, false otherwise.
298
-
299
-
300
- #### Parameters
301
-
302
- - `counters` - A Hash containing the names of the fields to update as keys and the amount to update the field by as values.
303
-
304
- #### Example
305
-
306
- ```rb
307
- user.money
308
- # => 100
309
-
310
- user.atomically.decrement_unsigned_counters(money: 10)
311
- # => true (success)
312
- user.reload.money
313
- # => 90
314
-
315
- user.atomically.decrement_unsigned_counters(money: 999)
316
- # => false (fail)
317
- user.reload.money
318
- # => 90
319
- ```
320
-
321
- #### SQL queries
322
-
323
- ```sql
324
- # user.atomically.decrement_unsigned_counters(money: 140)
325
- UPDATE `users` SET money = money - 140 WHERE `users`.`id` = 1 AND (money >= 140)
326
- ```
327
-
328
- ## Development
329
-
330
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test DB=mysql` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
331
-
332
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
333
-
334
- ## Contributing
335
-
336
- Bug reports and pull requests are welcome on GitHub at https://github.com/khiav223577/atomically. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
337
-
338
-
339
- ## License
340
-
341
- The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
342
-
1
+ # Atomically
2
+
3
+ [![Gem Version](https://img.shields.io/gem/v/atomically.svg?style=flat)](https://rubygems.org/gems/atomically)
4
+ [![Build Status](https://github.com/khiav223577/atomically/workflows/Ruby/badge.svg)](https://github.com/khiav223577/atomically/actions)
5
+ [![RubyGems](http://img.shields.io/gem/dt/atomically.svg?style=flat)](https://rubygems.org/gems/atomically)
6
+ [![Code Climate](https://codeclimate.com/github/khiav223577/atomically/badges/gpa.svg)](https://codeclimate.com/github/khiav223577/atomically)
7
+ [![Test Coverage](https://codeclimate.com/github/khiav223577/atomically/badges/coverage.svg)](https://codeclimate.com/github/khiav223577/atomically/coverage)
8
+
9
+ `atomically` is a Ruby Gem for you to write atomic query with ease.
10
+
11
+ All methods are defined in `Atomically::QueryService` instead of defining in `ActiveRecord` directly, in order not to pollute the model instance.
12
+
13
+ ## Supports
14
+ - Ruby 2.2 ~ 2.7, 3.0
15
+ - Rails 3.2, 4.2, 5.0, 5.1, 5.2, 6.0
16
+ - MySQL, PostgreSQL
17
+
18
+ ## Table of contents
19
+
20
+ 1. [Installation](#installation)
21
+ 2. [Methods](#methods)
22
+ - Relation Methods
23
+ - [create_or_plus](#create_or_plus-columns-values-on_duplicate_update_columns-conflict_target)
24
+ - [pay_all](#pay_all-hash-update_columns-primary_key-id)
25
+ - [update_all](#update_all-expected_number-updates)
26
+ - [update_all_and_get_ids](#update_all_and_get_ids-updates)
27
+ - Model Methods
28
+ - [update](#update-attrs-from-not_set)
29
+ - [decrement_unsigned_counters](#decrement_unsigned_counters-counters)
30
+ 3. [Development](#development)
31
+ 4. [Contributing](#contributing)
32
+ 5. [License](#license)
33
+
34
+ ## Installation
35
+
36
+ Add this line to your application's Gemfile:
37
+
38
+ ```ruby
39
+ gem 'atomically'
40
+ ```
41
+
42
+ And then execute:
43
+
44
+ $ bundle
45
+
46
+ Or install it yourself as:
47
+
48
+ $ gem install atomically
49
+
50
+ ## Methods
51
+
52
+ Note: ActiveRecord validations and callbacks will **NOT** be triggered when calling below methods.
53
+
54
+ ### create_or_plus _(columns, values, on_duplicate_update_columns, conflict_target:)_
55
+
56
+ Import an array of records. When key is duplicate, plus the old value with new value.
57
+ It is useful to add `items` to `user` when `user_items` may not exist. (Let `User` and `Item` are many-to-many relationship.)
58
+
59
+ #### Parameters
60
+
61
+ - First two args (`columns`, `values`) are the same with the [import](https://github.com/zdennis/activerecord-import#columns-and-arrays) method.
62
+ - `on_duplicate_update_columns` - The column that will be updated on duplicate.
63
+ - `conflict_target` - Needed only in pg. Specifies which columns have unique index.
64
+
65
+ #### Example
66
+
67
+ ```rb
68
+ class User < ApplicationRecord
69
+ has_many :user_items
70
+ has_many :items, through: :user_items
71
+ end
72
+
73
+ class UserItem < ApplicationRecord
74
+ belongs_to :user
75
+ belongs_to :item
76
+ end
77
+
78
+ class Item < ApplicationRecord
79
+ has_many :user_items
80
+ has_many :users, through: :user_items
81
+ end
82
+
83
+ user = User.find(2)
84
+ item1 = Item.find(1)
85
+ item2 = Item.find(2)
86
+ ```
87
+
88
+ ```rb
89
+ columns = [:user_id, :item_id, :quantity]
90
+ values = [[user.id, item1.id, 3], [user.id, item2.id, 2]]
91
+
92
+ # mysql
93
+ UserItem.atomically.create_or_plus(columns, values, [:quantity])
94
+
95
+ # pg
96
+ UserItem.atomically.create_or_plus(columns, values, [:quantity], conflict_target: [:user_id, :item_id])
97
+ ```
98
+
99
+ before
100
+
101
+ ![before](https://user-images.githubusercontent.com/4011729/67365648-95e89480-f5a4-11e9-8147-279385c6f442.png)
102
+
103
+ after
104
+
105
+ ![after](https://user-images.githubusercontent.com/4011729/67365653-97b25800-f5a4-11e9-8314-8e6ff8d2cd61.png)
106
+
107
+
108
+ #### SQL queries
109
+
110
+ ```sql
111
+ # mysql
112
+ INSERT INTO `user_items` (`user_id`,`item_id`,`quantity`,`created_at`,`updated_at`) VALUES
113
+ (2,1,3,'2018-11-27 03:44:25','2018-11-27 03:44:25'),
114
+ (2,2,2,'2018-11-27 03:44:25','2018-11-27 03:44:25')
115
+ ON DUPLICATE KEY UPDATE
116
+ `quantity` = `quantity` + VALUES(`quantity`)
117
+
118
+ # pg
119
+ INSERT INTO "user_items" ("user_id","item_id","quantity","created_at","updated_at") VALUES
120
+ (2,1,3,'2018-11-27 03:44:25.847909','2018-11-27 03:44:25.847909'),
121
+ (2,2,2,'2018-11-27 03:44:25.847909','2018-11-27 03:44:25.847909')
122
+ ON CONFLICT (user_id, item_id) DO UPDATE SET
123
+ "quantity" = "user_items"."quantity" + excluded."quantity" RETURNING "id"
124
+ ```
125
+
126
+ ---
127
+ ### pay_all _(hash, update_columns, primary_key: :id)_
128
+
129
+ Reduce the quantity of items and return how many rows and updated if all of them are enough.
130
+ Do nothing and return zero if any of them is not enough.
131
+
132
+ #### Parameters
133
+
134
+ - `hash` - A hash contains the id of the models as keys and the amount to update the field by as values.
135
+ - `update_columns` - The column that will be updated.
136
+ - `primary_key` - Specify the column that `id`(the key of hash) refers to.
137
+
138
+ #### Example
139
+
140
+ ```rb
141
+ user.user_items.atomically.pay_all({ item1.id => 4, item2.id => 3 }, [:quantity], primary_key: :item_id)
142
+ # => 2 (if success)
143
+ # => 0 (if some aren't enough)
144
+ ```
145
+
146
+ #### SQL queries
147
+
148
+ ```sql
149
+ UPDATE `user_items` SET `quantity` = `quantity` + (@change :=
150
+ CASE `item_id`
151
+ WHEN 1 THEN -4
152
+ WHEN 2 THEN -3
153
+ END)
154
+ WHERE `user_items`.`user_id` = 1 AND (
155
+ `user_items`.`item_id` = 1 AND (`quantity` >= 4) OR `user_items`.`item_id` = 2 AND (`quantity` >= 3)
156
+ ) AND (
157
+ (
158
+ SELECT COUNT(*) FROM (
159
+ SELECT `user_items`.* FROM `user_items`
160
+ WHERE `user_items`.`user_id` = 1 AND (
161
+ `user_items`.`item_id` = 1 AND (`quantity` >= 4) OR `user_items`.`item_id` = 2 AND (`quantity` >= 3)
162
+ )
163
+ ) subquery
164
+ ) = 2
165
+ )
166
+ ```
167
+
168
+ ---
169
+ ### update_all _(expected_number, updates)_
170
+
171
+ Behaves like [ActiveRecord::Relation#update_all](https://apidock.com/rails/ActiveRecord/Relation/update_all) but add an additional constrain that the number of affected rows equals to what you specify.
172
+
173
+ #### Parameters
174
+
175
+ - `expected_number` - The number of rows that you expect to be updated.
176
+ - `updates` - A string, array, or hash representing the SET part of an SQL statement.
177
+
178
+ #### Examples
179
+ ```rb
180
+ User.where(id: [5, 6]).atomically.update_all(2, name: '')
181
+ # => 2 (success)
182
+
183
+ User.where(id: [7, 8, 9]).atomically.update_all(2, name: '')
184
+ # => 0 (fail)
185
+ ```
186
+
187
+ #### SQL queries
188
+
189
+ ```sql
190
+ # User.where(id: [7, 8, 9]).atomically.update_all(2, name: '')
191
+ UPDATE `users` SET `users`.`name` = '' WHERE `users`.`id` IN (7, 8, 9) AND (
192
+ (
193
+ SELECT COUNT(*) FROM (
194
+ SELECT `users`.* FROM `users` WHERE `users`.`id` IN (7, 8, 9)
195
+ ) subquery
196
+ ) = 2
197
+ )
198
+ ```
199
+
200
+ ---
201
+ ### update_all_and_get_ids _(updates)_
202
+
203
+ Behaves like [ActiveRecord::Relation#update_all](https://apidock.com/rails/ActiveRecord/Relation/update_all), but return an array of updated records' id instead of the number of updated records.
204
+
205
+
206
+ #### Parameters
207
+
208
+ - `updates` - A string, array, or hash representing the SET part of an SQL statement.
209
+
210
+ #### Example
211
+
212
+ ```rb
213
+ User.where(account: ['moon', 'wolf']).atomically.update_all_and_get_ids('money = money + 1')
214
+ # => [254, 371] (array of updated user ids)
215
+
216
+ User.where(account: ['moon', 'wolf']).update_all('money = money + 1')
217
+ # => 2 (the number of updated records)
218
+ ```
219
+
220
+ #### SQL queries
221
+
222
+ ```sql
223
+ # mysql
224
+ BEGIN
225
+ SET @ids := NULL
226
+ UPDATE `users` SET money = money + 1 WHERE `users`.`account` IN ('moon', 'wolf') AND ((SELECT @ids := CONCAT_WS(',', `users`.`id`, @ids)))
227
+ SELECT @ids FROM DUAL
228
+ COMMIT
229
+
230
+ # pg
231
+ UPDATE 'users' SET money = money + 1 RETURNING id
232
+ ```
233
+
234
+ ---
235
+ ### update _(attrs, from: :not_set)_
236
+
237
+ Updates the attributes of the model from the passed-in hash and saves the record. Return true if update successfully, false otherwise. This method can detect race condition and make sure the model is updated only once.
238
+
239
+ The difference between this method and [ActiveRecord#update](https://apidock.com/rails/ActiveRecord/Persistence/update) is that it will add extra WHERE conditions to prevent race condition.
240
+
241
+ #### Parameters
242
+
243
+ - `attrs` - Same with the first parameter of [ActiveRecord#update](https://apidock.com/rails/ActiveRecord/Persistence/update)
244
+ - `from` - The value before update. If not set, use the current attriutes of the model.
245
+
246
+ #### Example
247
+
248
+ ```rb
249
+ class Arena < ApplicationRecord
250
+ def atomically_close!
251
+ atomically.update(closed_at: Time.now)
252
+ end
253
+
254
+ def close!
255
+ update(closed_at: Time.now)
256
+ end
257
+ end
258
+ ```
259
+
260
+ Let `arena.closed_at` be nil.
261
+
262
+ ```rb
263
+ arena.atomically_close!
264
+ # => true (if success)
265
+ # => false (if race condition occurs)
266
+ ```
267
+
268
+ The return value can be used to prevent race condition and make sure some piece of code is executed only once.
269
+
270
+ ```rb
271
+ if arena.atomically_close!
272
+ # Only one request can pass this check and execute the code here.
273
+ # You can send rewards, calculate ranking, or fire background job here.
274
+ # No need to worry about being invoked multiple times.
275
+ do_something
276
+ end
277
+ ```
278
+
279
+ #### SQL queries
280
+
281
+
282
+ ```sql
283
+ # arena.atomically_close!
284
+ UPDATE `arenas` SET `arenas`.`closed_at` = '2018-11-27 03:44:25', `updated_at` = '2018-11-27 03:44:25'
285
+ WHERE `arenas`.`id` = 1752 AND `arenas`.`closed_at` IS NULL
286
+
287
+ # arena.close!
288
+ UPDATE `arenas` SET `arenas`.`closed_at` = '2018-11-27 03:44:25', `updated_at` = '2018-11-27 03:44:25'
289
+ WHERE `arenas`.`id` = 1752
290
+ ```
291
+
292
+ ---
293
+ ### decrement_unsigned_counters _(counters)_
294
+
295
+ Decrement numeric fields via a direct SQL update, and make sure that it will not become negative.
296
+
297
+ Return true if update successfully, false otherwise.
298
+
299
+
300
+ #### Parameters
301
+
302
+ - `counters` - A Hash containing the names of the fields to update as keys and the amount to update the field by as values.
303
+
304
+ #### Example
305
+
306
+ ```rb
307
+ user.money
308
+ # => 100
309
+
310
+ user.atomically.decrement_unsigned_counters(money: 10)
311
+ # => true (success)
312
+ user.reload.money
313
+ # => 90
314
+
315
+ user.atomically.decrement_unsigned_counters(money: 999)
316
+ # => false (fail)
317
+ user.reload.money
318
+ # => 90
319
+ ```
320
+
321
+ #### SQL queries
322
+
323
+ ```sql
324
+ # user.atomically.decrement_unsigned_counters(money: 140)
325
+ UPDATE `users` SET money = money - 140 WHERE `users`.`id` = 1 AND (money >= 140)
326
+ ```
327
+
328
+ ## Development
329
+
330
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test DB=mysql` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
331
+
332
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
333
+
334
+ ## Contributing
335
+
336
+ Bug reports and pull requests are welcome on GitHub at https://github.com/khiav223577/atomically. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
337
+
338
+
339
+ ## License
340
+
341
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
342
+