cortex-reaver 0.0.9 → 0.1.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 (77) hide show
  1. data/bin/cortex_reaver +7 -2
  2. data/lib/cortex_reaver.rb +51 -71
  3. data/lib/cortex_reaver/config.rb +23 -7
  4. data/lib/cortex_reaver/controller/admin.rb +6 -8
  5. data/lib/cortex_reaver/controller/comment.rb +17 -17
  6. data/lib/cortex_reaver/controller/config.rb +3 -2
  7. data/lib/cortex_reaver/controller/controller.rb +22 -0
  8. data/lib/cortex_reaver/controller/documentation.rb +1 -3
  9. data/lib/cortex_reaver/controller/journal.rb +13 -12
  10. data/lib/cortex_reaver/controller/main.rb +36 -29
  11. data/lib/cortex_reaver/controller/page.rb +15 -11
  12. data/lib/cortex_reaver/controller/photograph.rb +21 -15
  13. data/lib/cortex_reaver/controller/project.rb +16 -13
  14. data/lib/cortex_reaver/controller/tag.rb +16 -14
  15. data/lib/cortex_reaver/controller/user.rb +11 -13
  16. data/lib/cortex_reaver/helper/attachments.rb +18 -12
  17. data/lib/cortex_reaver/helper/auth.rb +2 -2
  18. data/lib/cortex_reaver/helper/canonical.rb +2 -2
  19. data/lib/cortex_reaver/helper/crud.rb +78 -38
  20. data/lib/cortex_reaver/helper/feeds.rb +2 -5
  21. data/lib/cortex_reaver/helper/form.rb +1 -1
  22. data/lib/cortex_reaver/helper/navigation.rb +1 -1
  23. data/lib/cortex_reaver/helper/photographs.rb +12 -3
  24. data/lib/cortex_reaver/helper/template.rb +37 -0
  25. data/lib/cortex_reaver/{view/blank_layout.rhtml → layout/blank.rhtml} +1 -1
  26. data/lib/cortex_reaver/{view/text_layout.rhtml → layout/text.rhtml} +1 -1
  27. data/lib/cortex_reaver/migrations/013_draft.rb +17 -0
  28. data/lib/cortex_reaver/model/comment.rb +64 -53
  29. data/lib/cortex_reaver/model/journal.rb +23 -21
  30. data/lib/cortex_reaver/model/model.rb +9 -0
  31. data/lib/cortex_reaver/model/page.rb +24 -42
  32. data/lib/cortex_reaver/model/photograph.rb +17 -17
  33. data/lib/cortex_reaver/model/project.rb +21 -18
  34. data/lib/cortex_reaver/model/tag.rb +12 -8
  35. data/lib/cortex_reaver/model/user.rb +79 -41
  36. data/lib/cortex_reaver/public/css/main.css +4 -0
  37. data/lib/cortex_reaver/snippets/numeric.rb +15 -0
  38. data/lib/cortex_reaver/snippets/ramaze/cache/memcached.rb +14 -0
  39. data/lib/cortex_reaver/support/attachments.rb +113 -105
  40. data/lib/cortex_reaver/support/cached_rendering.rb +65 -62
  41. data/lib/cortex_reaver/support/canonical.rb +82 -85
  42. data/lib/cortex_reaver/support/comments.rb +57 -51
  43. data/lib/cortex_reaver/support/cortex_reaver_validation_helpers.rb +13 -0
  44. data/lib/cortex_reaver/support/sequenceable.rb +202 -203
  45. data/lib/cortex_reaver/support/tags.rb +103 -94
  46. data/lib/cortex_reaver/support/timestamps.rb +27 -21
  47. data/lib/cortex_reaver/support/viewable.rb +17 -0
  48. data/lib/cortex_reaver/version.rb +3 -3
  49. data/lib/cortex_reaver/view/admin/index.rhtml +2 -2
  50. data/lib/cortex_reaver/view/comments/comment.rhtml +4 -1
  51. data/lib/cortex_reaver/view/comments/list.rhtml +1 -1
  52. data/lib/cortex_reaver/view/comments/post_form.rhtml +1 -1
  53. data/lib/cortex_reaver/view/journals/form.rhtml +3 -1
  54. data/lib/cortex_reaver/view/journals/journal.rhtml +6 -4
  55. data/lib/cortex_reaver/view/journals/list.rhtml +2 -2
  56. data/lib/cortex_reaver/view/journals/show.rhtml +1 -1
  57. data/lib/cortex_reaver/view/pages/form.rhtml +2 -1
  58. data/lib/cortex_reaver/view/pages/list.rhtml +2 -2
  59. data/lib/cortex_reaver/view/pages/show.rhtml +1 -1
  60. data/lib/cortex_reaver/view/photographs/form.rhtml +7 -3
  61. data/lib/cortex_reaver/view/photographs/list.rhtml +1 -1
  62. data/lib/cortex_reaver/view/photographs/show.rhtml +7 -7
  63. data/lib/cortex_reaver/view/projects/form.rhtml +1 -0
  64. data/lib/cortex_reaver/view/projects/list.rhtml +3 -3
  65. data/lib/cortex_reaver/view/projects/show.rhtml +5 -2
  66. data/lib/cortex_reaver/view/tags/list.rhtml +6 -2
  67. data/lib/cortex_reaver/view/tags/show.rhtml +10 -5
  68. data/lib/cortex_reaver/view/users/form.rhtml +1 -1
  69. data/lib/cortex_reaver/view/users/list.rhtml +5 -2
  70. data/lib/cortex_reaver/view/users/login.rhtml +1 -1
  71. data/lib/cortex_reaver/view/users/show.rhtml +5 -1
  72. metadata +159 -149
  73. data/lib/cortex_reaver/public/dispatch.fcgi +0 -11
  74. data/lib/cortex_reaver/snippets/ramaze/dispatcher/file.rb +0 -37
  75. data/lib/cortex_reaver/support/pagination.rb +0 -38
  76. data/lib/cortex_reaver/view/error.rhtml +0 -72
  77. data/lib/cortex_reaver/view/photographs/short.rhtml +0 -3
