jakewendt-documents 0.2.0

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 (32) hide show
  1. data/README.rdoc +72 -0
  2. data/app/controllers/documents_controller.rb +97 -0
  3. data/app/models/document.rb +28 -0
  4. data/app/views/documents/_document.html.erb +15 -0
  5. data/app/views/documents/_form.html.erb +19 -0
  6. data/app/views/documents/edit.html.erb +4 -0
  7. data/app/views/documents/index.html.erb +11 -0
  8. data/app/views/documents/new.html.erb +3 -0
  9. data/app/views/documents/preview.html.erb +15 -0
  10. data/config/document.yml +54 -0
  11. data/config/routes.rb +5 -0
  12. data/generators/documents/USAGE +0 -0
  13. data/generators/documents/documents_generator.rb +69 -0
  14. data/generators/documents/templates/functional/documents_controller_test.rb +219 -0
  15. data/generators/documents/templates/javascripts/documents.js +0 -0
  16. data/generators/documents/templates/migrations/add_attachments_document_to_document.rb +19 -0
  17. data/generators/documents/templates/migrations/create_documents.rb +19 -0
  18. data/generators/documents/templates/migrations/polymorphicize_document_owner.rb +13 -0
  19. data/generators/documents/templates/stylesheets/documents.css +0 -0
  20. data/generators/documents/templates/unit/document_test.rb +115 -0
  21. data/lib/documents.rb +54 -0
  22. data/lib/documents/factories.rb +4 -0
  23. data/lib/documents/file_utils_extension.rb +18 -0
  24. data/lib/documents/owner.rb +12 -0
  25. data/lib/documents/pending.rb +72 -0
  26. data/lib/documents/tasks.rb +1 -0
  27. data/lib/tasks/application.rake +40 -0
  28. data/lib/tasks/database.rake +52 -0
  29. data/lib/tasks/documentation.rake +68 -0
  30. data/lib/tasks/rcov.rake +41 -0
  31. data/lib/tasks/ucb_ccls_engine_tasks.rake +50 -0
  32. metadata +364 -0
