check_out 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.md +55 -0
  3. data/Rakefile +34 -0
  4. data/lib/check_out.rb +140 -0
  5. metadata +83 -0
@@ -0,0 +1,20 @@
1
+ Copyright 2011 Nick Ragaz
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,55 @@
1
+ CheckOut
2
+ ============
3
+
4
+ Let users `check out` Active Record instances and then release them when they're done editing.
5
+
6
+ Using `include CheckOut` in a model's class definition provides the following features:
7
+
8
+ * Scopes:
9
+ 1. `checked_out_to(user)`
10
+ 2. `checked_out`
11
+ 3. `released` (inverse of `checked_out`)
12
+ 4. `available_to(user)`
13
+ * Collection / scope methods:
14
+ 1. `check_out_all(user)`
15
+ 2. `release_all_checkouts(user)` (`user` is optional)
16
+ * Instance methods:
17
+ 1. `checked_out?`
18
+ 2. `checked_out_to?(user)`
19
+ 3. `check_out(user)`
20
+ 4. `release(user)` (`user` is optional)
21
+ 5. `update_attributes_and_release(attributes)`
22
+
23
+ The model must have the following columns:
24
+
25
+ * `checked_out_by_user_id` (integer)
26
+ * `checked_out_by_user_type` (string)
27
+ * `checked_out_at` (datetime)
28
+
29
+ Requires Rails ~> 3 and Ruby 1.9.2.
30
+
31
+ Usage
32
+ -----
33
+
34
+ create_table "jobs" do |t|
35
+ t.integer :checked_out_by_user_id
36
+ t.string :checked_out_by_user_type
37
+ t.datetime :checked_out_at
38
+ end
39
+
40
+ class Job < ActiveRecord::Base
41
+ include CheckOut
42
+ end
43
+
44
+ # in 'edit' action
45
+
46
+ job.checkout(user) # => job.checked_out_at = Time.now, etc.
47
+
48
+ # in 'update' action
49
+
50
+ job = Job.available_to(user).find(params[:id])
51
+ job.update_attributes_and_release(params[:job])
52
+
53
+ The challenging thing is knowing when to release the record if the user decides not to make any changes (i.e. cancels) or just closes their browser.
54
+
55
+ I've found that by including `Job.release_all_checkouts(current_user)` in the 'index' action, I can avoid almost all conflicts. I also add an "unlock" action for manually overriding an existing checkout. A link to 'unlock' appears in a flash message if a user tries to edit a record that is already checked out.
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'WasNewRecord'
18
+ rdoc.options << '--line-numbers' << '--inline-source'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+
24
+ require 'rake/testtask'
25
+
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << 'lib'
28
+ t.libs << 'test'
29
+ t.pattern = 'test/**/*_test.rb'
30
+ t.verbose = false
31
+ end
32
+
33
+
34
+ task :default => :test
@@ -0,0 +1,140 @@
1
+ require 'active_support/concern'
2
+
3
+ module CheckOut
4
+ extend ActiveSupport::Concern
5
+
6
+ class AlreadyCheckedOutError < Exception; end
7
+
8
+ class NotYoursError < Exception; end
9
+
10
+ included do
11
+ belongs_to :checked_out_by_user, polymorphic: true
12
+
13
+ scope :checked_out_to, lambda { |u=nil|
14
+ u ?
15
+ where(
16
+ checked_out_by_user_id: u.id,
17
+ checked_out_by_user_type: u.class.name
18
+ ) :
19
+ checked_out
20
+ }
21
+
22
+ scope :checked_out,
23
+ where("#{table_name}.checked_out_by_user_id IS NOT NULL OR #{table_name}.checked_out_by_user_type IS NOT NULL OR #{table_name}.checked_out_at IS NOT NULL")
24
+
25
+ scope :released,
26
+ where("#{table_name}.checked_out_by_user_id IS NULL AND #{table_name}.checked_out_by_user_type IS NULL AND #{table_name}.checked_out_at IS NULL")
27
+
28
+ scope :released_or_checked_out_by, lambda { |u|
29
+ where("#{table_name}.checked_out_by_user_id IS NULL OR (#{table_name}.checked_out_by_user_id = ? AND #{table_name}.checked_out_by_user_type = ?)", u.id, u.class.name)
30
+ }
31
+
32
+ scope :available_to, lambda { |u| released_or_checked_out_by(u) }
33
+ end
34
+
35
+
36
+ module ClassMethods
37
+ def check_out_all(user)
38
+ ids = select("#{table_name}.id").map(&:id)
39
+
40
+ unscoped do
41
+ where(id: ids).
42
+ update_all(
43
+ checked_out_by_user_id: user.id,
44
+ checked_out_by_user_type: user.class.name,
45
+ checked_out_at: Time.zone.now
46
+ )
47
+ end unless ids.empty?
48
+ end
49
+ alias :check_out_all_to :check_out_all
50
+
51
+ def release_all_checkouts(user=nil)
52
+ if user
53
+ ids = select("#{table_name}.id").where(
54
+ checked_out_by_user_id: user.id,
55
+ checked_out_by_user_type: user.class.name
56
+ ).map(&:id)
57
+ else
58
+ ids = select("#{table_name}.id").checked_out.map(&:id)
59
+ end
60
+
61
+ unscoped do
62
+ changes = {
63
+ checked_out_by_user_id: nil,
64
+ checked_out_by_user_type: nil,
65
+ checked_out_at: nil
66
+ }
67
+
68
+ where(id: ids).update_all(changes)
69
+ end unless ids.empty?
70
+ end
71
+ alias :release_all_checkouts_from :release_all_checkouts
72
+ end
73
+
74
+
75
+ def checked_out_to?(user)
76
+ checked_out? &&
77
+ checked_out_by_user_type == user.class.name &&
78
+ checked_out_by_user_id == user.id
79
+ end
80
+
81
+ def checked_out?
82
+ checked_out_at?
83
+ end
84
+
85
+ def checkout(check_out_to_user)
86
+ changes = {}
87
+
88
+ if checked_out? && checked_out_by_user_id != check_out_to_user.id
89
+ raise AlreadyCheckedOutError, "#{self.class} #{self.id} is already checked out by #{checked_out_by_user_type} #{checked_out_by_user_id}"
90
+ elsif checked_out? && checked_out_by_user_id == check_out_to_user.id
91
+ changes[:checked_out_at] =
92
+ write_attribute(:checked_out_at, current_time_from_proper_timezone)
93
+ else
94
+ changes[:checked_out_by_user_id] =
95
+ write_attribute(:checked_out_by_user_id, check_out_to_user.id)
96
+ changes[:checked_out_by_user_type] =
97
+ write_attribute(:checked_out_by_user_type, check_out_to_user.class.name)
98
+ changes[:checked_out_at] =
99
+ write_attribute(:checked_out_at, current_time_from_proper_timezone)
100
+ end
101
+
102
+ self.class.unscoped do
103
+ self.class.update_all changes, { id: self.id }
104
+ end
105
+ end
106
+ alias :check_out :checkout
107
+ alias :checkout_to :checkout
108
+
109
+ def release(releasing_user=nil)
110
+ if releasing_user && releasing_user.id == checked_out_by_user_id
111
+ release
112
+ elsif releasing_user
113
+ raise NotYoursError, "#{self.class} #{self.id} is not checked out by #{releasing_user.class.name} #{releasing_user.id} and cannot be released"
114
+ end
115
+
116
+ changes = {}
117
+ changes[:checked_out_by_user_id] =
118
+ write_attribute(:checked_out_by_user_id, nil)
119
+ changes[:checked_out_by_user_type] =
120
+ write_attribute(:checked_out_by_user_type, nil)
121
+ changes[:checked_out_at] = write_attribute(:checked_out_at, nil)
122
+
123
+ self.checked_out_by_user = nil
124
+
125
+ self.class.unscoped do
126
+ self.class.update_all changes, { id: self.id }
127
+ end
128
+ end
129
+ alias :release_from :release
130
+
131
+ def update_attributes_and_release(params={})
132
+ params = params.merge(
133
+ checked_out_by_user_id: nil,
134
+ checked_out_at: nil,
135
+ checked_out_by_user_type: nil
136
+ )
137
+
138
+ update_attributes params
139
+ end
140
+ end
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: check_out
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Nick Ragaz
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-03-20 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activerecord
16
+ requirement: &70352053378760 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '3'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70352053378760
25
+ - !ruby/object:Gem::Dependency
26
+ name: activesupport
27
+ requirement: &70352053378280 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: '3'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70352053378280
36
+ - !ruby/object:Gem::Dependency
37
+ name: sqlite3
38
+ requirement: &70352053377900 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70352053377900
47
+ description: Let users `check out` Active Record instances and then release them when
48
+ they're done editing.
49
+ email: nick.ragaz@gmail.com
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - lib/check_out.rb
55
+ - MIT-LICENSE
56
+ - Rakefile
57
+ - README.md
58
+ homepage: http://github.com/nragaz/check_out
59
+ licenses: []
60
+ post_install_message:
61
+ rdoc_options: []
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ! '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ requirements: []
77
+ rubyforge_project:
78
+ rubygems_version: 1.8.11
79
+ signing_key:
80
+ specification_version: 3
81
+ summary: Let users `check out` Active Record instances and then release them when
82
+ they're done editing.
83
+ test_files: []