@@ -1,6 +1,6 @@
1
1
  module CortexReaver
2
2
  class Tag < Sequel::Model(:tags)
3
- include CortexReaver::Model::Canonical
3
+ plugin :canonical
4
4
 
5
5
  many_to_many :photographs, :class => 'CortexReaver::Photograph'
6
6
  many_to_many :journals, :class => 'CortexReaver::Journal'
@@ -9,20 +9,24 @@ module CortexReaver
9
9
 
10
10
  subset :unused, :count => 0
11
11
 
12
- validates do
13
- uniqueness_of :name
14
- presence_of :name
15
- length_of :name, :maximum => 255
16
- presence_of :title
17
- length_of :title, :maximum => 255
12
+ def validate
13
+ validates_unique :name
14
+ validates_presence :name
15
+ validates_max_length 255, :name
16
+ validates_presence :title
17
+ validates_max_length 255, :title
18
18
  end
19
19
 
20
20
  # When we delete a tag, ensure nothing else is linked to it.
21
- before_destroy(:drop_associations) do
21
+ def before_destroy
22
+ return false if super == false
23
+
22
24
  remove_all_photographs
23
25
  remove_all_journals
24
26
  remove_all_projects
25
27
  remove_all_pages
28
+
29
+ true
26
30
  end
27
31
 
28
32
  # Autocompletes a tag. Returns an array of matching candidates
@@ -2,47 +2,28 @@ require 'digest/sha2'
2
2
 
3
3
  module CortexReaver
4
4
  class User < Sequel::Model(:users)
