editstore 1.1.5
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.rdoc +182 -0
- data/Rakefile +44 -0
- data/app/assets/javascripts/editstore/application.js +15 -0
- data/app/assets/stylesheets/editstore/application.css +13 -0
- data/app/controllers/editstore/application_controller.rb +4 -0
- data/app/helpers/editstore/application_helper.rb +4 -0
- data/app/models/editstore/change.rb +119 -0
- data/app/models/editstore/connection.rb +6 -0
- data/app/models/editstore/field.rb +6 -0
- data/app/models/editstore/object_lock.rb +83 -0
- data/app/models/editstore/project.rb +9 -0
- data/app/models/editstore/run_log.rb +10 -0
- data/app/models/editstore/state.rb +36 -0
- data/app/views/layouts/editstore/application.html.erb +14 -0
- data/config/routes.rb +2 -0
- data/db/migrate/20130710234514_create_editstore_projects.rb +15 -0
- data/db/migrate/20130711172833_create_editstore_changes.rb +18 -0
- data/db/migrate/20130711173100_create_editstore_states.rb +17 -0
- data/db/migrate/20130711211606_create_editstore_fields.rb +14 -0
- data/db/migrate/20130814221827_add_error_to_change.rb +8 -0
- data/db/migrate/20131004224346_add_pending_to_change.rb +8 -0
- data/db/migrate/20131015203809_create_object_lock.rb +12 -0
- data/db/migrate/20131015205757_add_indices.rb +16 -0
- data/db/migrate/20131016183057_create_run_log.rb +17 -0
- data/db/migrate/20131017165120_add_locking_version.rb +8 -0
- data/lib/editstore.rb +7 -0
- data/lib/editstore/engine.rb +8 -0
- data/lib/editstore/version.rb +3 -0
- data/lib/tasks/editstore_tasks.rake +33 -0
- data/lib/validators/is_druid_validator.rb +8 -0
- data/test/editstore_test.rb +7 -0
- data/test/fixtures/editstore/editstore_fields.yml +23 -0
- data/test/fixtures/editstore/editstore_projects.yml +20 -0
- data/test/test_helper.rb +15 -0
- data/test/unit/editstore/change_test.rb +9 -0
- data/test/unit/editstore/field_test.rb +9 -0
- data/test/unit/editstore/project_test.rb +9 -0
- data/test/unit/editstore/state_test.rb +9 -0
- metadata +212 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2013 YOURNAME
|
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.
|
data/README.rdoc
ADDED
@@ -0,0 +1,182 @@
|
|
1
|
+
= Editstore
|
2
|
+
|
3
|
+
This project uses MIT-LICENSE.
|
4
|
+
|
5
|
+
It provides models that allow your client web application to cache descriptive metadata updates in a special database.
|
6
|
+
|
7
|
+
== Installation
|
8
|
+
|
9
|
+
1. Add the following to your application's 'Gemfile':
|
10
|
+
|
11
|
+
source 'http://sul-gems.stanford.edu' # only if you don't already have it
|
12
|
+
gem 'editstore'
|
13
|
+
|
14
|
+
2. Install the gem
|
15
|
+
|
16
|
+
bundle install
|
17
|
+
|
18
|
+
3. Add the migrations to your app
|
19
|
+
|
20
|
+
rake editstore:install:migrations
|
21
|
+
|
22
|
+
4. Add development and test database connection stings to the 'database.yml' file in your app for development/test:
|
23
|
+
|
24
|
+
editstore_development:
|
25
|
+
adapter: sqlite3
|
26
|
+
database: db/editstore_development.sqlite3
|
27
|
+
pool: 5
|
28
|
+
timeout: 5000
|
29
|
+
|
30
|
+
editstore_test:
|
31
|
+
adapter: sqlite3
|
32
|
+
database: db/editstore_test.sqlite3
|
33
|
+
pool: 5
|
34
|
+
timeout: 5000
|
35
|
+
|
36
|
+
editstore_production:
|
37
|
+
adapter: mysql2
|
38
|
+
database: # GET IT FROM FRIENDLY SYSADMIN IF YOU NEED IT FOR PRODUCTION
|
39
|
+
|
40
|
+
5. Generate a migration to store your project specific name and fields in your own test/dev databases:
|
41
|
+
|
42
|
+
rails g migration editstore_myproject
|
43
|
+
|
44
|
+
6. Edit your migration to add your project name and fields. Be sure to have the migration use the correct Editstore database
|
45
|
+
connection string as shown below. For servers (e.g. staging, production), these changes be done after your creating your template and running the
|
46
|
+
rake editstore:update_editstore_config RAILS_ENV=environment task.
|
47
|
+
|
48
|
+
By default, migrations such as this should only run in development and test mode. By adding the "if Editstore.run_migrations?" qualifier, you will
|
49
|
+
ensure you will not get the migrations except in development and test. The reason is that in staging and production the actual editstore database
|
50
|
+
will already be properly configured when you setup the project and do not want these migrations run again on the production servers during
|
51
|
+
deployment. However, since locally you are not running an actual correctly configured instance of the editstore system, these migrations give you
|
52
|
+
enough information to make the app work in development.
|
53
|
+
|
54
|
+
An example of this migration you need to add is shown below:
|
55
|
+
|
56
|
+
def up
|
57
|
+
if Editstore.run_migrations?
|
58
|
+
@connection=Editstore::Connection.connection
|
59
|
+
project=Editstore::Project.create(:name=>'Revs',:template=>'revs')
|
60
|
+
Editstore::Field.create(:name=>'title_tsi',:project_id=>project.id)
|
61
|
+
Editstore::Field.create(:name=>'pub_year_isim',:project_id=>project.id)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
7. Run the migrations
|
66
|
+
|
67
|
+
rake db:migrate
|
68
|
+
|
69
|
+
== Usage
|
70
|
+
|
71
|
+
1. [Optional] If you define the constant "EDITSTORE_PROJECT" in your ruby/rails project as a string that matches a known project in the
|
72
|
+
editstore database , the gem will automatically associated all updates with your project. A good spot to set this
|
73
|
+
constant in a Rails app is at the bottom of the 'config/application.rb' file, e.g.
|
74
|
+
|
75
|
+
EDITSTORE_PROJECT='Revs'
|
76
|
+
|
77
|
+
2. In the application, to apply an update to DOR, you just need to create a new "Editstore::Change" object and save it.
|
78
|
+
If your application needs to update a local SOLR or MySQL, you will need to do this yourself. This object just saves
|
79
|
+
the changes to the editstore database for later propagation to DOR using a project specific template.
|
80
|
+
|
81
|
+
The properties you need to set are:
|
82
|
+
|
83
|
+
old_value : for an update or delete operation, this specifies the current value and is required
|
84
|
+
new_value : for an update or create operation, this species the new value and is required
|
85
|
+
operation : set to a symbol with one of three possible values of :update, :delete or :create
|
86
|
+
project_id : set to the project_id of the editstore project, this is set automatically if you followed step 1 above
|
87
|
+
state : set to Editstore::State.ready OR Editstore::State.wait
|
88
|
+
setting to the 'ready' state means the change can be propagated to DOR immediately
|
89
|
+
setting to the 'wait' state means you do NOT want the change to be propagated to DOR immediately
|
90
|
+
-- if you set to 'wait', you will need to keep track of the change yourself (probably using the object ID once it is saved)
|
91
|
+
so you can come and update the state later to 'ready' when you want the change propagated. This might happen if you
|
92
|
+
have curated changes
|
93
|
+
|
94
|
+
field : a valid field name defined in your project template and listed in the 'fields' table of the editstore database for your project
|
95
|
+
druid : the druid of the object you want to update (can include or not include the 'druid:' prefix)
|
96
|
+
client_note : an optional note you can set for your application purposes only
|
97
|
+
|
98
|
+
Some examples are below:
|
99
|
+
|
100
|
+
a. Add a new value:
|
101
|
+
|
102
|
+
add=Editstore::Change.new
|
103
|
+
add.new_value='new value'
|
104
|
+
add.operation=:create
|
105
|
+
add.state=Editstore::State.ready
|
106
|
+
#add.project_id=1 # you can leave this off if you set the EDITSTORE_PROJECT constant as describe above
|
107
|
+
add.field='title'
|
108
|
+
add.druid='druid:oo000oo0001'
|
109
|
+
add.client_note='some note'
|
110
|
+
add.save
|
111
|
+
|
112
|
+
b. Change an existing value:
|
113
|
+
|
114
|
+
change=Editstore::Change.new
|
115
|
+
change.new_value='new value'
|
116
|
+
change.old_value='previous value'
|
117
|
+
change.operation=:update
|
118
|
+
change.state=Editstore::State.ready
|
119
|
+
#change.project_id=1 # you can leave this off if you set the EDITSTORE_PROJECT constant as describe above
|
120
|
+
change.field='title'
|
121
|
+
change.druid='druid:oo000oo0001'
|
122
|
+
change.client_note='some note'
|
123
|
+
change.save
|
124
|
+
|
125
|
+
c. Delete an existing value (useful to delete just value in a multivalued field):
|
126
|
+
|
127
|
+
delete=Editstore::Change.new
|
128
|
+
delete.old_value='value to delete'
|
129
|
+
delete.operation=:delete
|
130
|
+
delete.state=Editstore::State.ready
|
131
|
+
#delete.project_id=1 # you can leave this off if you set the EDITSTORE_PROJECT constant as describe above
|
132
|
+
delete.field='title'
|
133
|
+
delete.druid='druid:oo000oo0001'
|
134
|
+
delete.client_note='some note'
|
135
|
+
delete.save
|
136
|
+
|
137
|
+
d. Delete all existing values (can be used for either a single valued field or can be used to delete *all* values in a multivalued field):
|
138
|
+
|
139
|
+
# just leave off the 'old_value' attribute to delete any values associated with this field
|
140
|
+
delete=Editstore::Change.new
|
141
|
+
delete.operation=:delete
|
142
|
+
delete.state=Editstore::State.ready
|
143
|
+
#delete.project_id=1 # you can leave this off if you set the EDITSTORE_PROJECT constant as describe above
|
144
|
+
delete.field='title'
|
145
|
+
delete.druid='druid:oo000oo0001'
|
146
|
+
delete.client_note='some note'
|
147
|
+
delete.save
|
148
|
+
|
149
|
+
== Useful Cleanup Tasks
|
150
|
+
|
151
|
+
When running in development mode, you will get a lot of unprocessed changes building up in the development
|
152
|
+
editstore database (since you won't have the back-end system processing them and cleaning them out). To clean out
|
153
|
+
entries from the editstore database:
|
154
|
+
|
155
|
+
rake editstore:remove_pending
|
156
|
+
|
157
|
+
In production, you can also use this task to clean out any rows that have been marked as completed:
|
158
|
+
|
159
|
+
rake editstore:remove_complete
|
160
|
+
|
161
|
+
Use this task to clear out the unlocked druids from the object lock table. Should be safe to run at any time.
|
162
|
+
|
163
|
+
rake editstore:prune_locks
|
164
|
+
|
165
|
+
Use this task to clear out the locked druids from the object lock table. Should only be run if you are sure locks have hung and
|
166
|
+
there are no other processes running.
|
167
|
+
|
168
|
+
rake editstore:clear_locks
|
169
|
+
|
170
|
+
Use this task to clear out the any log run entries older than one month. Should be safe to run at any time if you don't need more than 1 month of run log history
|
171
|
+
|
172
|
+
rake editstore:prune_run_log
|
173
|
+
|
174
|
+
== Running Tests
|
175
|
+
|
176
|
+
There is a "dummy" rails app in "spec/dummy" that is used for testing. To run the tests, run the migrations and then
|
177
|
+
run the standard rspec tests:
|
178
|
+
|
179
|
+
cd ./spec/dummy && rake editstore:install:migrations && rake db:migrate RAILS_ENV=test && cd ../..
|
180
|
+
bundle exec rspec
|
181
|
+
|
182
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,44 @@
|
|
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 = 'Editstore'
|
18
|
+
rdoc.options << '--line-numbers'
|
19
|
+
rdoc.rdoc_files.include('README.rdoc')
|
20
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
21
|
+
end
|
22
|
+
|
23
|
+
APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
|
24
|
+
load 'rails/tasks/engine.rake'
|
25
|
+
|
26
|
+
|
27
|
+
|
28
|
+
Bundler::GemHelper.install_tasks
|
29
|
+
|
30
|
+
require 'rake/testtask'
|
31
|
+
|
32
|
+
Rake::TestTask.new(:test) do |t|
|
33
|
+
t.libs << 'lib'
|
34
|
+
t.libs << 'test'
|
35
|
+
t.pattern = 'test/**/*_test.rb'
|
36
|
+
t.verbose = false
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
task :default => :test
|
41
|
+
|
42
|
+
# DLSS specific stuff
|
43
|
+
require 'dlss/rake/dlss_release'
|
44
|
+
Dlss::Release.new
|
@@ -0,0 +1,15 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// the compiled file.
|
9
|
+
//
|
10
|
+
// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
|
11
|
+
// GO AFTER THE REQUIRES BELOW.
|
12
|
+
//
|
13
|
+
//= require jquery
|
14
|
+
//= require jquery_ujs
|
15
|
+
//= require_tree .
|
@@ -0,0 +1,13 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the top of the
|
9
|
+
* compiled file, but it's generally better to create a new file per style scope.
|
10
|
+
*
|
11
|
+
*= require_self
|
12
|
+
*= require_tree .
|
13
|
+
*/
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module Editstore
|
2
|
+
require 'druid-tools'
|
3
|
+
|
4
|
+
class Change < Connection
|
5
|
+
belongs_to :state
|
6
|
+
belongs_to :project
|
7
|
+
belongs_to :object_lock, :primary_key=>:druid, :foreign_key=>:druid
|
8
|
+
attr_accessible :field,:project_id,:old_value,:new_value,:operation,:client_note,:druid,:state_id,:error,:pending
|
9
|
+
|
10
|
+
OPERATIONS=%w{create update delete}
|
11
|
+
|
12
|
+
validates :field, presence: true
|
13
|
+
validates :operation, presence: true
|
14
|
+
validates :new_value, presence: true, :if => "%w{update create}.include? self.operation.to_s"
|
15
|
+
validates :old_value, presence: true, :if => "%w{update}.include? self.operation.to_s"
|
16
|
+
validates :project_id, presence: true, numericality: { only_integer: true }
|
17
|
+
validates :state_id, presence: true, numericality: { only_integer: true }
|
18
|
+
validate :valid_state_id
|
19
|
+
validate :valid_project_id
|
20
|
+
validate :valid_field_name
|
21
|
+
validate :valid_druid
|
22
|
+
validate :valid_operation
|
23
|
+
|
24
|
+
before_validation :set_default_project, :if => "defined?(EDITSTORE_PROJECT) && !EDITSTORE_PROJECT.nil?" # this allows the user to set this in their project so it will set automatically for each change
|
25
|
+
|
26
|
+
def self.prune
|
27
|
+
self.destroy_all(:state_id=>Editstore::State.complete)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.by_project_id(project_id)
|
31
|
+
if project_id.to_s.empty?
|
32
|
+
scoped
|
33
|
+
else
|
34
|
+
where(:project_id=>project_id)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.by_state_id(state_id)
|
39
|
+
if state_id.to_s.empty?
|
40
|
+
scoped
|
41
|
+
else
|
42
|
+
where(:state_id=>state_id)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# get the latest changes to process for a specific state (defaults to ready) and an optional limit
|
47
|
+
# if not restricted by druid, will group by druid
|
48
|
+
def self.latest(params={})
|
49
|
+
|
50
|
+
state_id=params[:state_id] || Editstore::State.ready.id
|
51
|
+
limit=params[:limit]
|
52
|
+
project_id=params[:project_id]
|
53
|
+
druid=params[:druid]
|
54
|
+
|
55
|
+
changes = scoped
|
56
|
+
changes = changes.includes(:project)
|
57
|
+
changes = changes.where(:state_id=>state_id) if state_id != '*'
|
58
|
+
changes = changes.where(:project_id=>project_id) if project_id
|
59
|
+
changes = changes.where(:druid=>druid) if druid
|
60
|
+
changes = changes.order('created_at,id asc')
|
61
|
+
changes = changes.limit(limit) unless limit.blank?
|
62
|
+
druid ? changes : changes.group_by {|c| c.druid}
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
# get the latest list of unique druids to process for a specific state (defaults to ready) and an optional limit, return an array
|
67
|
+
def self.latest_druids(params={})
|
68
|
+
|
69
|
+
state_id=params[:state_id] || Editstore::State.ready.id
|
70
|
+
limit=params[:limit]
|
71
|
+
project_id=params[:project_id]
|
72
|
+
|
73
|
+
changes = scoped
|
74
|
+
changes = changes.includes(:project)
|
75
|
+
changes = changes.where(:state_id=>state_id) if state_id != '*'
|
76
|
+
changes = changes.where(:project_id=>project_id) if project_id
|
77
|
+
changes = changes.order('editstore_changes.created_at,editstore_changes.id asc')
|
78
|
+
changes = changes.limit(limit) unless limit.blank?
|
79
|
+
changes.uniq.pluck(:druid)
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
def valid_operation
|
85
|
+
errors.add(:operation, "is not valid") unless OPERATIONS.include? operation.to_s
|
86
|
+
end
|
87
|
+
|
88
|
+
# project name is case insensitive
|
89
|
+
def set_default_project
|
90
|
+
default_project = Editstore::Project.where('lower(name)=?',EDITSTORE_PROJECT.downcase).limit(1)
|
91
|
+
self.project_id = default_project.first.id if (default_project.size == 1 && !self.project_id)
|
92
|
+
end
|
93
|
+
|
94
|
+
def valid_druid
|
95
|
+
if !DruidTools::Druid.valid?(self.druid)
|
96
|
+
errors.add(:druid,"is invalid")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def valid_field_name
|
101
|
+
if Editstore::Field.where(:name=>self.field,:project_id=>self.project_id).count == 0
|
102
|
+
errors.add(:field, "is invalid")
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def valid_state_id
|
107
|
+
if !Editstore::State.exists?(self.state_id)
|
108
|
+
errors.add(:state_id, "is invalid")
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def valid_project_id
|
113
|
+
if !Editstore::Project.exists?(self.project_id)
|
114
|
+
errors.add(:project_id, "is invalid")
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Editstore
|
2
|
+
class ObjectLock < Connection
|
3
|
+
attr_accessible :locked, :druid
|
4
|
+
|
5
|
+
def self.prune
|
6
|
+
self.destroy_all(:locked=>nil)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.unlock_all
|
10
|
+
self.all_locked.each {|obj| obj.unlock}
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.get_pid(pid)
|
14
|
+
pid.start_with?('druid:') ? pid : "druid:#{pid}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.all_locked
|
18
|
+
self.where('locked IS NOT NULL')
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.all_unlocked
|
22
|
+
self.where(:locked=>nil)
|
23
|
+
end
|
24
|
+
|
25
|
+
# some convenience class level methods for operating on a specific druid
|
26
|
+
def self.lock(pid)
|
27
|
+
status=self.find_by_druid(self.get_pid(pid))
|
28
|
+
if status # this druid already exists in the table
|
29
|
+
return status.lock
|
30
|
+
else # this druid does not exist in the table, create it and lock
|
31
|
+
self.create(:druid=>self.get_pid(pid),:locked=>Time.now)
|
32
|
+
return true
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.unlock(pid)
|
37
|
+
status=self.find_by_druid(self.get_pid(pid))
|
38
|
+
if status # this druid already exists in the table
|
39
|
+
return status.unlock
|
40
|
+
else
|
41
|
+
return false # druid doesn't exist, can't unlock
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# check to see if this druid is locked
|
46
|
+
def self.locked?(pid)
|
47
|
+
status=self.find_by_druid(self.get_pid(pid))
|
48
|
+
status.nil? ? false : status.locked?
|
49
|
+
end
|
50
|
+
|
51
|
+
# object level methods for performing actions
|
52
|
+
def locked?
|
53
|
+
!unlocked?
|
54
|
+
end
|
55
|
+
|
56
|
+
def unlocked?
|
57
|
+
locked == nil
|
58
|
+
end
|
59
|
+
|
60
|
+
# lock this object
|
61
|
+
def lock
|
62
|
+
if unlocked?
|
63
|
+
self.locked = Time.now
|
64
|
+
save
|
65
|
+
return true # lock at the current time
|
66
|
+
else
|
67
|
+
return false # already locked
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# unlock this object
|
72
|
+
def unlock
|
73
|
+
if locked?
|
74
|
+
self.locked = nil
|
75
|
+
save
|
76
|
+
return true # unlock it
|
77
|
+
else
|
78
|
+
return false # already unlocked
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Editstore
|
2
|
+
class State < Connection
|
3
|
+
has_many :changes
|
4
|
+
attr_accessible :name
|
5
|
+
validates :name, presence: true
|
6
|
+
|
7
|
+
# these helper methods allow you to easily refer to a particular state like this, with the query cached in the object:
|
8
|
+
# Editstore::State.wait
|
9
|
+
# Editstore::State.ready
|
10
|
+
|
11
|
+
def self.wait
|
12
|
+
@@wait ||= self.find_by_name('wait')
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.ready
|
16
|
+
@@ready ||= self.find_by_name('ready')
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.in_process
|
20
|
+
@@in_process ||= self.find_by_name('in process')
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.error
|
24
|
+
@@error ||= self.find_by_name('error')
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.applied
|
28
|
+
@@applied ||= self.find_by_name('applied')
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.complete
|
32
|
+
@@complete ||= self.find_by_name('complete')
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Editstore</title>
|
5
|
+
<%= stylesheet_link_tag "editstore/application", :media => "all" %>
|
6
|
+
<%= javascript_include_tag "editstore/application" %>
|
7
|
+
<%= csrf_meta_tags %>
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
|
11
|
+
<%= yield %>
|
12
|
+
|
13
|
+
</body>
|
14
|
+
</html>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
class CreateEditstoreProjects < ActiveRecord::Migration
|
2
|
+
|
3
|
+
def change
|
4
|
+
if Editstore.run_migrations?
|
5
|
+
@connection=Editstore::Connection.connection
|
6
|
+
create_table :editstore_projects do |t|
|
7
|
+
t.string :name, :null=>false
|
8
|
+
t.string :template, :null=>false
|
9
|
+
t.timestamps
|
10
|
+
end
|
11
|
+
Editstore::Project.create(:id=>1,:name=>'Generic',:template=>'generic')
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class CreateEditstoreChanges < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
if Editstore.run_migrations?
|
4
|
+
@connection=Editstore::Connection.connection
|
5
|
+
create_table :editstore_changes do |t|
|
6
|
+
t.integer :project_id, :null=>false
|
7
|
+
t.string :field, :null=>false
|
8
|
+
t.string :druid, :null=>false
|
9
|
+
t.text :old_value
|
10
|
+
t.text :new_value
|
11
|
+
t.string :operation, :null=>false
|
12
|
+
t.integer :state_id, :null=>false
|
13
|
+
t.text :client_note
|
14
|
+
t.timestamps
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class CreateEditstoreStates < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
if Editstore.run_migrations?
|
4
|
+
@connection=Editstore::Connection.connection
|
5
|
+
create_table :editstore_states do |t|
|
6
|
+
t.string :name, :null=>false
|
7
|
+
t.timestamps
|
8
|
+
end
|
9
|
+
Editstore::State.create(:id=>1,:name=>'wait')
|
10
|
+
Editstore::State.create(:id=>2,:name=>'ready')
|
11
|
+
Editstore::State.create(:id=>3,:name=>'in process')
|
12
|
+
Editstore::State.create(:id=>4,:name=>'error')
|
13
|
+
Editstore::State.create(:id=>5,:name=>'applied')
|
14
|
+
Editstore::State.create(:id=>6,:name=>'complete')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class CreateEditstoreFields < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
if Editstore.run_migrations?
|
4
|
+
@connection=Editstore::Connection.connection
|
5
|
+
create_table :editstore_fields do |t|
|
6
|
+
t.integer :project_id,:null=>false
|
7
|
+
t.string :name,:null=>false
|
8
|
+
t.timestamps
|
9
|
+
end
|
10
|
+
Editstore::Field.create(:id=>1,:name=>'title',:project_id=>'1')
|
11
|
+
Editstore::Field.create(:id=>1,:name=>'description',:project_id=>'1')
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class CreateObjectLock < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
if Editstore.run_migrations?
|
4
|
+
@connection=Editstore::Connection.connection
|
5
|
+
create_table :editstore_object_locks do |t|
|
6
|
+
t.string :druid, :null=>false
|
7
|
+
t.datetime :locked
|
8
|
+
t.timestamps
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class AddIndices < ActiveRecord::Migration
|
2
|
+
def up
|
3
|
+
if Editstore.run_migrations?
|
4
|
+
@connection=Editstore::Connection.connection
|
5
|
+
add_index(:editstore_changes, :project_id)
|
6
|
+
add_index(:editstore_changes, :druid)
|
7
|
+
add_index(:editstore_changes, :state_id)
|
8
|
+
add_index(:editstore_fields, :project_id)
|
9
|
+
add_index(:editstore_object_locks, :druid)
|
10
|
+
add_index(:editstore_object_locks, :locked)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def down
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class CreateRunLog < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
if Editstore.run_migrations?
|
4
|
+
@connection=Editstore::Connection.connection
|
5
|
+
create_table :editstore_run_logs do |t|
|
6
|
+
t.integer :total_druids
|
7
|
+
t.integer :total_changes
|
8
|
+
t.integer :num_errors
|
9
|
+
t.integer :num_pending
|
10
|
+
t.string :note
|
11
|
+
t.datetime :started
|
12
|
+
t.datetime :ended
|
13
|
+
t.timestamps
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/editstore.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
namespace :editstore do
|
2
|
+
|
3
|
+
desc "Remove completed updates"
|
4
|
+
task :remove_complete => :environment do
|
5
|
+
Editstore::Change.prune
|
6
|
+
end
|
7
|
+
|
8
|
+
desc "Prune run log to remove entries over 1 month old"
|
9
|
+
task :prune_run_log => :environment do
|
10
|
+
Editstore::RunLog.prune
|
11
|
+
end
|
12
|
+
|
13
|
+
desc "Prune locked object tables to remove any unlocked druids"
|
14
|
+
task :prune_locks => :environment do
|
15
|
+
Editstore::ObjectLock.prune
|
16
|
+
end
|
17
|
+
|
18
|
+
desc "Remove all object locks for any druids"
|
19
|
+
task :clear_locks => :environment do
|
20
|
+
Editstore::ObjectLock.unlock_all
|
21
|
+
end
|
22
|
+
|
23
|
+
desc "Remove unprocessed updates"
|
24
|
+
task :remove_pending => :environment do
|
25
|
+
unless Rails.env.production?
|
26
|
+
Editstore::Change.destroy_all(:state_id=>Editstore::State.wait)
|
27
|
+
Editstore::Change.destroy_all(:state_id=>Editstore::State.ready)
|
28
|
+
else
|
29
|
+
puts "Refusing to delete since we're running under the #{Rails.env} environment. You know, for safety."
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html
|
2
|
+
|
3
|
+
# This model initially had no columns defined. If you add columns to the
|
4
|
+
# model remove the '{}' from the fixture names and add the columns immediately
|
5
|
+
# below each fixture, per the syntax in the comments below
|
6
|
+
#
|
7
|
+
title:
|
8
|
+
name: title
|
9
|
+
project_id: 1 # generic
|
10
|
+
created_at: <%=Time.now%>
|
11
|
+
updated_at: <%=Time.now%>
|
12
|
+
|
13
|
+
description:
|
14
|
+
name: description
|
15
|
+
project_id: 1 # generic
|
16
|
+
created_at: <%=Time.now%>
|
17
|
+
updated_at: <%=Time.now%>
|
18
|
+
|
19
|
+
title_revs:
|
20
|
+
name: title
|
21
|
+
project_id: 2 # revs
|
22
|
+
created_at: <%=Time.now%>
|
23
|
+
updated_at: <%=Time.now%>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html
|
2
|
+
|
3
|
+
# This model initially had no columns defined. If you add columns to the
|
4
|
+
# model remove the '{}' from the fixture names and add the columns immediately
|
5
|
+
# below each fixture, per the syntax in the comments below
|
6
|
+
#
|
7
|
+
generic:
|
8
|
+
id: 1
|
9
|
+
name: generic
|
10
|
+
template: generic
|
11
|
+
created_at: <%=Time.now%>
|
12
|
+
updated_at: <%=Time.now%>
|
13
|
+
|
14
|
+
revs:
|
15
|
+
id: 2
|
16
|
+
name: revs
|
17
|
+
template: revs
|
18
|
+
created_at: <%=Time.now%>
|
19
|
+
updated_at: <%=Time.now%>
|
20
|
+
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# Configure Rails Environment
|
2
|
+
ENV["RAILS_ENV"] = "test"
|
3
|
+
|
4
|
+
require File.expand_path("../spec/dummy/config/environment.rb", __FILE__)
|
5
|
+
require "rails/test_help"
|
6
|
+
|
7
|
+
Rails.backtrace_cleaner.remove_silencers!
|
8
|
+
|
9
|
+
# Load support files
|
10
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
|
11
|
+
|
12
|
+
# Load fixtures from the engine
|
13
|
+
if ActiveSupport::TestCase.method_defined?(:fixture_path=)
|
14
|
+
ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__)
|
15
|
+
end
|
metadata
ADDED
@@ -0,0 +1,212 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: editstore
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.1.5
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Peter Mangiafico
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-10-17 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rails
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 3.2.13
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 3.2.13
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: druid-tools
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.2.0
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.2.0
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: sqlite3
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: lyberteam-devel
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 1.0.1
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 1.0.1
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: lyberteam-gems-devel
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>'
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 1.0.0
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>'
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 1.0.0
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: rspec
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: rspec-rails
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
description: The editstore gem is a Rails engine, including all models and migrations
|
127
|
+
needed to connect to the editstore database for caching descriptive metadata edits.
|
128
|
+
email:
|
129
|
+
- pmangiafico@stanford.edu
|
130
|
+
executables: []
|
131
|
+
extensions: []
|
132
|
+
extra_rdoc_files: []
|
133
|
+
files:
|
134
|
+
- app/assets/javascripts/editstore/application.js
|
135
|
+
- app/assets/stylesheets/editstore/application.css
|
136
|
+
- app/controllers/editstore/application_controller.rb
|
137
|
+
- app/helpers/editstore/application_helper.rb
|
138
|
+
- app/models/editstore/change.rb
|
139
|
+
- app/models/editstore/connection.rb
|
140
|
+
- app/models/editstore/field.rb
|
141
|
+
- app/models/editstore/object_lock.rb
|
142
|
+
- app/models/editstore/project.rb
|
143
|
+
- app/models/editstore/run_log.rb
|
144
|
+
- app/models/editstore/state.rb
|
145
|
+
- app/views/layouts/editstore/application.html.erb
|
146
|
+
- config/routes.rb
|
147
|
+
- db/migrate/20130710234514_create_editstore_projects.rb
|
148
|
+
- db/migrate/20130711172833_create_editstore_changes.rb
|
149
|
+
- db/migrate/20130711173100_create_editstore_states.rb
|
150
|
+
- db/migrate/20130711211606_create_editstore_fields.rb
|
151
|
+
- db/migrate/20130814221827_add_error_to_change.rb
|
152
|
+
- db/migrate/20131004224346_add_pending_to_change.rb
|
153
|
+
- db/migrate/20131015203809_create_object_lock.rb
|
154
|
+
- db/migrate/20131015205757_add_indices.rb
|
155
|
+
- db/migrate/20131016183057_create_run_log.rb
|
156
|
+
- db/migrate/20131017165120_add_locking_version.rb
|
157
|
+
- lib/editstore/engine.rb
|
158
|
+
- lib/editstore/version.rb
|
159
|
+
- lib/editstore.rb
|
160
|
+
- lib/tasks/editstore_tasks.rake
|
161
|
+
- lib/validators/is_druid_validator.rb
|
162
|
+
- MIT-LICENSE
|
163
|
+
- Rakefile
|
164
|
+
- README.rdoc
|
165
|
+
- test/editstore_test.rb
|
166
|
+
- test/fixtures/editstore/editstore_fields.yml
|
167
|
+
- test/fixtures/editstore/editstore_projects.yml
|
168
|
+
- test/test_helper.rb
|
169
|
+
- test/unit/editstore/change_test.rb
|
170
|
+
- test/unit/editstore/field_test.rb
|
171
|
+
- test/unit/editstore/project_test.rb
|
172
|
+
- test/unit/editstore/state_test.rb
|
173
|
+
homepage: ''
|
174
|
+
licenses: []
|
175
|
+
post_install_message:
|
176
|
+
rdoc_options: []
|
177
|
+
require_paths:
|
178
|
+
- lib
|
179
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
180
|
+
none: false
|
181
|
+
requirements:
|
182
|
+
- - ! '>='
|
183
|
+
- !ruby/object:Gem::Version
|
184
|
+
version: '0'
|
185
|
+
segments:
|
186
|
+
- 0
|
187
|
+
hash: 4522800769409732582
|
188
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
189
|
+
none: false
|
190
|
+
requirements:
|
191
|
+
- - ! '>='
|
192
|
+
- !ruby/object:Gem::Version
|
193
|
+
version: '0'
|
194
|
+
segments:
|
195
|
+
- 0
|
196
|
+
hash: 4522800769409732582
|
197
|
+
requirements: []
|
198
|
+
rubyforge_project:
|
199
|
+
rubygems_version: 1.8.24
|
200
|
+
signing_key:
|
201
|
+
specification_version: 3
|
202
|
+
summary: The editstore gem is used by client web applications (like Revs Digital Library)
|
203
|
+
to stage changes to descriptive metadata in DOR
|
204
|
+
test_files:
|
205
|
+
- test/editstore_test.rb
|
206
|
+
- test/fixtures/editstore/editstore_fields.yml
|
207
|
+
- test/fixtures/editstore/editstore_projects.yml
|
208
|
+
- test/test_helper.rb
|
209
|
+
- test/unit/editstore/change_test.rb
|
210
|
+
- test/unit/editstore/field_test.rb
|
211
|
+
- test/unit/editstore/project_test.rb
|
212
|
+
- test/unit/editstore/state_test.rb
|