lockless 0.1.0 → 0.2.2

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: 309a363dd9ca963e995c6e95b2e8b5a7d3b56e4a18e5a1db38c907f1849e3582
4
- data.tar.gz: 35ab5c79f82880b34f741a505232d75bd2b9675bf9be6362ec621d4314084947
3
+ metadata.gz: dfbf0af85c74d0fc3b05165382effa2efcc35648ba4d6fa0c378a9d7bb2b53ff
4
+ data.tar.gz: ab37c259d96aeb2689756b84748be5648ced851c374db572ff6d215fb17167c6
5
5
  SHA512:
6
- metadata.gz: 05c084e18869a15d55038959dd0e18c170b95f8ff11be5a9799cafaf9a8e88381a6c74394493b9739674e6ee7c824e2f17dc44abfbd748d0d27765e33f5c4a5e
7
- data.tar.gz: 86451dffd6f60aff22d91808d97a8712a472aef3e54574b14bfa9d7eb0152c91089213cfc2dcfdbb68f73c1d3f0ca24137569ed20b23478dc2a7f3dae01dbc32
6
+ metadata.gz: 99c8c3878e4353f8a8863dc0d4efb0b24a63ac6460fdf438274a225404cf0a106af52e677cf3869fced18aa736c9a0d3ad9afdee5fa97ae6219fb3cd54861360
7
+ data.tar.gz: c06923cbc493206e78bf8c5376a3b3ba21c2d7265ed461d2018fe50ca1358e2d1af66def2d03e015cd1a97ad032f4f944bacb15d6391e7ed60d7b97212948b1a
@@ -1,4 +1,4 @@
1
- name: Ruby
1
+ name: RubyCI
2
2
 
3
3
  on: [push,pull_request]
4
4
 
data/.rspec_status CHANGED
@@ -1,7 +1,9 @@
1
1
  example_id | status | run_time |
2
2
  ------------------------------------------ | ------ | --------------- |
3
- ./spec/lockless/model_spec.rb[1:1] | passed | 0.01249 seconds |
4
- ./spec/lockless/model_spec.rb[1:2:1:1] | passed | 0.02708 seconds |
5
- ./spec/lockless/model_spec.rb[1:2:2:1:1] | passed | 0.00505 seconds |
6
- ./spec/lockless/model_spec.rb[1:2:2:1:2:1] | passed | 0.02621 seconds |
7
- ./spec/lockless/model_spec.rb[1:2:2:2:1] | passed | 0.00658 seconds |
3
+ ./spec/lockless/model_spec.rb[1:1] | passed | 0.01148 seconds |
4
+ ./spec/lockless/model_spec.rb[1:2:1:1] | passed | 0.02232 seconds |
5
+ ./spec/lockless/model_spec.rb[1:3:1:1] | passed | 0.00415 seconds |
6
+ ./spec/lockless/model_spec.rb[1:3:2:1:1] | passed | 0.005 seconds |
7
+ ./spec/lockless/model_spec.rb[1:3:2:1:2:1] | passed | 0.03027 seconds |
8
+ ./spec/lockless/model_spec.rb[1:3:2:2:1] | passed | 0.00563 seconds |
9
+ ./spec/lockless/model_spec.rb[1:3:2:2:2] | passed | 0.00243 seconds |
data/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # Lockless
2
2
 