5
- include CortexReaver::Model::Timestamps
6
- include CortexReaver::Model::Sequenceable
7
-
8
- has_many :created_comments, :key => 'created_by', :class => 'CortexReaver::Comment'
9
- has_many :created_journals, :key => 'created_by', :class => 'CortexReaver::Journal'
10
- has_many :created_pages, :key => 'created_by', :class => 'CortexReaver::Page'
11
- has_many :created_photographs, :key => 'created_by', :class => 'CortexReaver::Photograph'
12
- has_many :created_projects, :key => 'created_by', :class => 'CortexReaver::Project'
13
- has_many :updated_comments, :key => 'updated_by', :class => 'CortexReaver::Comment'
14
- has_many :updated_journals, :key => 'updated_by', :class => 'CortexReaver::Journal'
15
- has_many :updated_pages, :key => 'updated_by', :class => 'CortexReaver::Page'
16
- has_many :updated_photographs, :key => 'updated_by', :class => 'CortexReaver::Photograph'
17
- has_many :updated_projects, :key => 'updated_by', :class => 'CortexReaver::Project'
18
-
19
- validates do
20
- uniqueness_of :login
21
- length_of :login, :with => 5..255, :allow_blank => true
22
- format_of :login, :with => /^[A-Za-z0-9\-_]+$/
23
- length_of :name, :maximum => 255
24
- length_of :http, :allow_blank => true, :maximum => 255
25
- uniqueness_of :email
26
- length_of :email, :allow_blank => true, :maximum => 255
27
- format_of :email,
28
- :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/, :allow_blank => true
29
- confirmation_of :password, :allow_nil => true#, :allow_false => true
30
- each(:password_length, :tag => :password_length) do |object, attribute, value|
31
- unless value.nil? or (8..255) === value
32
- object.errors['password'] << 'must be between 8 and 255 characters.'
33
- end
34
- end
35
- end
5
+ plugin :timestamps
6
+ plugin :sequenceable
7
+
8
+ one_to_many :created_comments, :key => 'created_by', :class => 'CortexReaver::Comment'
9
+ one_to_many :created_journals, :key => 'created_by', :class => 'CortexReaver::Journal'
10
+ one_to_many :created_pages, :key => 'created_by', :class => 'CortexReaver::Page'
11
+ one_to_many :created_photographs, :key => 'created_by', :class => 'CortexReaver::Photograph'
12
+ one_to_many :created_projects, :key => 'created_by', :class => 'CortexReaver::Project'
13
+ one_to_many :updated_comments, :key => 'updated_by', :class => 'CortexReaver::Comment'
14
+ one_to_many :updated_journals, :key => 'updated_by', :class => 'CortexReaver::Journal'
15
+ one_to_many :updated_pages, :key => 'updated_by', :class => 'CortexReaver::Page'
16
+ one_to_many :updated_photographs, :key => 'updated_by', :class => 'CortexReaver::Photograph'
17
+ one_to_many :updated_projects, :key => 'updated_by', :class => 'CortexReaver::Project'
36
18
 
37
- # Ensure an administrator is always available.
38
- validates_each :admin do |object, attribute, value|
39
- if admins = User.filter(:admin => true) and admins.size == 1 and admins.first.id == self.id and not value
40
- object.errors[attribute] << "can't be unset; only one administrator left!"
41
- end
42
- end
43
19
 
44
20
  self.window_size = 64
45
21
 
22
+ # Is this the special anonymous user?
23
+ def anonymous?
24
+ false
25
+ end
26
+
46
27
  # Returns an authenticated user by login and password, or nil.
47
28
  def self.authenticate(login, password)
48
29
  user = self[:login => login]
@@ -60,6 +41,8 @@ module CortexReaver
60
41
 
61
42
  # Create anonymous user
62
43
  @anonymous_user = self.new(:name => "Anonymous")
44
+
45
+ # These functions are embedded for speed. Much faster public browsing!
63
46
  def @anonymous_user.can_create? other
64
47
  false
65
48
  end
@@ -69,6 +52,9 @@ module CortexReaver
69
52
  def @anonymous_user.can_delete? other
70
53
  false
71
54
  end
55
+ def @anonymous_user.anonymous?
56
+ true
57
+ end
72
58
 
73
59
  @anonymous_user
74
60
  end
@@ -98,6 +84,16 @@ module CortexReaver
98
84
  end
99
85
  end
100
86
 
87
+ # Ensure that we don't destroy the only admin.
88
+ def before_destroy
89
+ return false if super == false
90
+
91
+ if admins = User.filter(:admin => true) and admins.count == 1 and admins.first.id == self.id
92
+ self.errors.add nil, "Can't destroy the only administrator."
93
+ return false
94
+ end
95
+ end
96
+
101
97
  def can_create?(other)
102
98
  if admin?
103
99
  # Administrators may create anything
@@ -157,6 +153,22 @@ module CortexReaver
157
153
  end
158
154
  end
159
155
 
156
+ def can_view?(other)
157
+ if other.respond_to? :draft and other.draft
158
+ # Draft
159
+ if admin? or can_edit? other
160
+ # User can edit this draft
161
+ true
162
+ else
163
+ # Nope, not yet!
164
+ false
165
+ end
166
+ else
167
+ # Not a draft
168
+ true
169
+ end
170
+ end
171
+
160
172
  # Returns true if user is a contributor
161
173
  def contributor?
162
174
  self.contributor
@@ -172,11 +184,17 @@ module CortexReaver
172
184
  self.moderator
173
185
  end
174
186
 
187
+ # Name falls back to login if blank
188
+ def name
189
+ name = self[:name]
190
+ name.blank? ? login : name
191
+ end
192
+
175
193
  # Set user password
176
194
  def password=(password)