data/README.rdoc ADDED
@@ -0,0 +1,72 @@
1
+ = Documents
2
+
3
+ == ToDo
4
+
5
+
6
+
7
+ == Required Gem Sources
8
+
9
+ gem sources -a http://rubygems.org
10
+ gem sources -a http://gems.github.com
11
+
12
+
13
+ == Required Gems
14
+
15
+ * {assert_this_and_that}[http://github.com/jakewendt/assert_this_and_that]
16
+ * {ruby_extension}[http://github.com/jakewendt/ruby_extension] - modifications, updates and patches for ruby.
17
+ * rails ~> 2
18
+ * i18n =0.3.7
19
+ * chronic
20
+ * ruby-hmac
21
+ * aws-s3
22
+ * ssl_requirement
23
+ * ryanb-acts-as-list
24
+ * RedCloth
25
+ * paperclip
26
+ * thoughtbot-factory_girl
27
+ * jakewendt-rails_helpers
28
+ * jakewendt-ruby_extension
29
+ * jakewendt-assert_this_and_that
30
+ * jakewendt-calnet_authenticated
31
+
32
+
33
+ == Installation and Usage
34
+
35
+ cp config/s3.yml.example config/s3.yml
36
+ # Add your own s3 access keys. Leave 'test' as it is.
37
+
38
+
39
+ config.gem 'jakewendt-documents',
40
+ :lib => 'documents',
41
+ :source => 'http://rubygems.org'
42
+
43
+
44
+ class User (or whatever)
45
+ document_owner
46
+ end
47
+
48
+
49
+ == Testing (as an app)
50
+
51
+ rake db:migrate
52
+ rake db:fixtures:load
53
+ rake test
54
+ script/server
55
+
56
+
57
+ == Gemified with Jeweler
58
+
59
+ vi Rakefile
60
+ rake version:write
61
+
62
+ rake version:bump:patch
63
+ rake version:bump:minor
64
+ rake version:bump:major
65
+
66
+ rake gemspec
67
+
68
+ rake install
69
+ rake release
70
+
71
+
72
+ Copyright (c) 2010 [Jake Wendt], released under the MIT license
@@ -0,0 +1,97 @@
1
+ class DocumentsController < ApplicationController
2
+
3
+ before_filter :may_maintain_pages_required
4
+ before_filter :document_required, :only => :show
5
+ before_filter :id_required,
6
+ :only => [ :edit, :update, :destroy, :preview ]
7
+
8
+ def show
9
+ if @document.document.path.blank?
10
+ flash[:error] = "Does not contain a document"
11
+ redirect_to preview_document_path(@document)
12
+ elsif @document.document.exists?
13
+ #puts "Document exists!"
14
+ if @document.document.options[:storage] == :filesystem # &&
15
+ # File.exists?(@document.document.path)
16
+ # basically development or non-s3 setup
17
+ send_file @document.document.path
18
+ else
19
+ #puts "Storage is S3?"
20
+
21
+ # Privacy filters are still not active
22
+ # basically a private s3 file
23
+ # redirect_to @document.s3_url
24
+ redirect_to @document.document.expiring_url
25
+
26
+ end
27
+ else
28
+ flash[:error] = "Document does not exist at the expected location."
29
+ redirect_to preview_document_path(@document)
30
+ end
31
+ end
32
+
33
+ def preview
34
+ # # otherwise looks for template for pdf, jpg or whatever
35
+ # params[:format] = 'html'
36
+ end
37
+
38
+ def index
39
+ @documents = Document.all
40
+ end
41
+
42
+ def new
43
+ @document = Document.new
44
+ end
45
+
46
+ def create
47
+ @document = Document.new(params[:document])
48
+ @document.save!
49
+ redirect_to preview_document_path(@document)
50
+ rescue ActiveRecord::RecordInvalid
51
+ flash.now[:error] = "Error"
52
+ render :action => 'new'
53
+ end
54
+
55
+ def update
56
+ @document.update_attributes!(params[:document])
57
+ redirect_to preview_document_path(@document)
58
+ rescue ActiveRecord::RecordInvalid
59
+ flash.now[:error] = "Error"
60
+ render :action => 'edit'
61
+ end
62
+
63
+ def destroy
64
+ @document.destroy
65
+ redirect_to documents_path
66
+ end
67
+
68
+ protected
69
+
70
+ def id_required
71
+ if !params[:id].blank? and Document.exists?(params[:id])
72
+ @document = Document.find(params[:id])
73
+ else
74
+ access_denied("Valid document id required!", documents_path)
75
+ end
76
+ end
77
+
78
+ def document_required
79
+ if !params[:id].blank? and Document.exists?(params[:id])
80
+ @document = Document.find(params[:id])
81
+ elsif !params[:id].blank? and Document.exists?(
82
+ :document_file_name => "#{params[:id]}.#{params[:format]}")
83
+ documents = Document.find(:all, :conditions => {
84
+ :document_file_name => "#{params[:id]}.#{params[:format]}"})
85
+ # Due to the unique index, there can be only one!
86
+ # if documents.length > 1
87
+ # access_denied("More than one document matches #{params[:id]}!",
88
+ # documents_path)
89
+ # else
90
+ @document=documents[0]
91
+ # end
92
+ else
93
+ access_denied("Valid document id required!", documents_path)
94
+ end
95
+ end
96
+
97
+ end
@@ -0,0 +1,28 @@
1
+ require 'hmac-sha1'
2
+ #
3
+ # http://amazon.rubyforge.org/
4
+ #
5
+ class Document < ActiveRecord::Base
6
+ belongs_to :owner, :polymorphic => true
7
+
8
+ validates_presence_of :title
9
+ validates_length_of :title, :minimum => 4
10
+
11
+ validates_uniqueness_of :document_file_name, :allow_nil => true
12
+
13
+ before_validation :nullify_blank_document_file_name
14
+
15
+ has_attached_file :document,
16
+ YAML::load(ERB.new(IO.read(File.expand_path(
17
+ File.join(File.dirname(__FILE__),'../..','config/document.yml')
18
+ ))).result)[Rails.env]
19
+
20
+ def nullify_blank_document_file_name
21
+ self.document_file_name = nil if document_file_name.blank?
22
+ end
23
+
24
+ def to_s
25
+ title
26
+ end
27
+
28
+ end
@@ -0,0 +1,15 @@
1
+ <% content_tag_for( :tr, document, :class => 'row' ) do %>
2
+ <td class='title'>
3
+ <%= link_to document.title, preview_document_path(document) %>
4
+ </td>
5
+ <td class='path'>
6
+ <%= document.document_file_name %>
7
+ </td>
8
+ <td class='manage'>
9
+ <%= button_link_to 'Edit', edit_document_path(document) %>&nbsp;
10
+ <% destroy_link_to 'Destroy', document_path(document) do %>
11
+ <%= hidden_field_tag 'confirm', "Destroy document '#{document}'?",
12
+ :id => nil %>
13
+ <% end %>
14
+ </td>
15
+ <% end %><!-- class='document row' -->
@@ -0,0 +1,19 @@
1
+ <%# stylesheets('document') %>
2
+
3
+ <% form_for(@document,:html => { :multipart => true }) do |f| %>
4
+ <%= f.error_messages %>
5
+
6
+ <%= f.wrapped_text_field :title %>
7
+
8
+ <div>
9
+ <div class='document'>
10
+ <%= f.label :document -%>
11
+ <% unless @document.new_record? -%>
12
+ (<%= @document.document.url -%>)
13
+ <% end -%><br />
14
+ <%= f.file_field :document %>
15
+ </div>
16
+ </div>
17
+ <%= f.wrapped_text_area :abstract %>
18
+ <p><%= f.submit( (@document.new_record?)? "Create" : "Update" )%></p>
19
+ <% end %>
@@ -0,0 +1,4 @@
1
+ <h1>Editing Document</h1>
2
+ <%= render 'form' %>
3
+ <%= link_to 'Show', @document %> |
4
+ <%= link_to 'Back', documents_path %>
@@ -0,0 +1,11 @@
1
+ <% stylesheets('documents') %>
2
+
3
+ <% if @documents.length > 0 %>
4
+ <table id='documents'><tbody>
5
+ <%= render :partial => 'document', :collection => @documents %>
6
+ </tbody></table><!-- id='documents' -->
7
+ <% end %>
8
+
9
+ <p>
10
+ <%= button_link_to 'Create New Document', new_document_path %>
11
+ </p>
@@ -0,0 +1,3 @@
1
+ <h1>New Document</h1>
2
+ <%= render 'form' %>
3
+ <%= link_to 'Back', documents_path %>
@@ -0,0 +1,15 @@
1
+ <%# stylesheets('document') %>
2
+
3
+ <p>
4
+ <b>Title:</b>
5
+ <%=h @document.title %>
6
+ </p>
7
+
8
+ <p>
9
+ <b>Abstract:</b>
10
+ <%=h @document.abstract %>
11
+ </p>
12
+
13
+ <%= link_to 'Download', document_path(@document) %> |
14
+ <%= link_to 'Edit', edit_document_path(@document) %> |
15
+ <%= link_to 'Back', documents_path %>
@@ -0,0 +1,54 @@
1
+ #DEFAULTS: &DEFAULTS
2
+ # :styles:
3
+ # :full: '900'
4
+ # :large: '800'
5
+ # :medium: '600'
6
+ # :small: '150x50'
7
+
8
+ # :attachment is the attachment name NOT the model name
9
+ # for document they are the same
10
+
11
+ <% common = "documents/:id/:filename" %>
12
+
13
+ #>> Rails.root.to_s.split('/')
14
+ #=> ["", "var", "lib", "tomcat5", "webapps", "clic", "WEB-INF"]
15
+
16
+ #>> Rails.root.to_s.split('/')
17
+ #=> ["", "Users", "jakewendt", "github_repo", "jakewendt", "ucb_ccls_clic"]
18
+
19
+ <%
20
+ app_name = ( defined?(RAILS_APP_NAME) ) ?
21
+ RAILS_APP_NAME :
22
+ Rails.root.to_s.split('/').reject{|x|x == "WEB-INF"}.last
23
+ %>
24
+
25
+ development:
26
+ :path: <%= "#{Rails.root}/development/#{common}" %>
27
+ # <<: *DEFAULTS
28
+
29
+ test:
30
+ :path: <%= "#{Rails.root}/test/#{common}" %>
31
+ # <<: *DEFAULTS
32
+
33
+ #production:
34
+ # :path: <%= "/home/tomcat/#{app_name}/:attachment/:id/:filename" %>
35
+ ## # <<: *DEFAULTS
36
+
37
+ production:
38
+ # Set the storage class to RRS which is cheaper than
39
+ # the default of STANDARD
40
+ :s3_headers:
41
+ x-amz-storage-class: REDUCED_REDUNDANCY
42
+ # public_read or private
43
+ :s3_permissions: :private
44
+ :storage: :s3
45
+ :s3_protocol: https
46
+ :s3_credentials: <%="#{Rails.root}/config/s3.yml" %>
47
+ :bucket: <%= app_name %>
48
+ # common has a : as the first char so it needs special care
49
+ # or the string will magically be turned into a symbol
50
+ # which isn't what we want
51
+ # Not anymore.
52
+ :path: <%= common %>
53
+ # S3 must have a defined path or will generate
54
+ # "Stack level too deep" errors
data/config/routes.rb ADDED
@@ -0,0 +1,5 @@
1
+ ActionController::Routing::Routes.draw do |map|
2
+
3
+ map.resources :documents, :member => { :preview => :get }
4
+
5
+ end
File without changes
@@ -0,0 +1,69 @@
1
+ class DocumentsGenerator < Rails::Generator::Base
2
+
3
+ def manifest
4
+ # See Rails::Generator::Commands::Create
5
+ # rails-2.3.10/lib/rails_generator/commands.rb
6
+ # for code methods for record (Manifest)
7
+ record do |m|
8
+
9
+ %w( create_documents
10
+ add_attachments_document_to_document
11
+ polymorphicize_document_owner
12
+ ).each do |migration|
13
+ m.migration_template "migrations/#{migration}.rb",
14
+ 'db/migrate', :migration_file_name => migration
15
+ end
16
+
17
+ m.directory('public/javascripts')
18
+ Dir["#{File.dirname(__FILE__)}/templates/javascripts/*js"].each{|file|
19
+ f = file.split('/').slice(-2,2).join('/')
20
+ m.file(f, "public/javascripts/#{File.basename(file)}")
21
+ }
22
+ m.directory('public/stylesheets')
23
+ Dir["#{File.dirname(__FILE__)}/templates/stylesheets/*css"].each{|file|
24
+ f = file.split('/').slice(-2,2).join('/')
25
+ m.file(f, "public/stylesheets/#{File.basename(file)}")
26
+ }
27
+ m.directory('test/functional/documents')
28
+ Dir["#{File.dirname(__FILE__)}/templates/functional/*rb"].each{|file|
29
+ f = file.split('/').slice(-2,2).join('/')
30
+ m.file(f, "test/functional/documents/#{File.basename(file)}")
31
+ }
32
+ m.directory('test/unit/documents')
33
+ Dir["#{File.dirname(__FILE__)}/templates/unit/*rb"].each{|file|
34
+ f = file.split('/').slice(-2,2).join('/')
35
+ m.file(f, "test/unit/documents/#{File.basename(file)}")
36
+ }
37
+ end
38
+ end
39
+
40
+ end
41
+ module Rails::Generator::Commands
42
+ class Create
43
+ def migration_template(relative_source,
44
+ relative_destination, template_options = {})
45
+ migration_directory relative_destination
46
+ migration_file_name = template_options[
47
+ :migration_file_name] || file_name
48
+ if migration_exists?(migration_file_name)
49
+ puts "Another migration is already named #{migration_file_name}: #{existing_migrations(migration_file_name).first}: Skipping"
50
+ else
51
+ template(relative_source, "#{relative_destination}/#{next_migration_string}_#{migration_file_name}.rb", template_options)
52
+ end
53
+ end
54
+ end # Create
55
+ class Base
56
+ protected
57
+ # the loop through migrations happens so fast
58
+ # that they all have the same timestamp which
59
+ # won't work when you actually try to migrate.
60
+ # All the timestamps MUST be unique.
61
+ def next_migration_string(padding = 3)
62
+ @s = (!@s.nil?)? @s.to_i + 1 : if ActiveRecord::Base.timestamped_migrations
63
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
64
+ else
65
+ "%.#{padding}d" % next_migration_number
66
+ end
67
+ end
68
+ end # Base
69
+ end
@@ -0,0 +1,219 @@
1
+ require File.dirname(__FILE__) + '/../../test_helper'
2
+
3
+ class Documents::DocumentsControllerTest < ActionController::TestCase
4
+ tests DocumentsController
5
+
6
+ ASSERT_ACCESS_OPTIONS = {
7
+ :model => 'Document',
8
+ :actions => [:new,:create,:edit,:update,:destroy,:index],
9
+ :method_for_create => :factory_create,
10
+ :attributes_for_create => :factory_attributes
11
+ }
12
+
13
+ def factory_create
14
+ Factory(:document)
15
+ end
16
+ def factory_attributes
17
+ Factory.attributes_for(:document)
18
+ end
19
+
20
+ assert_access_with_https
21
+ assert_access_with_login({
22
+ :logins => [:super_user,:admin,:editor]})
23
+
24
+ assert_no_access_with_http
25
+ assert_no_access_with_login({
26
+ :logins => [:interviewer,:reader,:active_user] })
27
+ assert_no_access_without_login
28
+
29
+ assert_no_access_with_login(
30
+ :attributes_for_create => nil,
31
+ :method_for_create => nil,
32
+ :actions => nil,
33
+ :suffix => " and invalid id",
34
+ :login => :superuser,
35
+ :redirect => :documents_path,
36
+ :edit => { :id => 0 },
37
+ :update => { :id => 0 },
38
+ :destroy => { :id => 0 }
39
+ )
40
+
41
+ %w( super_user admin editor ).each do |cu|
42
+
43
+
44
+ ## still only privacy filter is based on "may_maintain_pages"
45
+ ## which isn't really gonna work
46
+ # test "should get redirect to public s3 document with #{cu} login" do
47
+ # Document.any_instance.stubs(:s3_public?).returns(true)
48
+ # document = Factory(:document, :document_file_name => 'bogus_file_name')
49
+ # assert !File.exists?(document.document.path)
50
+ # login_as send(cu)
51
+ # get :show, :id => document.id
52
+ # assert_redirected_to document.document.url
53
+ # end
54
+ #
55
+ # test "should get redirect to private s3 document with #{cu} login" do
56
+ # Document.any_instance.stubs(:s3_private?).returns(true)
57
+ # document = Factory(:document, :document_file_name => 'bogus_file_name')
58
+ # assert !File.exists?(document.document.path)
59
+ # login_as send(cu)
60
+ # get :show, :id => document.id
61
+ # assert_redirected_to document.s3_url
62
+ # end
63
+
64
+ # Add may_download_document
65
+
66
+ # test "should get redirect to public s3 document with #{cu} login" do
67
+ # Document.any_instance.stubs(:s3_public?).returns(true)
68
+ # document = Factory(:document, :document_file_name => 'bogus_file_name')
69
+ # assert !File.exists?(document.document.path)
70
+ # login_as send(cu)
71
+ # get :show, :id => document.id
72
+ # assert_redirected_to document.document.url
73
+ # end
74
+
75
+ test "should get redirect to private s3 document with #{cu} login" do
76
+ Document.has_attached_file :document, {
77
+ :s3_headers => {
78
+ 'x-amz-storage-class' => 'REDUCED_REDUNDANCY' },
79
+ :s3_permissions => :private,
80
+ :storage => :s3,
81
+ :s3_protocol => 'https',
82
+ :s3_credentials => "#{Rails.root}/config/s3.yml",
83
+ :bucket => 'ccls',
84
+ :path => "documents/:id/:filename"
85
+ }
86
+
87
+ # Since the REAL S3 credentials are only in production
88
+ # Bad credentials make exists? return true????
89
+ Rails.stubs(:env).returns('production')
90
+ document = Factory(:document, :document_file_name => 'bogus_file_name')
91
+ assert !document.document.exists?
92
+ assert !File.exists?(document.document.path)
93
+
94
+ AWS::S3::S3Object.stubs(:exists?).returns(true)
95
+
96
+ login_as send(cu)
97
+ get :show, :id => document.id
98
+ assert_response :redirect
99
+ assert_match %r{\Ahttp(s)?://s3.amazonaws.com/ccls/documents/\d+/bogus_file_name\.\?AWSAccessKeyId=\w+&Expires=\d+&Signature=.+\z}, @response.redirected_to
100
+ end
101
+
102
+
103
+
104
+
105
+
106
+
107
+ test "should NOT download document with nil document and #{cu} login" do
108
+ document = Factory(:document)
109
+ assert document.document.path.blank?
110
+ login_as send(cu)
111
+ get :show, :id => document.id
112
+ assert_redirected_to preview_document_path(document)
113
+ assert_not_nil flash[:error]
114
+ end
115
+
116
+ test "should NOT download document with no document and #{cu} login" do
117
+ document = Factory(:document, :document_file_name => 'bogus_file_name')
118
+ assert !File.exists?(document.document.path)
119
+ login_as send(cu)
120
+ get :show, :id => document.id
121
+ assert_redirected_to preview_document_path(document)
122
+ assert_not_nil flash[:error]
123
+ end
124
+
125
+ test "should NOT download nonexistant document with #{cu} login" do
126
+ assert !File.exists?('some_fake_file_name.doc')
127
+ login_as send(cu)
128
+ get :show, :id => 'some_fake_file_name',:format => 'doc'
129
+ assert_redirected_to documents_path
130
+ assert_not_nil flash[:error]
131
+ end
132
+
133
+ test "should preview document with document and #{cu} login" do
134
+ document = Factory(:document)
135
+ login_as send(cu)
136
+ get :preview, :id => document.id
137
+ assert_response :success
138
+ assert_nil flash[:error]
139
+ end
140
+
141
+ test "should download document by id with document and #{cu} login" do
142
+ document = Document.create!(Factory.attributes_for(:document,
143
+ :document => File.open(File.dirname(__FILE__) +
144
+ '/../../assets/edit_save_wireframe.pdf')))
145
+ login_as send(cu)
146
+ get :show, :id => document.reload.id
147
+ assert_nil flash[:error]
148
+ assert_not_nil @response.headers['Content-disposition'].match(
149
+ /attachment;.*pdf/)
150
+ document.destroy
151
+ end
152
+
153
+ test "should download document by name with document and #{cu} login" do
154
+ document = Document.create!(Factory.attributes_for(:document,
155
+ :document => File.open(File.dirname(__FILE__) +
156
+ '/../../assets/edit_save_wireframe.pdf')))
157
+ login_as send(cu)
158
+ get :show, :id => 'edit_save_wireframe',
159
+ :format => 'pdf'
160
+ assert_nil flash[:error]
161
+ assert_not_nil @response.headers['Content-disposition'].match(
162
+ /attachment;.*pdf/)
163
+ document.destroy
164
+ end
165
+
166
+ test "should NOT create invalid document with #{cu} login" do
167
+ login_as send(cu)
168
+ assert_no_difference('Document.count') do
169
+ post :create, :document => {}
170
+ end
171
+ assert_not_nil flash[:error]
172
+ assert_template 'new'
173
+ assert_response :success
174
+ end
175
+
176
+ test "should NOT update invalid document with #{cu} login" do
177
+ login_as send(cu)
178
+ put :update, :id => Factory(:document).id,
179
+ :document => { :title => "a" }
180
+ assert_not_nil flash[:error]
181
+ assert_template 'edit'
182
+ assert_response :success
183
+ end
184
+
185
+ end
186
+
187
+ %w( interviewer reader active_user ).each do |cu|
188
+
189
+ test "should NOT preview document with #{cu} login" do
190
+ document = Factory(:document)
191
+ login_as send(cu)
192
+ get :preview, :id => document.id
193
+ assert_redirected_to root_path
194
+ assert_not_nil flash[:error]
195
+ end
196
+
197
+ test "should NOT download document with #{cu} login" do
198
+ document = Factory(:document)
199
+ login_as send(cu)
200
+ get :show, :id => document.id
201
+ assert_redirected_to root_path
202
+ assert_not_nil flash[:error]
203
+ end
204
+
205
+ end
206
+
207
+ test "should NOT preview document without login" do
208
+ document = Factory(:document)
209
+ get :preview, :id => document.id
210
+ assert_redirected_to_login
211
+ end
212
+
213
+ test "should NOT download document without login" do
214
+ document = Factory(:document)
215
+ get :show, :id => document.id
216
+ assert_redirected_to_login
217
+ end
218
+
219
+ end