editstore 1.1.5
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.
- 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
|