177
195
  self.salt ||= self.class.new_salt
178
196
  self[:password] = self.class.crypt(password, self.salt)
179
- @password_length = password.length
197
+ @password_length = '*' * password.length
180
198
  end
181
199
 
182
200
  # Password confirmation
@@ -211,6 +229,25 @@ module CortexReaver
211
229
  '/users/show/' + login
212
230
  end
213
231
 
232
+ def validate
233
+ validates_unique(:login, :message => "Already taken.")
234
+ validates_max_length(255, :login, :message => "Please enter a username shorter than 255 characters.")
235
+ validates_format(/^[A-Za-z0-9\-_]+$/, :login, :message => "Logins can only contain alphanumeric characters, dashes, and underscores.")
236
+ validates_max_length(255, :name, :allow_blank => true, :message => "Please enter a name shorter than 255 characters.")
237
+ validates_max_length(255, :http, :allow_blank => true, :message => "Please enter an HTTP address shorter than 255 characters.")
238
+ validates_unique(:email, :message => "Already taken.")
239
+ validates_max_length(255, :email, :message => "Please enter an email address shorter than 255 characters.")
240
+ validates_format(/^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/, :email, :message => "Please enter a valid email address.")
241
+ validates_confirmation(:password, :message => "Make sure your passwords match.")
242
+ validates_min_length(8, :password_length, :message => "Passwords must be at least 8 characters.", :allow_nil => true)
243
+ validates_max_length(255, :password_length, :message => "Passwords must be at most 255 characters.", :allow_nil => true)
244
+
245
+ # Ensure an administrator is always available.
246
+ if admins = User.filter(:admin => true) and admins.count == 1 and admins.first.id == self.id and not admin?
247
+ errors[:admin] << "can't be unset; only one administrator left!"
248
+ end
249
+ end
250
+
214
251
  private
215
252
  # Valid characters for salt
216
253
  SALT_CHARS = ('0'..'9').to_a + ('a'..'z').to_a + ('A'..'Z').to_a
@@ -232,14 +269,15 @@ module CortexReaver
232
269
  public
233
270
 
234
271
  # Create default user if none exist
235
- if table_exists? and count == 0
272
+ if dataset.table_exists? and count == 0
236
273
  u = User.new(
237
274
  :login => 'shodan',
238
275
  :name => 'Shodan',
276
+ :email => 'shodan@localhost.localdomain',
239
277
  :admin => true
240
278
  )
241
- u.password = 'shodan'
242
- u.save!
279
+ u.password = 'citadelstation'
280
+ u.save
243
281
  end
244
282
  end
245
283
  end
@@ -248,6 +248,10 @@ th {
248
248
  display: none;
249
249
  }
250
250
 
251
+ .draft, .draft h2 a {
252
+ color: #777;
253
+ }
254
+
251
255
  .current {
252
256
  font-weight: bolder;
253
257
  }
@@ -0,0 +1,15 @@
1
+ class Numeric
2
+ # Returns a -th, -nd, ... string
3
+ def ordinal
4
+ if (11..13).include?(self.to_i % 100)
5
+ "#{self}th"
6
+ else
7
+ case self.to_i % 10
8
+ when 1; "#{self}st"
9
+ when 2; "#{self}nd"
10
+ when 3; "#{self}rd"
11
+ else "#{self}th"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ module Ramaze
2
+ class MemcachedCache
3
+ # I can't figure out a good way to specify the parameters Ramaze::Cache
4
+ # uses when instantiating new caches, so I'm just going to override the
5
+ # setup here. Innate hardcodes the app name as "pristine". :(
6
+ def cache_setup(host, user, app, name)
7
+ app = Ramaze.options.app.name
8
+ @namespace = [host, user, app, name].compact.join('-')
9
+ options = {:namespace => @namespace}.merge(OPTIONS)
10
+ servers = options.delete(:servers)
11
+ @store = ::MemCache.new(servers, options)
12
+ end
13
+ end
14
+ end
@@ -1,25 +1,12 @@
1
1
  require 'fileutils'
2
2
 
3
- module CortexReaver
4
- module Model
3
+ module Sequel
4
+ module Plugins
5
5
  module Attachments
6
-
7
6
  # Attachments allows records to manipulate files associated with them.
8
7
  #
9
8
  # For our purposes, an attachment represents an object that is associated
