jakewendt-documents 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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