padlock-rails 0.1.0
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 +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +129 -0
- data/Rakefile +1 -0
- data/lib/generators/padlock_generator.rb +19 -0
- data/lib/generators/templates/migration.rb +15 -0
- data/lib/padlock.rb +65 -0
- data/lib/padlock/instance.rb +8 -0
- data/lib/padlock/integrations.rb +1 -0
- data/lib/padlock/integrations/active_record.rb +12 -0
- data/lib/padlock/lockable.rb +33 -0
- data/lib/padlock/user.rb +27 -0
- data/lib/padlock/version.rb +3 -0
- data/padlock.gemspec +26 -0
- data/spec/generators/padlock_generator_spec.rb +22 -0
- data/spec/padlock/instance_spec.rb +13 -0
- data/spec/padlock/lockable_spec.rb +23 -0
- data/spec/padlock/padlock_spec.rb +137 -0
- data/spec/padlock/user_spec.rb +55 -0
- data/spec/spec_helper.rb +53 -0
- metadata +171 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c3f4ce9e5ceed02e7eb545db3c689c66473e7656
|
4
|
+
data.tar.gz: 65c64cec2148b199a1422aa5333a5e226afb6dfc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e4d8e3d9c93c4dea62497344d7f733599bf0cc00b6e8c3b1404065150d3b8b06bbd20a3c3e8338ad0a869d21339ed0375bef8183d9eb4482bf7f8d64c3c21a14
|
7
|
+
data.tar.gz: 7ba8645376b082bb72ccb008eab2f3a733d9e5f53ce72cbafe1a769b073cb42773899aa2be6d4fb21bb798588021bc82fea031c931ab85e0f1e81430f87bf357
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 acuppy
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
# Padlock
|
2
|
+
|
3
|
+
[](https://travis-ci.org/CodingZeal/padlock) [](https://codeclimate.com/github/CodingZeal/padlock)
|
4
|
+
|
5
|
+
Lock a content item for editing.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'padlock'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Generate the padlocks table with the included Rails generator:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
$ rails generate padlock:install
|
21
|
+
$ rake db:migrate
|
22
|
+
```
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
#### User:
|
27
|
+
|
28
|
+
Padlock will associate every lock to a User object. Therefore, you need
|
29
|
+
to specify the User type model:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
class User < ActiveRecord::Base
|
33
|
+
acts_as_padlock_user
|
34
|
+
end
|
35
|
+
```
|
36
|
+
|
37
|
+
Optionally set the foreign key:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
class User < ActiveRecord::Base
|
41
|
+
acts_as_padlock_user foreign_key: "admin_id"
|
42
|
+
end
|
43
|
+
```
|
44
|
+
|
45
|
+
Once activated a User object can have many lockable objects
|
46
|
+
|
47
|
+
#### Locking:
|
48
|
+
|
49
|
+
All lockable objects are associated to a padlock user.
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
current_user.padlock(lockable) # => Lock the object for editing by the current_user. Override an existing lock
|
53
|
+
current_user.padlock!(lockable) # => Lock the object and raise an exception if lockable is already locked by another user
|
54
|
+
```
|
55
|
+
|
56
|
+
You can also pass in multiple lockable objects to a single user.
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
current_user.padlock(lockable_1 [, lockable_2, ...])
|
60
|
+
```
|
61
|
+
|
62
|
+
Or check the status of a single object.
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
current_user.locked?(lockable) # => true/false
|
66
|
+
```
|
67
|
+
|
68
|
+
For integration with the Timeout gem, you can touch a lockable object and extend the padlock's TTL.
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
currrent_user.touch(lockable_1 [, lockable, ...])
|
72
|
+
```
|
73
|
+
|
74
|
+
Padlocks can also be administered through the global Padlock object
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
Padlock.lock(current_user, lockable [, lockable, ...]) # => locks it to the user
|
78
|
+
Padlock.locked?(lockable) # => true/false
|
79
|
+
Padlock.unlock!(lockable_1 [, lockable_2, ...]) # => unlocks a group of objects
|
80
|
+
Padlock.unlocked?(lockable) # => true/false
|
81
|
+
```
|
82
|
+
|
83
|
+
#### Lockable Objects:
|
84
|
+
|
85
|
+
Padlock assumes that all objects inherited from `ActiveRecord::Base` can be locked by a user record.
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
lockable.locked? # => true/false
|
89
|
+
lockable.unlocked? # => true/false
|
90
|
+
lockable.locked_by # => associated padlock user record
|
91
|
+
lockable.lock_touched_at # => last time the padlock was updated
|
92
|
+
lockable.unlock! # => unsets the padlock
|
93
|
+
```
|
94
|
+
|
95
|
+
#### Stale Padlocks
|
96
|
+
|
97
|
+
Lockables that have been locked for extended periods of time, may need to be reset. In this case you can detect stale locks and unset them.
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
Padlock.unlock_stale # => unlocks all padlocks that have not been touched in the last 24 hours
|
101
|
+
```
|
102
|
+
|
103
|
+
#### Configuration and Initialization
|
104
|
+
|
105
|
+
You can customize certain configuration options by added an initializer (config/padlock.rb) or pre-boot configuration file:
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
Padlock.config.timeout = 1.week # => sets a new unlock stale timeout. Default is 24 hours.
|
109
|
+
Padlock.config.table_name = "..." # => define the database table name for the padlocks. Default is "padlocks"
|
110
|
+
Padlock.config.user_foreign_key = "..." # => defines the foreign key relating to the User object. Default is "user_id".
|
111
|
+
Padlock.config.user_class_name = "..." # => defines the user model class. Default is "User".
|
112
|
+
```
|
113
|
+
|
114
|
+
## Credits
|
115
|
+
|
116
|
+
Authored by Adam Cuppy (@acuppy) of Coding ZEAL (http://codingzeal.com)
|
117
|
+
|
118
|
+

|
119
|
+
|
120
|
+
This is freely distributed under the MIT license. Use it, modify it,
|
121
|
+
enjoy :)
|
122
|
+
|
123
|
+
## Contributing
|
124
|
+
|
125
|
+
1. Fork it
|
126
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
127
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
128
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
129
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
require 'rails/generators/migration'
|
3
|
+
|
4
|
+
module Padlock
|
5
|
+
module Generators
|
6
|
+
class PadlockGenerator < ::Rails::Generators::Base
|
7
|
+
include Rails::Generators::Migration
|
8
|
+
source_root File.expand_path("../templates", __FILE__)
|
9
|
+
|
10
|
+
def self.next_migration_number(path)
|
11
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
12
|
+
end
|
13
|
+
|
14
|
+
def copy_migration
|
15
|
+
migration_template 'migration.rb', "db/migrate/create_padlocks.rb"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/padlock.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require "ostruct"
|
2
|
+
require "active_record"
|
3
|
+
|
4
|
+
module Padlock
|
5
|
+
class << self
|
6
|
+
def config
|
7
|
+
@@config ||= OpenStruct.new(
|
8
|
+
table_name: "padlocks",
|
9
|
+
user_foreign_key: "user_id",
|
10
|
+
user_class_name: "User",
|
11
|
+
timeout: 1.day
|
12
|
+
)
|
13
|
+
end
|
14
|
+
|
15
|
+
def lock(user, *objects)
|
16
|
+
objects.each do |object|
|
17
|
+
unlock!(object)
|
18
|
+
user.padlocks.create(lockable: object)
|
19
|
+
object.reload
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def locked? object
|
24
|
+
object.locked?
|
25
|
+
end
|
26
|
+
|
27
|
+
def unlock! *objects
|
28
|
+
objects.each { |object| object.unlock! }
|
29
|
+
end
|
30
|
+
|
31
|
+
def unlocked? object
|
32
|
+
object.unlocked?
|
33
|
+
end
|
34
|
+
|
35
|
+
def unlock_stale
|
36
|
+
stale_locks.destroy_all
|
37
|
+
end
|
38
|
+
|
39
|
+
def touch *objects
|
40
|
+
objects.each do |object|
|
41
|
+
if object.locked?
|
42
|
+
object.updated_at = Time.now
|
43
|
+
object.save
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def timeout
|
51
|
+
Time.now - config.timeout
|
52
|
+
end
|
53
|
+
|
54
|
+
def stale_locks
|
55
|
+
Padlock::Instance.where("updated_at >= ?", timeout)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
require "padlock/version"
|
61
|
+
require "padlock/instance"
|
62
|
+
require "padlock/lockable"
|
63
|
+
require "padlock/user"
|
64
|
+
require "padlock/integrations"
|
65
|
+
require "generators/padlock_generator"
|
@@ -0,0 +1,8 @@
|
|
1
|
+
module Padlock
|
2
|
+
class Instance < ActiveRecord::Base
|
3
|
+
self.table_name = Padlock.config.table_name
|
4
|
+
|
5
|
+
belongs_to :lockable, polymorphic: true
|
6
|
+
belongs_to :user, class_name: "::#{Padlock.config.user_class_name}", foreign_key: Padlock.config.user_foreign_key
|
7
|
+
end
|
8
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require File.expand_path("integrations/active_record", File.dirname(__FILE__))
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Padlock
|
2
|
+
module ActiveRecord
|
3
|
+
def acts_as_padlock_user(opts={})
|
4
|
+
Padlock.config.user_class_name = self.name
|
5
|
+
Padlock.config.user_foreign_key = opts[:foreign_key] if opts[:foreign_key].present?
|
6
|
+
|
7
|
+
class_eval { include Padlock::User }
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
ActiveRecord::Base.send :extend, Padlock::ActiveRecord
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Padlock
|
2
|
+
module Lockable
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
has_one :padlock, class_name: "Padlock::Instance", as: :lockable
|
7
|
+
end
|
8
|
+
|
9
|
+
def locked?
|
10
|
+
self.padlock.present?
|
11
|
+
end
|
12
|
+
|
13
|
+
def unlocked?
|
14
|
+
!self.locked?
|
15
|
+
end
|
16
|
+
|
17
|
+
def locked_by
|
18
|
+
self.padlock.user if self.locked?
|
19
|
+
end
|
20
|
+
|
21
|
+
def locked_by? user
|
22
|
+
locked_by == user
|
23
|
+
end
|
24
|
+
|
25
|
+
def lock_touched_at
|
26
|
+
self.padlock.updated_at if self.locked?
|
27
|
+
end
|
28
|
+
|
29
|
+
def unlock!
|
30
|
+
self.padlock.destroy if self.locked?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/padlock/user.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module Padlock
|
2
|
+
module User
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
has_many :padlocks, class_name: "Padlock::Instance", foreign_key: Padlock.config.user_foreign_key
|
7
|
+
end
|
8
|
+
|
9
|
+
def padlock *objects
|
10
|
+
Padlock.lock(self, *objects)
|
11
|
+
end
|
12
|
+
|
13
|
+
def padlock! *objects
|
14
|
+
objects.each do |object|
|
15
|
+
if object.unlocked?
|
16
|
+
Padlock.lock(self, object)
|
17
|
+
else
|
18
|
+
raise "Attempting to lock an object that is already locked"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def locked? object
|
24
|
+
self.padlocks.include? object
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/padlock.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'padlock/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "padlock-rails"
|
8
|
+
spec.version = Padlock::VERSION
|
9
|
+
spec.authors = ["Coding Zeal", "Adam Cuppy"]
|
10
|
+
spec.email = ["adam@codingzeal.com"]
|
11
|
+
spec.summary = %q{Concurent editing collision prevention}
|
12
|
+
spec.homepage = "https://github.com/CodingZeal/padlock"
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.files = `git ls-files`.split($/)
|
16
|
+
spec.test_files = spec.files.grep(%r{^(spec|features)/})
|
17
|
+
spec.require_paths = ["lib"]
|
18
|
+
|
19
|
+
spec.add_development_dependency "bundler", ">= 1.3"
|
20
|
+
spec.add_development_dependency "rake"
|
21
|
+
spec.add_development_dependency "rspec", ">= 2.14.1"
|
22
|
+
spec.add_development_dependency "generator_spec"
|
23
|
+
spec.add_development_dependency "sqlite3", ">= 1.3.0"
|
24
|
+
spec.add_development_dependency "pry"
|
25
|
+
spec.add_development_dependency "activerecord", "~> 4.0.0"
|
26
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Padlock::Generators::PadlockGenerator, type: :generator do
|
4
|
+
destination File.expand_path("../../tmp", __FILE__)
|
5
|
+
|
6
|
+
before do
|
7
|
+
prepare_destination
|
8
|
+
run_generator
|
9
|
+
end
|
10
|
+
|
11
|
+
it "has generated the migration file" do
|
12
|
+
destination_root.should have_structure do
|
13
|
+
directory "db" do
|
14
|
+
directory "migrate" do
|
15
|
+
file "create_padlocks.rb" do
|
16
|
+
contains "class CreatePadlocks"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Padlock::Instance do
|
4
|
+
let(:user) { User.create!(name: "Jim Bob") }
|
5
|
+
let(:object) { User.create!(name: "Annie Bob") }
|
6
|
+
|
7
|
+
subject { Padlock::Instance.new(lockable: object, user: user) }
|
8
|
+
|
9
|
+
it { should be_a_kind_of ActiveRecord::Base }
|
10
|
+
|
11
|
+
its(:user) { should == user }
|
12
|
+
its(:lockable) { should == object }
|
13
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe LockableObject do
|
4
|
+
subject { LockableObject.create }
|
5
|
+
|
6
|
+
let(:user) { User.create!(name: "Jim Bob") }
|
7
|
+
|
8
|
+
context "when unlocked" do
|
9
|
+
it { should_not be_locked }
|
10
|
+
it { should be_unlocked }
|
11
|
+
it { expect(subject.locked_by).to be_nil }
|
12
|
+
it { expect(subject.lock_touched_at).to be_nil }
|
13
|
+
end
|
14
|
+
|
15
|
+
context "when locked" do
|
16
|
+
before { user.padlock(subject) }
|
17
|
+
|
18
|
+
it { should be_locked }
|
19
|
+
it { should_not be_unlocked }
|
20
|
+
it { expect(subject.locked_by).to eq user }
|
21
|
+
it { expect(subject.lock_touched_at).to_not be_nil }
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Padlock do
|
4
|
+
describe ".config" do
|
5
|
+
let(:config) { nil }
|
6
|
+
|
7
|
+
before { Padlock.class_variable_set(:@@config, config) }
|
8
|
+
|
9
|
+
it { expect( Padlock.config ).to be_a_kind_of OpenStruct }
|
10
|
+
|
11
|
+
it "returns a persistant object" do
|
12
|
+
expect( Padlock.config.random ).to be_nil
|
13
|
+
Padlock.config.random = "value"
|
14
|
+
expect( Padlock.config.random ).to eq "value"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe ".lock" do
|
19
|
+
let(:object) { double(:object) }
|
20
|
+
let(:user) { double(:user) }
|
21
|
+
let(:padlocks) { double(:padlocks) }
|
22
|
+
|
23
|
+
before { user.stub(:padlocks).and_return(padlocks) }
|
24
|
+
|
25
|
+
it "unlocks the object before attempting to lock it" do
|
26
|
+
object.should_receive(:unlock!)
|
27
|
+
padlocks.stub(:create)
|
28
|
+
object.stub(:reload)
|
29
|
+
Padlock.lock(user, object)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "creates the Padlock::Instance" do
|
33
|
+
object.stub(:unlock!)
|
34
|
+
padlocks.should_receive(:create).with(lockable: object)
|
35
|
+
object.stub(:reload)
|
36
|
+
Padlock.lock(user, object)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "reloads the object" do
|
40
|
+
object.stub(:unlock!)
|
41
|
+
padlocks.stub(:create)
|
42
|
+
object.should_receive(:reload)
|
43
|
+
Padlock.lock(user, object)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "accepts a splat of objects" do
|
47
|
+
expect(object).to receive(:unlock!).exactly(2).times
|
48
|
+
expect(padlocks).to receive(:create).exactly(2).times
|
49
|
+
expect(object).to receive(:reload).exactly(2).times
|
50
|
+
Padlock.lock(user, object, object)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe ".locked?" do
|
55
|
+
let(:object) { User.create!(name: "Annie Bob") }
|
56
|
+
|
57
|
+
it do
|
58
|
+
object.should_receive(:locked?).and_return(true)
|
59
|
+
expect(Padlock.locked?(object)).to be_true
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe ".unlock!" do
|
64
|
+
let(:object) { double(:object) }
|
65
|
+
|
66
|
+
context "single object" do
|
67
|
+
it do
|
68
|
+
object.should_receive(:unlock!).exactly(1).times
|
69
|
+
Padlock.unlock!(object)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context "multiple objects" do
|
74
|
+
it do
|
75
|
+
object.should_receive(:unlock!).exactly(2).times
|
76
|
+
Padlock.unlock!(object, object)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe ".unlocked?" do
|
82
|
+
let(:object) { User.create!(name: "Annie Bob") }
|
83
|
+
|
84
|
+
it do
|
85
|
+
object.should_receive(:unlocked?).and_return(true)
|
86
|
+
expect(Padlock.unlocked?(object)).to be_true
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe ".unlock_stale" do
|
91
|
+
let(:object) { LockableObject.create!(created_at: Time.now - 1.week, updated_at: updated_at) }
|
92
|
+
let(:locked?) { true }
|
93
|
+
let(:updated_at) { Time.now - 2.hours }
|
94
|
+
let(:fake_relation) { double(:fake_relation) }
|
95
|
+
|
96
|
+
before do
|
97
|
+
Padlock::Instance.should_receive(:where).and_return(fake_relation)
|
98
|
+
fake_relation.should_receive(:destroy_all)
|
99
|
+
Padlock.unlock_stale
|
100
|
+
end
|
101
|
+
|
102
|
+
it "unlocks all lockables that have been locked more than 1 hour" do
|
103
|
+
expect(object).to be_unlocked
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe ".touch" do
|
108
|
+
let(:object) { LockableObject.create }
|
109
|
+
let(:locked?) { true }
|
110
|
+
let(:fake_padlock) { double(:fake_padlock) }
|
111
|
+
|
112
|
+
before do
|
113
|
+
object.stub(:locked?).and_return(locked?)
|
114
|
+
object.stub(:padlock).and_return(fake_padlock)
|
115
|
+
end
|
116
|
+
|
117
|
+
context "when locked" do
|
118
|
+
let(:locked?) { true }
|
119
|
+
|
120
|
+
it "sets a new value on the updated_at column and saves it" do
|
121
|
+
expect(object).to receive(:updated_at=)
|
122
|
+
expect(object).to receive(:save)
|
123
|
+
Padlock.touch(object)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
context "when unlocked" do
|
128
|
+
let(:locked?) { false }
|
129
|
+
|
130
|
+
it "leaves the object uneffected" do
|
131
|
+
expect(object).to_not receive(:updated_at=)
|
132
|
+
expect(object).to_not receive(:save)
|
133
|
+
Padlock.touch(object)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe User do
|
4
|
+
let(:object) { double(:object) }
|
5
|
+
subject { User.create!(name: "Jimmy John") }
|
6
|
+
|
7
|
+
describe "#padlock" do
|
8
|
+
|
9
|
+
it "delegates to Padlock.lock" do
|
10
|
+
Padlock.should_receive(:lock)
|
11
|
+
subject.padlock(object)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "accepts multiple objects" do
|
15
|
+
Padlock.stub(:lock)
|
16
|
+
subject.padlock(object, object)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "#padlock!" do
|
21
|
+
context "when unlocked" do
|
22
|
+
before do
|
23
|
+
object.stub(:unlocked?).and_return(true)
|
24
|
+
end
|
25
|
+
|
26
|
+
context "with a single object" do
|
27
|
+
it "delegates to Padlock.lock" do
|
28
|
+
Padlock.should_receive(:lock).with(subject, object).exactly(1).times
|
29
|
+
subject.padlock!(object)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "with multiple objects" do
|
34
|
+
it "delegates to Padlock.lock for each object" do
|
35
|
+
Padlock.should_receive(:lock).with(subject, object).exactly(2).times
|
36
|
+
subject.padlock!(object, object)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "#locked?" do
|
43
|
+
before { subject.stub_chain(:padlocks, :include?).and_return(include?) }
|
44
|
+
|
45
|
+
context "when included" do
|
46
|
+
let(:include?) { true }
|
47
|
+
it { expect(subject.locked?(object)).to be_true }
|
48
|
+
end
|
49
|
+
|
50
|
+
context "when not included" do
|
51
|
+
let(:include?) { false }
|
52
|
+
it { expect(subject.locked?(object)).to be_false }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'active_record'
|
3
|
+
require 'rspec'
|
4
|
+
require 'rspec/autorun'
|
5
|
+
require 'pry'
|
6
|
+
require "generator_spec"
|
7
|
+
|
8
|
+
PROJECT_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..')).freeze
|
9
|
+
$LOAD_PATH << File.join(PROJECT_ROOT, 'lib')
|
10
|
+
Dir[File.join(PROJECT_ROOT, 'spec/support/**/*.rb')].each { |file| require(file) }
|
11
|
+
|
12
|
+
require 'padlock'
|
13
|
+
|
14
|
+
RSpec.configure do |c|
|
15
|
+
c.before(:all) { setup_db }
|
16
|
+
c.after(:all) { teardown_db }
|
17
|
+
end
|
18
|
+
|
19
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
20
|
+
|
21
|
+
def setup_db
|
22
|
+
ActiveRecord::Schema.define(:version => 1) do
|
23
|
+
create_table :users do |t|
|
24
|
+
t.string :name
|
25
|
+
t.timestamps
|
26
|
+
end
|
27
|
+
|
28
|
+
create_table :lockable_objects do |t|
|
29
|
+
t.timestamps
|
30
|
+
end
|
31
|
+
|
32
|
+
create_table :padlocks do |t|
|
33
|
+
t.integer :lockable_id
|
34
|
+
t.string :lockable_type
|
35
|
+
t.integer :user_id
|
36
|
+
t.timestamps
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def teardown_db
|
42
|
+
ActiveRecord::Base.connection.tables.each do |table|
|
43
|
+
ActiveRecord::Base.connection.drop_table(table)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class User < ActiveRecord::Base
|
48
|
+
acts_as_padlock_user
|
49
|
+
end
|
50
|
+
|
51
|
+
class LockableObject < ActiveRecord::Base
|
52
|
+
include Padlock::Lockable
|
53
|
+
end
|
metadata
ADDED
@@ -0,0 +1,171 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: padlock-rails
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Coding Zeal
|
8
|
+
- Adam Cuppy
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2015-02-18 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.3'
|
21
|
+
type: :development
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '1.3'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: rake
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: rspec
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: 2.14.1
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: 2.14.1
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: generator_spec
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: sqlite3
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 1.3.0
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: 1.3.0
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: pry
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
type: :development
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: activerecord
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - "~>"
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: 4.0.0
|
105
|
+
type: :development
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - "~>"
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: 4.0.0
|
112
|
+
description:
|
113
|
+
email:
|
114
|
+
- adam@codingzeal.com
|
115
|
+
executables: []
|
116
|
+
extensions: []
|
117
|
+
extra_rdoc_files: []
|
118
|
+
files:
|
119
|
+
- ".gitignore"
|
120
|
+
- ".travis.yml"
|
121
|
+
- Gemfile
|
122
|
+
- LICENSE.txt
|
123
|
+
- README.md
|
124
|
+
- Rakefile
|
125
|
+
- lib/generators/padlock_generator.rb
|
126
|
+
- lib/generators/templates/migration.rb
|
127
|
+
- lib/padlock.rb
|
128
|
+
- lib/padlock/instance.rb
|
129
|
+
- lib/padlock/integrations.rb
|
130
|
+
- lib/padlock/integrations/active_record.rb
|
131
|
+
- lib/padlock/lockable.rb
|
132
|
+
- lib/padlock/user.rb
|
133
|
+
- lib/padlock/version.rb
|
134
|
+
- padlock.gemspec
|
135
|
+
- spec/generators/padlock_generator_spec.rb
|
136
|
+
- spec/padlock/instance_spec.rb
|
137
|
+
- spec/padlock/lockable_spec.rb
|
138
|
+
- spec/padlock/padlock_spec.rb
|
139
|
+
- spec/padlock/user_spec.rb
|
140
|
+
- spec/spec_helper.rb
|
141
|
+
homepage: https://github.com/CodingZeal/padlock
|
142
|
+
licenses:
|
143
|
+
- MIT
|
144
|
+
metadata: {}
|
145
|
+
post_install_message:
|
146
|
+
rdoc_options: []
|
147
|
+
require_paths:
|
148
|
+
- lib
|
149
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
150
|
+
requirements:
|
151
|
+
- - ">="
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: '0'
|
154
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
155
|
+
requirements:
|
156
|
+
- - ">="
|
157
|
+
- !ruby/object:Gem::Version
|
158
|
+
version: '0'
|
159
|
+
requirements: []
|
160
|
+
rubyforge_project:
|
161
|
+
rubygems_version: 2.2.2
|
162
|
+
signing_key:
|
163
|
+
specification_version: 4
|
164
|
+
summary: Concurent editing collision prevention
|
165
|
+
test_files:
|
166
|
+
- spec/generators/padlock_generator_spec.rb
|
167
|
+
- spec/padlock/instance_spec.rb
|
168
|
+
- spec/padlock/lockable_spec.rb
|
169
|
+
- spec/padlock/padlock_spec.rb
|
170
|
+
- spec/padlock/user_spec.rb
|
171
|
+
- spec/spec_helper.rb
|