3
- Concurrently update records without locks.
3
+ [![Gem Version](https://badge.fury.io/rb/lockless.png)](https://badge.fury.io/rb/lockless)
4
+ [![CI Status](https://github.com/cianmce/lockless/actions/workflows/main.yml/badge.svg)](https://github.com/cianmce/lockless/actions)
4
5
 
5
6
  Allows for safe concurrent updates to a single record without the need for locks.
6
7
 
@@ -54,6 +55,15 @@ class AddLocklessUuidToUsers < ActiveRecord::Migration[5.0]
54
55
  end
55
56
  ```
56
57
 
58
+ ### Custom column name
59
+
60
+ ```ruby
61
+ class User < ActiveRecord::Base
62
+ include Lockless::Model
63
+ self.lockless_column = :custom_uuid
64
+ end
65
+ ```
66
+
57
67
  ### Sample Usage
58
68
 
59
69
  ```ruby
@@ -66,15 +76,28 @@ user2.name = "new name2"
66
76
 
67
77
  # Save user2 before saving user1
68
78
  user2.lockless_save! # => true
79
+
80
+ # user1 fails to save as it's been updated earlier by user2
69
81
  user1.lockless_save! # => false
70
82
 
71
- # when lockless_save doesn't work you can either ignored the update
83
+ # when lockless_save doesn't work you can either ignore the update and continue
72
84
  # or reload the record and try again
85
+ # or calling `.save!` will forcefully save changes without respecting lockless
86
+ # `.save!` will cause the UUID to be changed again
73
87
 
74
88
  user1.reload
75
89
  user1.name # => "new name2"
76
90
  user1.name = "new name3"
77
91
  user1.lockless_save! # => true
92
+ user1.reload.name # => "new name3"
93
+
94
+ user1.reload
95
+ user1.name = "new name1"
96
+ User.all.update_all(name: "new name4")
97
+
98
+ # user1 fails to save as it's been updated earlier in `update_all`
99
+ user1.lockless_save! # => false
100
+ user1.reload.name # => "new name4"
78
101
  ```
79
102
 
80
103
  ## Development
@@ -83,13 +106,26 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
83
106
 
84
107
  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 the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
85
108
 
86
- ### Run all specs + standardrb
109
+ ### Testing
110
+ #### Run all specs + standardrb
87
111
 
88
112
  ```sh
89
113
  bundle exec rake
90
114
  ```
91
115
 
92
- ### Run specs using guard
116
+ #### Run only standardrb
117
+
118
+ ```sh
119
+ bundle exec rake standard
120
+ ````
121
+
122
+ #### Apply standardrb auto fixes
123
+
124
+ ```sh
125
+ bundle exec rake standard:fix
126
+ ```
127
+
128
+ #### Run specs using guard
93
129
 
94
130
  ```sh
95
131
  bundle exec guard
@@ -111,6 +147,5 @@ Everyone interacting in the Lockless project's codebases, issue trackers, chat r
111
147
 
112
148
  ## TODO
113
149
 
114
- - [ ] Allow for custom lockless column name
115
150
  - [ ] Allow for custom primary key column name
116
151
  - [ ] Allow a boolean to be passed to allow for validation to be skipped like in `.save`
@@ -2,6 +2,11 @@
2
2
 
3
3
  module Lockless
4
4
  # Handles lockless saves
5
+
6
+ #
7
+ # Options:
8
+ #
9
+ # - :lockless_column - The columns used to track soft delete, defaults to `:lockless_uuid`.
5
10
  module Model
6
11
  extend ActiveSupport::Concern
7
12
 
@@ -10,6 +15,14 @@ module Lockless
10
15
  self.lockless_column = :lockless_uuid
11
16
 
12
17
  before_save :set_lockless_uuid
18
+
19
+ delegate :generate_uuid, :lockless_column, to: :class
20
+ end
21
+
22
+ class_methods do
23
+ def generate_uuid
24
+ SecureRandom.uuid
25
+ end
13
26
  end
14
27
 
15
28
  # Saves record if it has not be modified in the time after it was loaded from the database.
@@ -18,18 +31,18 @@ module Lockless
18
31
  # false is returned if record is outdated or invalid
19
32
  def lockless_save
20
33
  return false unless valid?
21
- old_lockless_uuid = lockless_uuid
34
+ old_lockless_uuid = public_send(lockless_column)
22
35
  return save if new_record?
23
36
 
24
37
  run_callbacks(:save) do |variable|
25
38
  new_attrs = changed.collect { |prop| [prop.to_sym, self[prop]] }.to_h
26
39
 
27
- update_count = self.class.where(id: id, lockless_uuid: old_lockless_uuid).update_all(new_attrs)
40
+ update_count = self.class.where(:id => id, lockless_column => old_lockless_uuid).update_all(new_attrs)
28
41
  if update_count == 1
29
42
  changes_applied
30
43
  true
31
44
  else
32
- self.lockless_uuid = old_lockless_uuid
45
+ public_send("#{lockless_column}=", old_lockless_uuid)
33
46
  false
34
47
  end
35
48
  end
@@ -49,7 +62,7 @@ module Lockless
49
62
  private
50
63
 
51
64
  def set_lockless_uuid
52
- self.lockless_uuid = SecureRandom.uuid
65
+ public_send("#{lockless_column}=", generate_uuid)
53
66
  end
54
67
  end
55
68
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lockless
4
+ module Relation
5
+ # Appends update attributes with a random UUID when the model is a lockless model
6
+ #
7
+ # @param [String, Array, Hash] A string, array, or hash representing the SET part of an SQL statement.
8
+ # Lockless will only append random UUID updates if the param is a Hash
9
+ #
10
+ # @return [Boolean] Similar to `.save`, true is returned if the model is updated
11
+ # false is returned if record is outdated or invalid
12
+ def update_all(updates)
13
+ if updates.is_a?(Hash)
14
+ if model.method_defined?(:lockless_column) && updates[model.lockless_column].blank?
15
+ updates[model.lockless_column] = model.generate_uuid
16
+ end
17
+ end
18
+
19
+ super(updates)
20
+ end
21
+ end
22
+ end
23
+
24
+ ActiveRecord::Relation.prepend(Lockless::Relation)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Lockless
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.2"
5
5
  end
data/lib/lockless.rb CHANGED
@@ -4,3 +4,4 @@ require "active_record"
4
4
 
5
5
  require "lockless/version"
6
6
  require "lockless/model"
7
+ require "lockless/relation"
data/lockless.gemspec CHANGED
@@ -14,10 +14,9 @@ Gem::Specification.new do |spec|
14
14
  spec.license = "MIT"
15
15
  spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
16
16
 
17
- # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
18
-
19
17
  spec.metadata["homepage_uri"] = spec.homepage
20
18
  spec.metadata["source_code_uri"] = spec.homepage
19
+ spec.metadata["documentation_uri"] = "https://www.rubydoc.info/gems/lockless/#{Lockless::VERSION}"
21
20
 
22
21
  # Specify which files should be added to the gem when it is released.
23
22
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
@@ -28,12 +27,6 @@ Gem::Specification.new do |spec|
28
27
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
29
28
  spec.require_paths = ["lib"]
30
29
 
31
- # Uncomment to register a new dependency of your gem
32
- # spec.add_dependency "example-gem", "~> 1.0"
33
-
34
- # For more information and examples about making a new gem, checkout our
35
- # guide at: https://bundler.io/guides/creating_gem.html
36
-
37
30
  spec.add_development_dependency "standardrb", "~> 1.0"
38
31
  spec.add_development_dependency "bundler", ">= 1"
39
32
  spec.add_development_dependency "rake", "~> 13.0"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lockless
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cian McElhinney
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-11-21 00:00:00.000000000 Z
11
+ date: 2021-11-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: standardrb
@@ -215,6 +215,7 @@ files:
215
215
  - bin/setup
216
216
  - lib/lockless.rb
217
217
  - lib/lockless/model.rb
218
+ - lib/lockless/relation.rb
218
219
  - lib/lockless/version.rb
219
220
  - lockless.gemspec
220
221
  homepage: https://github.com/cianmce/lockless
@@ -223,6 +224,7 @@ licenses:
223
224
  metadata:
224
225
  homepage_uri: https://github.com/cianmce/lockless
225
226
  source_code_uri: https://github.com/cianmce/lockless
227
+ documentation_uri: https://www.rubydoc.info/gems/lockless/0.2.2
226
228
  post_install_message:
227
229
  rdoc_options: []
228
230
  require_paths: