editstore 1.1.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.rdoc +182 -0
  3. data/Rakefile +44 -0
  4. data/app/assets/javascripts/editstore/application.js +15 -0
  5. data/app/assets/stylesheets/editstore/application.css +13 -0
  6. data/app/controllers/editstore/application_controller.rb +4 -0
  7. data/app/helpers/editstore/application_helper.rb +4 -0
  8. data/app/models/editstore/change.rb +119 -0
  9. data/app/models/editstore/connection.rb +6 -0
  10. data/app/models/editstore/field.rb +6 -0
  11. data/app/models/editstore/object_lock.rb +83 -0
  12. data/app/models/editstore/project.rb +9 -0
  13. data/app/models/editstore/run_log.rb +10 -0
  14. data/app/models/editstore/state.rb +36 -0
  15. data/app/views/layouts/editstore/application.html.erb +14 -0
  16. data/config/routes.rb +2 -0
  17. data/db/migrate/20130710234514_create_editstore_projects.rb +15 -0
  18. data/db/migrate/20130711172833_create_editstore_changes.rb +18 -0
  19. data/db/migrate/20130711173100_create_editstore_states.rb +17 -0
  20. data/db/migrate/20130711211606_create_editstore_fields.rb +14 -0
  21. data/db/migrate/20130814221827_add_error_to_change.rb +8 -0
  22. data/db/migrate/20131004224346_add_pending_to_change.rb +8 -0
  23. data/db/migrate/20131015203809_create_object_lock.rb +12 -0
  24. data/db/migrate/20131015205757_add_indices.rb +16 -0
  25. data/db/migrate/20131016183057_create_run_log.rb +17 -0
  26. data/db/migrate/20131017165120_add_locking_version.rb +8 -0
  27. data/lib/editstore.rb +7 -0
  28. data/lib/editstore/engine.rb +8 -0
  29. data/lib/editstore/version.rb +3 -0
  30. data/lib/tasks/editstore_tasks.rake +33 -0
  31. data/lib/validators/is_druid_validator.rb +8 -0
  32. data/test/editstore_test.rb +7 -0
  33. data/test/fixtures/editstore/editstore_fields.yml +23 -0
  34. data/test/fixtures/editstore/editstore_projects.yml +20 -0
  35. data/test/test_helper.rb +15 -0
  36. data/test/unit/editstore/change_test.rb +9 -0
  37. data/test/unit/editstore/field_test.rb +9 -0
  38. data/test/unit/editstore/project_test.rb +9 -0
  39. data/test/unit/editstore/state_test.rb +9 -0
  40. 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,4 @@
1
+ module Editstore
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Editstore
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -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,6 @@
1
+ module Editstore
2
+ class Connection < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ establish_connection "editstore_#{Rails.env}"
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Editstore
2
+ class Field < Connection
3
+ belongs_to :project
4
+ attr_accessible :project_id,:name
5
+ end
6
+ 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,9 @@
1
+ module Editstore
2
+ class Project < Connection
3
+ has_many :changes, :dependent => :destroy
4
+ has_many :fields, :dependent => :destroy
5
+ attr_accessible :name, :template
6
+ validates :name, presence: true
7
+ validates :template, presence: true
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ module Editstore
2
+ class RunLog < Connection
3
+ attr_accessible :total_druids,:total_changes,:num_errors,:num_pending,:note
4
+ def self.prune
5
+ self.where('updated_at < ?',1.month.ago).each {|obj| obj.destroy}
6
+ end
7
+ end
8
+ end
9
+
10
+
@@ -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,2 @@
1
+ Editstore::Engine.routes.draw do
2
+ end
@@ -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,8 @@
1
+ class AddErrorToChange < ActiveRecord::Migration
2
+ def change
3
+ if Editstore.run_migrations?
4
+ @connection=Editstore::Connection.connection
5
+ add_column :editstore_changes, :error, :string
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ class AddPendingToChange < ActiveRecord::Migration
2
+ def change
3
+ if Editstore.run_migrations?
4
+ @connection=Editstore::Connection.connection
5
+ add_column :editstore_changes, :pending, :boolean, :default=>false
6
+ end
7
+ end
8
+ 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
@@ -0,0 +1,8 @@
1
+ class AddLockingVersion < ActiveRecord::Migration
2
+ def change
3
+ if Editstore.run_migrations?
4
+ @connection=Editstore::Connection.connection
5
+ add_column :editstore_object_locks, :lock_version, :string
6
+ end
7
+ end
8
+ end
data/lib/editstore.rb ADDED
@@ -0,0 +1,7 @@
1
+ require "editstore/engine"
2
+
3
+ module Editstore
4
+ def self.run_migrations?
5
+ %w{development test}.include? Rails.env
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ module Editstore
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Editstore
4
+ config.generators do |g|
5
+ g.test_framework :rspec, :view_specs => false
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,3 @@
1
+ module Editstore
2
+ VERSION = "1.1.5"
3
+ end
@@ -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,8 @@
1
+ class IsDruidValidator < ActiveModel::EachValidator
2
+
3
+ def validate_each(record, attribute, value)
4
+ return if DruidTools::Druid.valid?(value)
5
+ record.errors[attribute] << (options[:message] || "is not a valid druid")
6
+ end
7
+
8
+ end
@@ -0,0 +1,7 @@
1
+ require 'test_helper'
2
+
3
+ class EditstoreTest < ActiveSupport::TestCase
4
+ test "truth" do
5
+ assert_kind_of Module, Editstore
6
+ end
7
+ 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
+
@@ -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
@@ -0,0 +1,9 @@
1
+ require 'test_helper'
2
+
3
+ module Editstore
4
+ class ChangeTest < ActiveSupport::TestCase
5
+ # test "the truth" do
6
+ # assert true
7
+ # end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ require 'test_helper'
2
+
3
+ module Editstore
4
+ class FieldTest < ActiveSupport::TestCase
5
+ # test "the truth" do
6
+ # assert true
7
+ # end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ require 'test_helper'
2
+
3
+ module Editstore
4
+ class ProjectTest < ActiveSupport::TestCase
5
+ # test "the truth" do
6
+ # assert true
7
+ # end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ require 'test_helper'
2
+
3
+ module Editstore
4
+ class StateTest < ActiveSupport::TestCase
5
+ # test "the truth" do
6
+ # assert true
7
+ # end
8
+ end
9
+ 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