10
- # with a single parent object. A file represents a file on disk. Attachments
11
- # closely wrap files, but shouldn't be confused with them.
12
-
13
- def self.included(base)
14
- base.class_eval do
15
- # When we delete a record with attachments, delete the attachments first.
16
- before_delete(:delete_attachments) do
17
- attachments.each do |attachment|
18
- attachment.delete
19
- end
20
- end
21
- end
22
- end
9
+ # with a single parent object. A file represents a file on disk.
23
10
 
24
11
  # The separator used for public (e.g. HTTP URL) paths.
25
12
  PUBLIC_PATH_SEPARATOR = '/'
@@ -27,8 +14,100 @@ module CortexReaver
27
14
  # How many bytes to read at a time when saving files.
28
15
  DEFAULT_ATTACHMENT_DIRECTORY_MODE = 0755
29
16
 
17
+ module InstanceMethods
18
+ # When we delete a record with attachments, delete the attachments
19
+ # first.
20
+ def before_delete
21
+ return false if super == false
22
+ attachments.each do |attachment|
23
+ attachment.delete
24
+ end
25
+
26
+ true
27
+ end
28
+
29
+ # Returns a named attachment
30
+ def attachment(name)
31
+ Attachment.new(self, name)
32
+ end
33
+
34
+ # Returns the directory which contains attachments for this record.
35
+ # Forces a save of the record if an ID does not exist.
36
+ def attachment_path(type = :local, force_save = true)
37
+ sep = ''
38
+ case type
39
+ when :local
40
+ # We're interested in a local path on disk.
41
+ sep = File::SEPARATOR
42
+ path = CortexReaver.config.public_root.dup
43
+ when :public
44
+ # We're interested in a public (e.g. HTTP URL) path.
45
+ sep = PUBLIC_PATH_SEPARATOR
46
+ path = ''
47
+ else
48
+ raise ArgumentError.new('type must be either :local or :public')
49
+ end
50
+
51
+ # If we don't have an ID, save the record to obtain one.
52
+ if force_save and id.nil?
53
+ unless save
54
+ # Save failed!
55
+ return nil
56
+ end
57
+ end
58
+
59
+ # Complete the path.
60
+ path <<
61
+ sep + 'data' +
62
+ sep + self.class.to_s.demodulize.underscore.pluralize +
63
+ sep + self.id.to_s
64
+ end
65
+
66
+ # Returns an array of attachments.
67
+ def attachments
68
+ # Unsaved new records, naturally, have no attachments.
69
+ return [] if new?
70
+
71
+ attachments = Array.new
72
+ if path = local_attachment_path and File.directory? path
73
+ begin
74
+ attachments = Dir.open(path).reject do |name|
75
+ # Don't include dotfiles
76
+ name =~ /^\./
77
+ end
78
+ attachments.collect! do |name|
79
+ Attachment.new self, name
80
+ end
81
+ rescue
82
+ # Couldn't read the directory
83
+ end
84
+ end
85
+ attachments
86
+ end
87
+
88
+ # Ensures the attachment directory exists.
89
+ def create_attachment_directory
90
+ path = local_attachment_path
91
+ unless File.directory? path
92
+ FileUtils.mkdir_p path, :mode => DEFAULT_ATTACHMENT_DIRECTORY_MODE
93
+ end
94
+ end
95
+
96
+ # Returns the local directory which contains attachments for this
97
+ # record.
98
+ def local_attachment_path
99
+ attachment_path :local
100
+ end
101
+
102
+ # Returns the public directory which contains attachments for this
103
+ # record.
104
+ def public_attachment_path
105
+ attachment_path :public
106
+ end
107
+ end
108
+
30
109
  class Attachment
31
- # Attachable::Attachment wraps a file associated with a parent object.
110
+ # Wraps a file associated with a parent object.
32
111
 
33
112
  BUFFER_SIZE = 65536
34
113
  DEFAULT_MODE = 0644
@@ -62,8 +141,8 @@ module CortexReaver
62
141
  end
63
142
 
64
143
  # Returns a File object for this attachment. Optionally specify a mode
65
- # string, which is passed to File.new. Creates the attachment directory if
66
- # necessary.
144
+ # string, which is passed to File.new. Creates the attachment directory
145
+ # if necessary.
67
146
  def file(how = 'r', mode = nil)
68
147
  @parent.create_attachment_directory
69
148
  if mode
@@ -84,9 +163,10 @@ module CortexReaver
84
163
  # :copy copies the file contents using FileUtils.cp
85
164
  # :move moves the file.
86
165
  #
