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