parlement 0.2 → 0.3
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.
- 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
|