87
- # Ramaze offers us temporary File objects from form uploads. Creating a hard
88
- # link is extremely quick, saves disk space, but also doesn't interfere with
89
- # the temporary file in case someone else wants access to it.
166
+ # Ramaze offers us temporary File objects from form uploads. Creating a
167
+ # hard link is extremely quick, saves disk space, but also doesn't
168
+ # interfere with the temporary file in case someone else wants access
169
+ # to it.
90
170
  def file=(readable, mode = :hard_link)
91
171
  # Create attachment directory if necessary.
92
172
  @parent.create_attachment_directory
@@ -94,11 +174,14 @@ module CortexReaver
94
174
  if readable.respond_to? :path
95
175
  ret = case mode
96
176
  when :hard_link
97
- FileUtils.rm local_path if File.exist? local_path
98
- FileUtils.ln readable.path, local_path
99
- when :soft_link
100
- FileUtils.rm local_path if File.exist? local_path
101
- FileUtils.ln_s readable.path, local_path
177
+ begin
178
+ FileUtils.rm local_path if File.exist? local_path
179
+ FileUtils.ln readable.path, local_path
180
+ rescue
181
+ # Hmm, try copy. Could be a cross-device link, or the FS
182
+ # doesn't support it.
183
+ FileUtils.copy readable.path, local_path
184
+ end
102
185
  when :copy
103
186
  FileUtils.rm local_path if File.exist? local_path
104
187
  FileUtils.copy readable.path, local_path
@@ -106,7 +189,7 @@ module CortexReaver
106
189
  FileUtils.rm local_path if File.exist? local_path
107
190
  FileUtils.move readable.path, local_path
108
191
  else
109
- raise RuntimeError.new("mode must be :hard_link, :soft_link, :copy, or :move--got #{mode.inspect}")
192
+ raise RuntimeError.new("mode must be :hard_link :copy, or :move--got #{mode.inspect}")
110
193
  end
111
194
  reset_permissions
112
195
  ret
@@ -153,83 +236,8 @@ module CortexReaver
153
236
  FileUtils.chmod(DEFAULT_MODE, path(:local))
154
237
  end
155
238
  end
156
-
157
- # Returns a named attachment
158
- def attachment(name)
159
- Attachment.new(self, name)
160
- end
161
-
162
- # Returns the directory which contains attachments for this record. Forces
163
- # a save of the record if an ID does not exist.
164
- def attachment_path(type = :local, force_save = true)
165
- sep = ''
166
- case type
167
- when :local
168
- # We're interested in a local path on disk.
169
- sep = File::SEPARATOR
170
- path = CortexReaver.config[:public_root].dup
171
- when :public
172
- # We're interested in a public (e.g. HTTP URL) path.
173
- sep = PUBLIC_PATH_SEPARATOR
174
- path = ''
175
- else
176
- raise ArgumentError.new('type must be either :local or :public')
177
- end
178
-
179
- # If we don't have an ID, save the record to obtain one.
180
- if force_save and id.nil?
181
- unless save
182
- # Save failed!
183
- return nil
184
- end
185
- end
186
-
187
- # Complete the path.
188
- path <<
189
- sep + 'data' +
190
- sep + self.class.to_s.demodulize.underscore.pluralize +
191
- sep + self.id.to_s
192
- end
193
-
194
- # Returns an array of attachments.
195
- def attachments
196
- # Unsaved new records, naturally, have no attachments.
197
- return [] if new?
198
-
199
- attachments = Array.new
200
- if path = local_attachment_path and File.directory? path
201
- begin
202
- attachments = Dir.open(path).reject do |name|
203
- # Don't include dotfiles
204
- name =~ /^\./
205
- end
206
- attachments.collect! do |name|
207
- Attachment.new self, name
208
- end
209
- rescue
210
- # Couldn't read the directory
211
- end
212
- end
213
- attachments
214
- end
215
-
216
- # Ensures the attachment directory exists.
217
- def create_attachment_directory
218
- path = local_attachment_path
219
- unless File.directory? path
220
- FileUtils.mkdir_p path, :mode => DEFAULT_ATTACHMENT_DIRECTORY_MODE
221
- end
222
- end
223
-
224
- # Returns the local directory which contains attachments for this record.
225
- def local_attachment_path
226
- attachment_path :local
227
- end
228
-
229
- # Returns the public directory which contains attachments for this record.
230
- def public_attachment_path
231
- attachment_path :public
232
- end
233
239
  end
234
240
  end
235
241
  end
242
+
243
+ CortexReaver::Attachment = Sequel::Plugins::Attachments::Attachment