parlement 0.2 → 0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +14 -4
- data/README +25 -5
- data/Rakefile +21 -21
- data/app/controllers/account_controller.rb +5 -1
- data/app/controllers/elt_controller.rb +7 -10
- data/app/controllers/person_controller.rb +9 -0
- data/app/controllers/subscriber_controller.rb +21 -0
- data/app/helpers/elt_helper.rb +25 -13
- data/app/helpers/mailman.rb +9 -92
- data/app/helpers/subscriber_helper.rb +2 -0
- data/app/models/attachment.rb +2 -0
- data/app/models/elt.rb +64 -2
- data/app/models/mail.rb +198 -0
- data/app/models/mail_notify.rb +63 -0
- data/app/models/person.rb +8 -1
- data/app/views/account/_login.rhtml +31 -28
- data/app/views/account/_show.rhtml +4 -4
- data/app/views/elt/_elt.rhtml +23 -28
- data/app/views/elt/_list.rhtml +6 -2
- data/app/views/elt/new.rhtml +1 -1
- data/app/views/elt/show.rhtml +32 -10
- data/app/views/layouts/top.rhtml +16 -10
- data/app/views/mail_notify/publish.text.html.rhtml +46 -0
- data/app/views/mail_notify/publish.text.plain.rhtml +2 -0
- data/app/views/person/_listElts.rhtml +33 -0
- data/app/views/person/show.rhtml +21 -19
- data/config/boot.rb +2 -0
- data/config/environment.rb +19 -13
- data/config/environments/development.rb +3 -1
- data/config/environments/production.rb +2 -0
- data/config/environments/test.rb +2 -0
- data/config/routes.rb +5 -2
- data/db/ROOT/mail.txt +2 -0
- data/db/ROOT/parlement/news/release0.2.txt +8 -0
- data/db/ROOT/parlement/news/release0.3.txt +11 -0
- data/db/ROOT/parlement/test.txt +6 -1
- data/db/ROOT/parlement.txt +23 -30
- data/db/ROOT/perso.txt +17 -18
- data/db/development_structure.sql +133 -217
- data/db/schema.rb +83 -0
- data/db/schema.sql +11 -15
- data/lib/data_import.rb +3 -1
- data/public/attachment/file/architecture.png +0 -0
- data/public/attachment/file/architecture.svg +8972 -0
- data/public/attachment/file/security.svg +8960 -0
- data/public/images/Sleep-Deprivation-5.JPG +0 -0
- data/public/images/eltBackground.png +0 -0
- data/public/images/eltBackground.svg +89 -0
- data/public/images/orange_by_darren_Hester_350o.jpg +0 -0
- data/public/images/rails.png +0 -0
- data/public/images/smile.png +0 -0
- data/public/images/smile.svg +257 -0
- data/public/images/world.png +0 -0
- data/public/images/world.svg +170 -0
- data/public/javascripts/controls.js +30 -1
- data/public/javascripts/dragdrop.js +210 -145
- data/public/javascripts/effects.js +261 -399
- data/public/javascripts/ie7.js +6 -0
- data/public/javascripts/prototype.js +131 -72
- data/public/oldindex.html +270 -71
- data/public/stylesheets/default.css +189 -215
- data/script/about +1 -1
- data/script/breakpointer +1 -1
- data/script/console +1 -1
- data/script/destroy +1 -1
- data/script/generate +1 -1
- data/script/performance/benchmarker +1 -1
- data/script/performance/profiler +1 -1
- data/script/plugin +1 -1
- data/script/process/reaper +1 -1
- data/script/process/spawner +1 -1
- data/script/process/spinner +1 -1
- data/script/runner +1 -1
- data/script/server +1 -1
- data/test/fixtures/elts.yml +2 -0
- data/test/fixtures/mail/mail_ruby +27 -0
- data/test/fixtures/mail/mail_rubyChild +28 -0
- data/test/fixtures/mail/mail_rubyWithAttachment +7932 -0
- data/test/fixtures/mail/mail_rubyWithSubject +27 -0
- data/test/fixtures/mails.yml +7 -1
- data/test/fixtures/people.yml +5 -0
- data/test/fixtures/subscribers.yml +11 -0
- data/test/functional/account_controller_test.rb +38 -37
- data/test/functional/subscriber_controller_test.rb +128 -0
- data/test/test_helper.rb +44 -0
- data/test/unit/attachment_test.rb +1 -1
- data/test/unit/elt_test.rb +3 -2
- data/test/unit/mail_notify_test.rb +37 -0
- data/test/unit/mail_test.rb +124 -1
- data/test/unit/notifier_test.rb +0 -14
- data/test/unit/person_test.rb +2 -1
- data/test/unit/subscriber_test.rb +35 -0
- data/test/unit/user_test.rb +3 -3
- data/vendor/plugins/file_column/CHANGELOG +64 -0
- data/vendor/plugins/file_column/README +54 -0
- data/vendor/plugins/file_column/Rakefile +36 -0
- data/vendor/plugins/file_column/TODO +6 -0
- data/vendor/plugins/file_column/init.rb +12 -0
- data/vendor/plugins/file_column/lib/file_column.rb +719 -0
- data/vendor/plugins/file_column/lib/file_column_helper.rb +145 -0
- data/vendor/plugins/file_column/lib/file_compat.rb +28 -0
- data/vendor/plugins/file_column/lib/magick_file_column.rb +188 -0
- data/vendor/plugins/file_column/lib/validations.rb +112 -0
- data/vendor/plugins/file_column/test/abstract_unit.rb +90 -0
- data/vendor/plugins/file_column/test/connection.rb +17 -0
- data/vendor/plugins/file_column/test/file_column_helper_test.rb +97 -0
- data/vendor/plugins/file_column/test/file_column_test.rb +630 -0
- data/vendor/plugins/file_column/test/fixtures/entry.rb +32 -0
- data/vendor/plugins/file_column/test/fixtures/invalid-image.jpg +1 -0
- data/vendor/plugins/file_column/test/fixtures/kerb.jpg +0 -0
- data/vendor/plugins/file_column/test/fixtures/mysql.sql +25 -0
- data/vendor/plugins/file_column/test/fixtures/schema.rb +10 -0
- data/vendor/plugins/file_column/test/fixtures/skanthak.png +0 -0
- data/vendor/plugins/file_column/test/magick_test.rb +251 -0
- data/vendor/plugins/file_column/test/magick_view_only_test.rb +21 -0
- data/vendor/plugins/guid/README.TXT +19 -0
- data/vendor/plugins/guid/init.rb +23 -0
- data/vendor/plugins/guid/lib/usesguid.rb +37 -0
- data/vendor/plugins/guid/lib/uuid22.rb +43 -0
- data/vendor/plugins/guid/lib/uuidtools.rb +565 -0
- metadata +83 -15
- data/db/ROOT/CV.txt +0 -166
- data/lib/file_column.rb +0 -263
- data/lib/file_column_helper.rb +0 -45
- /data/{lib → vendor/plugins/file_column/lib}/rails_file_column.rb +0 -0
@@ -0,0 +1,145 @@
|
|
1
|
+
# This module contains helper methods for displaying and uploading files
|
2
|
+
# for attributes created by +FileColumn+'s +file_column+ method. It will be
|
3
|
+
# automatically included into ActionView::Base, thereby making this module's
|
4
|
+
# methods available in all your views.
|
5
|
+
module FileColumnHelper
|
6
|
+
|
7
|
+
# Use this helper to create an upload field for a file_column attribute. This will generate
|
8
|
+
# an additional hidden field to keep uploaded files during form-redisplays. For example,
|
9
|
+
# when called with
|
10
|
+
#
|
11
|
+
# <%= file_column_field("entry", "image") %>
|
12
|
+
#
|
13
|
+
# the following HTML will be generated (assuming the form is redisplayed and something has
|
14
|
+
# already been uploaded):
|
15
|
+
#
|
16
|
+
# <input type="hidden" name="entry[image_temp]" value="..." />
|
17
|
+
# <input type="file" name="entry[image]" />
|
18
|
+
#
|
19
|
+
# You can use the +option+ argument to pass additional options to the file-field tag.
|
20
|
+
#
|
21
|
+
# Be sure to set the enclosing form's encoding to 'multipart/form-data', by
|
22
|
+
# using something like this:
|
23
|
+
#
|
24
|
+
# <%= form_tag {:action => "create", ...}, :multipart => true %>
|
25
|
+
def file_column_field(object, method, options={})
|
26
|
+
result = ActionView::Helpers::InstanceTag.new(object.dup, method.to_s+"_temp", self).to_input_field_tag("hidden", {})
|
27
|
+
result << ActionView::Helpers::InstanceTag.new(object.dup, method, self).to_input_field_tag("file", options)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Creates an URL where an uploaded file can be accessed. When called for an Entry object with
|
31
|
+
# id 42 (stored in <tt>@entry</tt>) like this
|
32
|
+
#
|
33
|
+
# <%= url_for_file_column(@entry, "image")
|
34
|
+
#
|
35
|
+
# the following URL will be produced, assuming the file "test.png" has been stored in
|
36
|
+
# the "image"-column of an Entry object stored in <tt>@entry</tt>:
|
37
|
+
#
|
38
|
+
# /entry/image/42/test.png
|
39
|
+
#
|
40
|
+
# This will produce a valid URL even for temporary uploaded files, e.g. files where the object
|
41
|
+
# they are belonging to has not been saved in the database yet.
|
42
|
+
#
|
43
|
+
# The URL produces, although starting with a slash, will be relative
|
44
|
+
# to your app's root. If you pass it to one rails' +image_tag+
|
45
|
+
# helper, rails will properly convert it to an absolute
|
46
|
+
# URL. However, this will not be the case, if you create a link with
|
47
|
+
# the +link_to+ helper. In this case, you can pass <tt>:absolute =>
|
48
|
+
# true</tt> to +options+, which will make sure, the generated URL is
|
49
|
+
# absolute on your server. Examples:
|
50
|
+
#
|
51
|
+
# <%= image_tag url_for_file_column(@entry, "image") %>
|
52
|
+
# <%= link_to "Download", url_for_file_column(@entry, "image", :absolute => true) %>
|
53
|
+
#
|
54
|
+
# If there is currently no uploaded file stored in the object's column this method will
|
55
|
+
# return +nil+.
|
56
|
+
def url_for_file_column(object, method, options=nil)
|
57
|
+
case object
|
58
|
+
when String, Symbol
|
59
|
+
object = instance_variable_get("@#{object.to_s}")
|
60
|
+
end
|
61
|
+
|
62
|
+
# parse options
|
63
|
+
subdir = nil
|
64
|
+
absolute = false
|
65
|
+
if options
|
66
|
+
case options
|
67
|
+
when Hash
|
68
|
+
subdir = options[:subdir]
|
69
|
+
absolute = options[:absolute]
|
70
|
+
when String, Symbol
|
71
|
+
subdir = options
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
relative_path = object.send("#{method}_relative_path", subdir)
|
76
|
+
return nil unless relative_path
|
77
|
+
|
78
|
+
url = ""
|
79
|
+
url << request.relative_url_root.to_s if absolute
|
80
|
+
url << "/"
|
81
|
+
url << object.send("#{method}_options")[:base_url] << "/"
|
82
|
+
url << relative_path
|
83
|
+
end
|
84
|
+
|
85
|
+
# Same as +url_for_file_colum+ but allows you to access different versions
|
86
|
+
# of the image that have been processed by RMagick.
|
87
|
+
#
|
88
|
+
# If your +options+ parameter is non-nil this will
|
89
|
+
# access a different version of an image that will be produced by
|
90
|
+
# RMagick. You can use the following types for +options+:
|
91
|
+
#
|
92
|
+
# * a <tt>:symbol</tt> will select a version defined in the model
|
93
|
+
# via FileColumn::Magick's <tt>:versions</tt> feature.
|
94
|
+
# * a <tt>geometry_string</tt> will dynamically create an
|
95
|
+
# image resized as specified by <tt>geometry_string</tt>. The image will
|
96
|
+
# be stored so that it does not have to be recomputed the next time the
|
97
|
+
# same version string is used.
|
98
|
+
# * <tt>some_hash</tt> will dynamically create an image
|
99
|
+
# that is created according to the options in <tt>some_hash</tt>. This
|
100
|
+
# accepts exactly the same options as Magick's version feature.
|
101
|
+
#
|
102
|
+
# The version produced by RMagick will be stored in a special sub-directory.
|
103
|
+
# The directory's name will be derived from the options you specified
|
104
|
+
# (via a hash function) but if you want
|
105
|
+
# to set it yourself, you can use the <tt>:name => name</tt> option.
|
106
|
+
#
|
107
|
+
# Examples:
|
108
|
+
#
|
109
|
+
# <%= url_for_image_column @entry, "image", "640x480" %>
|
110
|
+
#
|
111
|
+
# will produce an URL like this
|
112
|
+
#
|
113
|
+
# /entry/image/42/bdn19n/filename.jpg
|
114
|
+
# # "640x480".hash.abs.to_s(36) == "bdn19n"
|
115
|
+
#
|
116
|
+
# and
|
117
|
+
#
|
118
|
+
# <%= url_for_image_column @entry, "image",
|
119
|
+
# :size => "50x50", :crop => "1:1", :name => "thumb" %>
|
120
|
+
#
|
121
|
+
# will produce something like this:
|
122
|
+
#
|
123
|
+
# /entry/image/42/thumb/filename.jpg
|
124
|
+
#
|
125
|
+
# Hint: If you are using the same geometry string / options hash multiple times, you should
|
126
|
+
# define it in a helper to stay with DRY. Another option is to define it in the model via
|
127
|
+
# FileColumn::Magick's <tt>:versions</tt> feature and then refer to it via a symbol.
|
128
|
+
#
|
129
|
+
# The URL produced by this method is relative to your application's root URL,
|
130
|
+
# although it will start with a slash.
|
131
|
+
# If you pass this URL to rails' +image_tag+ helper, it will be converted to an
|
132
|
+
# absolute URL automatically.
|
133
|
+
# If there is currently no image uploaded, this method will return +nil+.
|
134
|
+
def url_for_image_column(object, method, options=nil)
|
135
|
+
case object
|
136
|
+
when String, Symbol
|
137
|
+
object = instance_variable_get("@#{object.to_s}")
|
138
|
+
end
|
139
|
+
subdir = nil
|
140
|
+
if options
|
141
|
+
subdir = object.send("#{method}_state").create_magick_version_if_needed(options)
|
142
|
+
end
|
143
|
+
url_for_file_column(object, method, subdir)
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module FileColumn
|
2
|
+
|
3
|
+
# This bit of code allows you to pass regular old files to
|
4
|
+
# file_column. file_column depends on a few extra methods that the
|
5
|
+
# CGI uploaded file class adds. We will add the equivalent methods
|
6
|
+
# to file objects if necessary by extending them with this module. This
|
7
|
+
# avoids opening up the standard File class which might result in
|
8
|
+
# naming conflicts.
|
9
|
+
|
10
|
+
module FileCompat # :nodoc:
|
11
|
+
def original_filename
|
12
|
+
File.basename(path)
|
13
|
+
end
|
14
|
+
|
15
|
+
def size
|
16
|
+
File.size(path)
|
17
|
+
end
|
18
|
+
|
19
|
+
def local_path
|
20
|
+
path
|
21
|
+
end
|
22
|
+
|
23
|
+
def content_type
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
@@ -0,0 +1,188 @@
|
|
1
|
+
module FileColumn # :nodoc:
|
2
|
+
|
3
|
+
class BaseUploadedFile # :nodoc:
|
4
|
+
def transform_with_magick
|
5
|
+
if needs_resize?
|
6
|
+
begin
|
7
|
+
img = ::Magick::Image::read(absolute_path).first
|
8
|
+
rescue ::Magick::ImageMagickError
|
9
|
+
@magick_errors ||= []
|
10
|
+
@magick_errors << "invalid image"
|
11
|
+
return
|
12
|
+
end
|
13
|
+
|
14
|
+
if options[:magick][:versions]
|
15
|
+
options[:magick][:versions].each_pair do |version, version_options|
|
16
|
+
next if version_options[:lazy]
|
17
|
+
dirname = version_options[:name]
|
18
|
+
FileUtils.mkdir File.join(@dir, dirname)
|
19
|
+
resize_image(img, version_options, absolute_path(dirname))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
if options[:magick][:size] or options[:magick][:crop]
|
23
|
+
resize_image(img, options[:magick], absolute_path)
|
24
|
+
end
|
25
|
+
|
26
|
+
GC.start
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def create_magick_version_if_needed(version)
|
31
|
+
# RMagick might not have been loaded so far.
|
32
|
+
# We do not want to require it on every call of this method
|
33
|
+
# as this might be fairly expensive, so we just try if ::Magick
|
34
|
+
# exists and require it if not.
|
35
|
+
begin
|
36
|
+
::Magick
|
37
|
+
rescue NameError
|
38
|
+
require 'RMagick'
|
39
|
+
end
|
40
|
+
|
41
|
+
if version.is_a?(Symbol)
|
42
|
+
version_options = options[:magick][:versions][version]
|
43
|
+
else
|
44
|
+
version_options = MagickExtension::process_options(version)
|
45
|
+
end
|
46
|
+
|
47
|
+
unless File.exists?(absolute_path(version_options[:name]))
|
48
|
+
img = ::Magick::Image::read(absolute_path).first
|
49
|
+
dirname = version_options[:name]
|
50
|
+
FileUtils.mkdir File.join(@dir, dirname)
|
51
|
+
resize_image(img, version_options, absolute_path(dirname))
|
52
|
+
end
|
53
|
+
|
54
|
+
version_options[:name]
|
55
|
+
end
|
56
|
+
|
57
|
+
attr_reader :magick_errors
|
58
|
+
|
59
|
+
def has_magick_errors?
|
60
|
+
@magick_errors and !@magick_errors.empty?
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def needs_resize?
|
66
|
+
options[:magick] and just_uploaded? and
|
67
|
+
(options[:magick][:size] or options[:magick][:versions])
|
68
|
+
end
|
69
|
+
|
70
|
+
def resize_image(img, img_options, dest_path)
|
71
|
+
begin
|
72
|
+
if img_options[:crop]
|
73
|
+
dx, dy = img_options[:crop].split(':').map { |x| x.to_f }
|
74
|
+
w, h = (img.rows * dx / dy), (img.columns * dy / dx)
|
75
|
+
img = img.crop(::Magick::CenterGravity, [img.columns, w].min,
|
76
|
+
[img.rows, h].min)
|
77
|
+
end
|
78
|
+
|
79
|
+
if img_options[:size]
|
80
|
+
img = img.change_geometry(img_options[:size]) do |c, r, i|
|
81
|
+
i.resize(c, r)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
ensure
|
85
|
+
img.write dest_path
|
86
|
+
File.chmod options[:permissions], dest_path
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# If you are using file_column to upload images, you can
|
92
|
+
# directly process the images with RMagick,
|
93
|
+
# a ruby extension
|
94
|
+
# for accessing the popular imagemagick libraries. You can find
|
95
|
+
# more information about RMagick at http://rmagick.rubyforge.org.
|
96
|
+
#
|
97
|
+
# You can control what to do by adding a <tt>:magick</tt> option
|
98
|
+
# to your options hash. All operations are performed immediately
|
99
|
+
# after a new file is assigned to the file_column attribute (i.e.,
|
100
|
+
# when a new file has been uploaded).
|
101
|
+
#
|
102
|
+
# To resize the uploaded image according to an imagemagick geometry
|
103
|
+
# string, just use the <tt>:size</tt> option:
|
104
|
+
#
|
105
|
+
# file_column :image, :magick => {:size => "800x600>"}
|
106
|
+
#
|
107
|
+
# You can also create additional versions of your image, for example
|
108
|
+
# thumb-nails, like this:
|
109
|
+
# file_column :image, :magick => {:versions => {
|
110
|
+
# :thumb => {:size => "50x50"},
|
111
|
+
# :medium => {:size => "640x480>"}
|
112
|
+
# }
|
113
|
+
#
|
114
|
+
# If you wish to crop your images with a size ratio before scaling
|
115
|
+
# them according to your version geometry, you can use the :crop directive.
|
116
|
+
# file_column :image, :magick => {:versions => {
|
117
|
+
# :square => {:crop => "1:1", :size => "50x50", :name => "thumb"},
|
118
|
+
# :screen => {:crop => "4:3", :size => "640x480>"},
|
119
|
+
# :widescreen => {:crop => "16:9", :size => "640x360!"},
|
120
|
+
# }
|
121
|
+
# }
|
122
|
+
#
|
123
|
+
# These versions will be stored in separate sub-directories, named like the
|
124
|
+
# symbol you used to identify the version. So in the previous example, the
|
125
|
+
# image versions will be stored in "thumb", "screen" and "widescreen"
|
126
|
+
# directories, resp.
|
127
|
+
# A name different from the symbol can be set via the <tt>:name</tt> option.
|
128
|
+
#
|
129
|
+
# These versions can be accessed via FileColumnHelper's +url_for_image_column+
|
130
|
+
# method like this:
|
131
|
+
#
|
132
|
+
# <%= url_for_image_column "entry", "image", :thumb %>
|
133
|
+
#
|
134
|
+
# <b>Note:</b> You'll need the
|
135
|
+
# RMagick extension being installed in order to use file_column's
|
136
|
+
# imagemagick integration.
|
137
|
+
module MagickExtension
|
138
|
+
|
139
|
+
def self.file_column(klass, attr, options) # :nodoc:
|
140
|
+
require 'RMagick'
|
141
|
+
options[:magick] = process_options(options[:magick],false) if options[:magick]
|
142
|
+
if options[:magick][:versions]
|
143
|
+
options[:magick][:versions].each_pair do |name, value|
|
144
|
+
options[:magick][:versions][name] = process_options(value, name.to_s)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
state_method = "#{attr}_state".to_sym
|
148
|
+
after_assign_method = "#{attr}_magick_after_assign".to_sym
|
149
|
+
|
150
|
+
klass.send(:define_method, after_assign_method) do
|
151
|
+
self.send(state_method).transform_with_magick
|
152
|
+
end
|
153
|
+
|
154
|
+
options[:after_upload] ||= []
|
155
|
+
options[:after_upload] << after_assign_method
|
156
|
+
|
157
|
+
klass.validate do |record|
|
158
|
+
state = record.send(state_method)
|
159
|
+
if state.has_magick_errors?
|
160
|
+
state.magick_errors.each do |error|
|
161
|
+
record.errors.add attr, error
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
def self.process_options(options,create_name=true)
|
169
|
+
options = {:size => options } if options.kind_of?(String)
|
170
|
+
if options[:geometry]
|
171
|
+
options[:size] = options.delete(:geometry)
|
172
|
+
end
|
173
|
+
if options[:name].nil? and create_name
|
174
|
+
if create_name == true
|
175
|
+
hash = 0
|
176
|
+
for key in [:size, :crop]
|
177
|
+
hash = hash ^ options[key].hash if options[key]
|
178
|
+
end
|
179
|
+
options[:name] = hash.abs.to_s(36)
|
180
|
+
else
|
181
|
+
options[:name] = create_name
|
182
|
+
end
|
183
|
+
end
|
184
|
+
options
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
188
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module FileColumn
|
2
|
+
module Validations #:nodoc:
|
3
|
+
|
4
|
+
def self.append_features(base)
|
5
|
+
super
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
# This module contains methods to create validations of uploaded files. All methods
|
10
|
+
# in this module will be included as class methods into <tt>ActiveRecord::Base</tt>
|
11
|
+
# so that you can use them in your models like this:
|
12
|
+
#
|
13
|
+
# class Entry < ActiveRecord::Base
|
14
|
+
# file_column :image
|
15
|
+
# validates_filesize_of :image, :in => 0..1.megabyte
|
16
|
+
# end
|
17
|
+
module ClassMethods
|
18
|
+
EXT_REGEXP = /\.([A-z0-9]+)$/
|
19
|
+
|
20
|
+
# This validates the file type of one or more file_columns. A list of file columns
|
21
|
+
# should be given followed by an options hash.
|
22
|
+
#
|
23
|
+
# Required options:
|
24
|
+
# * <tt>:in</tt> => list of extensions or mime types. If mime types are used they
|
25
|
+
# will be mapped into an extension via FileColumn::ClassMethods::MIME_EXTENSIONS.
|
26
|
+
#
|
27
|
+
# Examples:
|
28
|
+
# validates_file_format_of :field, :in => ["gif", "png", "jpg"]
|
29
|
+
# validates_file_format_of :field, :in => ["image/jpeg"]
|
30
|
+
def validates_file_format_of(*attrs)
|
31
|
+
|
32
|
+
options = attrs.pop if attrs.last.is_a?Hash
|
33
|
+
raise ArgumentError, "Please include the :in option." if !options || !options[:in]
|
34
|
+
options[:in] = [options[:in]] if options[:in].is_a?String
|
35
|
+
raise ArgumentError, "Invalid value for option :in" unless options[:in].is_a?Array
|
36
|
+
|
37
|
+
validates_each(attrs, options) do |record, attr, value|
|
38
|
+
unless value.blank?
|
39
|
+
mime_extensions = record.send("#{attr}_options")[:mime_extensions]
|
40
|
+
extensions = options[:in].map{|o| mime_extensions[o] || o }
|
41
|
+
record.errors.add attr, "is not a valid format." unless extensions.include?(value.scan(EXT_REGEXP).flatten.first)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
# This validates the file size of one or more file_columns. A list of file columns
|
48
|
+
# should be given followed by an options hash.
|
49
|
+
#
|
50
|
+
# Required options:
|
51
|
+
# * <tt>:in</tt> => A size range. Note that you can use ActiveSupport's
|
52
|
+
# numeric extensions for kilobytes, etc.
|
53
|
+
#
|
54
|
+
# Examples:
|
55
|
+
# validates_filesize_of :field, :in => 0..100.megabytes
|
56
|
+
# validates_filesize_of :field, :in => 15.kilobytes..1.megabyte
|
57
|
+
def validates_filesize_of(*attrs)
|
58
|
+
|
59
|
+
options = attrs.pop if attrs.last.is_a?Hash
|
60
|
+
raise ArgumentError, "Please include the :in option." if !options || !options[:in]
|
61
|
+
raise ArgumentError, "Invalid value for option :in" unless options[:in].is_a?Range
|
62
|
+
|
63
|
+
validates_each(attrs, options) do |record, attr, value|
|
64
|
+
unless value.blank?
|
65
|
+
size = File.size(value)
|
66
|
+
record.errors.add attr, "is smaller than the allowed size range." if size < options[:in].first
|
67
|
+
record.errors.add attr, "is larger than the allowed size range." if size > options[:in].last
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
IMAGE_SIZE_REGEXP = /^(\d+)x(\d+)$/
|
74
|
+
|
75
|
+
# Validates the image size of one or more file_columns. A list of file columns
|
76
|
+
# should be given followed by an options hash. The validation will pass
|
77
|
+
# if both image dimensions (rows and columns) are at least as big as
|
78
|
+
# given in the <tt>:min</tt> option.
|
79
|
+
#
|
80
|
+
# Required options:
|
81
|
+
# * <tt>:min</tt> => minimum image dimension string, in the format NNxNN
|
82
|
+
# (columns x rows).
|
83
|
+
#
|
84
|
+
# Example:
|
85
|
+
# validates_image_size :field, :min => "1200x1800"
|
86
|
+
#
|
87
|
+
# This validation requires RMagick to be installed on your system
|
88
|
+
# to check the image's size.
|
89
|
+
def validates_image_size(*attrs)
|
90
|
+
options = attrs.pop if attrs.last.is_a?Hash
|
91
|
+
raise ArgumentError, "Please include a :min option." if !options || !options[:min]
|
92
|
+
minimums = options[:min].scan(IMAGE_SIZE_REGEXP).first.collect{|n| n.to_i} rescue []
|
93
|
+
raise ArgumentError, "Invalid value for option :min (should be 'XXxYY')" unless minimums.size == 2
|
94
|
+
|
95
|
+
require 'RMagick'
|
96
|
+
|
97
|
+
validates_each(attrs, options) do |record, attr, value|
|
98
|
+
unless value.blank?
|
99
|
+
begin
|
100
|
+
img = ::Magick::Image::read(value).first
|
101
|
+
record.errors.add('image', "is too small, must be at least #{minimums[0]}x#{minimums[1]}") if ( img.rows < minimums[1] || img.columns < minimums[0] )
|
102
|
+
rescue ::Magick::ImageMagickError
|
103
|
+
record.errors.add('image', "invalid image")
|
104
|
+
end
|
105
|
+
img = nil
|
106
|
+
GC.start
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_record'
|
5
|
+
require 'action_view'
|
6
|
+
require File.dirname(__FILE__) + '/connection'
|
7
|
+
require 'stringio'
|
8
|
+
|
9
|
+
RAILS_ROOT = File.dirname(__FILE__)
|
10
|
+
|
11
|
+
$: << "../lib"
|
12
|
+
|
13
|
+
require 'file_column'
|
14
|
+
require 'file_compat'
|
15
|
+
require 'validations'
|
16
|
+
|
17
|
+
# do not use the file executable normally in our tests as
|
18
|
+
# it may not be present on the machine we are running on
|
19
|
+
FileColumn::ClassMethods::DEFAULT_OPTIONS =
|
20
|
+
FileColumn::ClassMethods::DEFAULT_OPTIONS.merge({:file_exec => nil})
|
21
|
+
|
22
|
+
class ActiveRecord::Base
|
23
|
+
include FileColumn
|
24
|
+
include FileColumn::Validations
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
class RequestMock
|
29
|
+
attr_accessor :relative_url_root
|
30
|
+
|
31
|
+
def initialize
|
32
|
+
@relative_url_root = ""
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class Test::Unit::TestCase
|
37
|
+
def assert_equal_paths(expected_path, path)
|
38
|
+
assert_equal normalize_path(expected_path), normalize_path(path)
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def normalize_path(path)
|
45
|
+
Pathname.new(path).realpath
|
46
|
+
end
|
47
|
+
|
48
|
+
def uploaded_file(path, content_type, filename, type=:tempfile)
|
49
|
+
if type == :tempfile
|
50
|
+
t = Tempfile.new(File.basename(filename))
|
51
|
+
FileUtils.copy_file(path, t.path)
|
52
|
+
else
|
53
|
+
if path
|
54
|
+
t = StringIO.new(IO.read(path))
|
55
|
+
else
|
56
|
+
t = StringIO.new
|
57
|
+
end
|
58
|
+
end
|
59
|
+
(class << t; self; end).class_eval do
|
60
|
+
alias local_path path if type == :tempfile
|
61
|
+
define_method(:local_path) { "" } if type == :stringio
|
62
|
+
define_method(:original_filename) {filename}
|
63
|
+
define_method(:content_type) {content_type}
|
64
|
+
end
|
65
|
+
return t
|
66
|
+
end
|
67
|
+
|
68
|
+
def upload(basename, content_type=:guess, file_type=:tempfile)
|
69
|
+
if content_type == :guess
|
70
|
+
case basename
|
71
|
+
when /\.jpg$/ then content_type = "image/jpeg"
|
72
|
+
when /\.png$/ then content_type = "image/png"
|
73
|
+
else content_type = nil
|
74
|
+
end
|
75
|
+
end
|
76
|
+
uploaded_file(file_path(basename), content_type, basename, file_type)
|
77
|
+
end
|
78
|
+
|
79
|
+
def clear_validations
|
80
|
+
[:validate, :validate_on_create, :validate_on_update].each do |attr|
|
81
|
+
Entry.write_inheritable_attribute attr, []
|
82
|
+
Movie.write_inheritable_attribute attr, []
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def file_path(filename)
|
87
|
+
File.expand_path("#{File.dirname(__FILE__)}/fixtures/#{filename}")
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
print "Using native MySQL\n"
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
ActiveRecord::Base.logger = Logger.new("debug.log")
|
5
|
+
|
6
|
+
db = 'file_column_test'
|
7
|
+
|
8
|
+
ActiveRecord::Base.establish_connection(
|
9
|
+
:adapter => "mysql",
|
10
|
+
:host => "localhost",
|
11
|
+
:username => "rails",
|
12
|
+
:password => "",
|
13
|
+
:database => db,
|
14
|
+
:socket => "/var/run/mysqld/mysqld.sock"
|
15
|
+
)
|
16
|
+
|
17
|
+
load File.dirname(__FILE__) + "/fixtures/schema.rb"
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/abstract_unit'
|
2
|
+
require File.dirname(__FILE__) + '/fixtures/entry'
|
3
|
+
|
4
|
+
class UrlForFileColumnTest < Test::Unit::TestCase
|
5
|
+
include FileColumnHelper
|
6
|
+
|
7
|
+
def setup
|
8
|
+
Entry.file_column :image
|
9
|
+
@request = RequestMock.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_url_for_file_column_with_temp_entry
|
13
|
+
@e = Entry.new(:image => upload("skanthak.png"))
|
14
|
+
url = url_for_file_column("e", "image")
|
15
|
+
assert_match %r{^/entry/image/tmp/\d+(\.\d+)+/skanthak.png$}, url
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_url_for_file_column_with_saved_entry
|
19
|
+
@e = Entry.new(:image => upload("skanthak.png"))
|
20
|
+
assert @e.save
|
21
|
+
|
22
|
+
url = url_for_file_column("e", "image")
|
23
|
+
assert_equal "/entry/image/#{@e.id}/skanthak.png", url
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_url_for_file_column_works_with_symbol
|
27
|
+
@e = Entry.new(:image => upload("skanthak.png"))
|
28
|
+
assert @e.save
|
29
|
+
|
30
|
+
url = url_for_file_column(:e, :image)
|
31
|
+
assert_equal "/entry/image/#{@e.id}/skanthak.png", url
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_url_for_file_column_works_with_object
|
35
|
+
e = Entry.new(:image => upload("skanthak.png"))
|
36
|
+
assert e.save
|
37
|
+
|
38
|
+
url = url_for_file_column(e, "image")
|
39
|
+
assert_equal "/entry/image/#{e.id}/skanthak.png", url
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_url_for_file_column_should_return_nil_on_no_uploaded_file
|
43
|
+
e = Entry.new
|
44
|
+
assert_nil url_for_file_column(e, "image")
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_url_for_file_column_without_extension
|
48
|
+
e = Entry.new
|
49
|
+
e.image = uploaded_file(file_path("kerb.jpg"), "something/unknown", "local_filename")
|
50
|
+
assert e.save
|
51
|
+
assert_equal "/entry/image/#{e.id}/local_filename", url_for_file_column(e, "image")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class UrlForFileColumnTest < Test::Unit::TestCase
|
56
|
+
include FileColumnHelper
|
57
|
+
include ActionView::Helpers::AssetTagHelper
|
58
|
+
include ActionView::Helpers::TagHelper
|
59
|
+
include ActionView::Helpers::UrlHelper
|
60
|
+
|
61
|
+
def setup
|
62
|
+
Entry.file_column :image
|
63
|
+
|
64
|
+
# mock up some request data structures for AssetTagHelper
|
65
|
+
@request = RequestMock.new
|
66
|
+
@request.relative_url_root = "/foo/bar"
|
67
|
+
@controller = self
|
68
|
+
end
|
69
|
+
|
70
|
+
def request
|
71
|
+
@request
|
72
|
+
end
|
73
|
+
|
74
|
+
IMAGE_URL = %r{^/foo/bar/entry/image/.+/skanthak.png$}
|
75
|
+
def test_with_image_tag
|
76
|
+
e = Entry.new(:image => upload("skanthak.png"))
|
77
|
+
html = image_tag url_for_file_column(e, "image")
|
78
|
+
url = html.scan(/src=\"(.+)\"/).first.first
|
79
|
+
|
80
|
+
assert_match IMAGE_URL, url
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_with_link_to_tag
|
84
|
+
e = Entry.new(:image => upload("skanthak.png"))
|
85
|
+
html = link_to "Download", url_for_file_column(e, "image", :absolute => true)
|
86
|
+
url = html.scan(/href=\"(.+)\"/).first.first
|
87
|
+
|
88
|
+
assert_match IMAGE_URL, url
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_relative_url_root_not_modified
|
92
|
+
e = Entry.new(:image => upload("skanthak.png"))
|
93
|
+
url_for_file_column(e, "image", :absolute => true)
|
94
|
+
|
95
|
+
assert_equal "/foo/bar", @request.relative_url_root
|
96
|
+
end
|
97
|
+
end
|