mail_form 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
@@ -0,0 +1,21 @@
1
+ * Version 1.0
2
+
3
+ * Rename to mail_form and launch Rails 2.3 branch.
4
+
5
+ * Version 0.4
6
+
7
+ * Added support to template
8
+
9
+ # Version 0.3
10
+
11
+ * Added support to symbols on :sender, :subject and :recipients
12
+ * Added support to symbols on :validate
13
+
14
+ # Version 0.2
15
+
16
+ * Added support to request objects and append DSL
17
+ * Added support to :attachments (thanks to @andrewtimberlake)
18
+
19
+ # Version 0.1
20
+
21
+ * First release
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Plataforma Tec http://blog.plataformatec.com.br/
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,186 @@
1
+ == MailForm
2
+
3
+ MailForm allows you to send an e-mail straight from a form. For instance,
4
+ if you want to make a contact form just the following lines are needed (including the e-mail):
5
+
6
+ class ContactForm < MailForm
7
+ subject "My Contact Form"
8
+ recipients "your.email@your.domain.com"
9
+ sender{|c| %{"#{c.name}" <#{c.email}>} }
10
+
11
+ attribute :name, :validate => true
12
+ attribute :email, :validate => /[^@]+@[^\.]+\.[\w\.\-]+/
13
+ attribute :file, :attachment => true
14
+
15
+ attribute :message
16
+ attribute :nickname, :captcha => true
17
+ end
18
+
19
+ Then you start a script/console and type:
20
+
21
+ c = ContactForm.new(:name => 'José', :email => 'jose@email.com', :message => 'Cool!')
22
+ c.deliver
23
+
24
+ Check your inbox and the e-mail will be there, with the sent fields (assuming
25
+ that you configured your delivery method properly).
26
+
27
+ MailForm also support attachments, I18n, error messages like in ActiveRecord
28
+ (so it works with custom FormBuilders) and can also send the user request information
29
+ in the mail.
30
+
31
+ It's tested and compatible with Rails 2.2.x and Rails 2.3.x.
32
+
33
+ == Installation
34
+
35
+ Install MailForm is very easy. It is stored in Gemcutter, so just run the following:
36
+
37
+ sudo gem install mail_form --version=1.0.0
38
+
39
+ If you want it as plugin, just do:
40
+
41
+ script/plugin install git://github.com/plataformatec/mail_form.git
42
+
43
+ == Request
44
+
45
+ MailForm makes easy to append user information to the contact mail. You just
46
+ have to do:
47
+
48
+ class ContactForm < MailForm
49
+ append :remote_ip, :user_agent, :session
50
+ # ...
51
+ end
52
+
53
+ And in your controller:
54
+
55
+ @contact_form = ContactForm.new(params[:contact_form])
56
+ @contact_form.request = request
57
+
58
+ The remote ip, user agent and session will be sent in the e-mail in a
59
+ request information session. You can give to append any method that the
60
+ request object responds to.
61
+
62
+ == API Overview
63
+
64
+ ==== attributes(*attributes)
65
+
66
+ Declare your form attributes. All attributes declared here will be appended
67
+ to the e-mail, except the ones :captcha is true.
68
+
69
+ Options:
70
+
71
+ * :validate - When true, validates the attributes can't be blank.
72
+ When a regexp is given, check if the attribute matches is not blank and
73
+ then if it matches the regexp.
74
+
75
+ Whenever :validate is a symbol, the method given as symbol will be
76
+ called. You can then add validations as you do in ActiveRecord (errors.add).
77
+
78
+ * :attachment - When given, expects a file to be sent and attaches
79
+ it to the e-mail. Don't forget to set your form to multitype.
80
+
81
+ * :captcha - When true, validates the attributes must be blank.
82
+ This is a simple way to avoid spam and the input should be hidden with CSS.
83
+
84
+ Examples:
85
+
86
+ class ContactForm < MailForm
87
+ attributes :name, :validate => true
88
+ attributes :email, :validate => /[^@]+@[^\.]+\.[\w\.\-]+/
89
+ attributes :message
90
+ attributes :screenshot, :attachment => true, :validate => :screenshot_required?
91
+ attributes :nickname, :captcha => true
92
+
93
+ def screenshot_required?
94
+ # ...
95
+ end
96
+ end
97
+
98
+ c = ContactForm.new(:nickname => 'not_blank', :email => 'your@email.com', :name => 'José')
99
+ c.valid? #=> true
100
+ c.spam? #=> true (raises an error in development, to remember you to hide it)
101
+ c.deliver #=> false (just delivers if is not a spam and is valid)
102
+
103
+ c = ContactForm.new(:email => 'invalid')
104
+ c.valid? #=> false
105
+ c.errors.inspect #=> { :name => :blank, :email => :invalid }
106
+ c.errors.full_messages #=> [ "Name can't be blank", "Email is invalid" ]
107
+
108
+ c = ContactForm.new(:name => 'José', :email => 'your@email.com')
109
+ # save is an alias to deliver
110
+ c.save #=> true
111
+
112
+ ==== subject(string_or_symbol_or_block)
113
+
114
+ Declares the subject of the contact email. It can be a string or a proc or a symbol.
115
+
116
+ When a symbol is given, it will call a method on the form object with the same
117
+ name as the symbol. As a proc, it receives a mail form instance. It defaults
118
+ to the class human name.
119
+
120
+ subject "My Contact Form"
121
+ subject { |c| "Contacted by #{c.name}" }
122
+
123
+ ==== sender(string_or_symbol_or_block)
124
+
125
+ Declares contact email sender. It can be a string or a proc or a symbol.
126
+
127
+ When a symbol is given, it will call a method on the form object with the same
128
+ name as the symbol. As a proc, it receives a mail form instance. By default is:
129
+
130
+ sender { |c| c.email }
131
+
132
+ This requires that your MailForm object have an email attribute.
133
+
134
+ ==== recipients(string_or_array_or_symbol_or_block)
135
+
136
+ Who will receive the e-mail. Can be a string or array or a symbol or a proc.
137
+
138
+ When a symbol is given, it will call a method on the form object with the same
139
+ name as the symbol. As a proc, it receives a mail form instance.
140
+
141
+ Both the proc and the symbol must return a string or an array. By default is nil.
142
+
143
+ ==== template(string_or_symbol_or_proc)
144
+
145
+ Allow you to set the template that is going to rendered. This allows you to have
146
+ several MailForm instances, using different templates.
147
+
148
+ ==== headers(hash)
149
+
150
+ Additional headers to your e-mail.
151
+
152
+ == I18n
153
+
154
+ All models, attributes and messages in MailForm can be used with localized.
155
+ Below is an I18n example file:
156
+
157
+ mail_form:
158
+ models:
159
+ contact_form: "Your site contact form"
160
+ attributes:
161
+ email: "E-mail"
162
+ telephone: "Telephone number"
163
+ message: "Sent message"
164
+ messages:
165
+ blank: "can not be blank"
166
+ invalid: "is not valid"
167
+ telephone: "must have eight digits"
168
+ request:
169
+ title: "Technical information about the user"
170
+ remote_ip: "IP Address"
171
+ user_agent: "Browser"
172
+
173
+ == Maintainers
174
+
175
+ * José Valim - http://github.com/josevalim
176
+ * Carlos Antonio - http://github.com/carlosantoniodasilva
177
+
178
+ == Contributors
179
+
180
+ * Andrew Timberlake - http://github.com/andrewtimberlake
181
+
182
+ == Bugs and Feedback
183
+
184
+ If you discover any bug, please use github issues tracker.
185
+
186
+ Copyright (c) 2009 Plataforma Tec http://blog.plataformatec.com.br/
data/Rakefile ADDED
@@ -0,0 +1,38 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+ require File.join(File.dirname(__FILE__), "lib", "mail_form", "version")
5
+
6
+ desc 'Run tests for MailForm.'
7
+ Rake::TestTask.new(:test) do |t|
8
+ t.pattern = 'test/**/*_test.rb'
9
+ t.verbose = true
10
+ end
11
+
12
+ desc 'Generate documentation for MailForm.'
13
+ Rake::RDocTask.new(:rdoc) do |rdoc|
14
+ rdoc.rdoc_dir = 'rdoc'
15
+ rdoc.title = 'MailForm'
16
+ rdoc.options << '--line-numbers' << '--inline-source'
17
+ rdoc.rdoc_files.include('README')
18
+ rdoc.rdoc_files.include('MIT-LICENSE')
19
+ rdoc.rdoc_files.include('lib/**/*.rb')
20
+ end
21
+
22
+ begin
23
+ require 'jeweler'
24
+ Jeweler::Tasks.new do |s|
25
+ s.name = "mail_form"
26
+ s.version = MailForm::VERSION
27
+ s.summary = "Send e-mail straight from forms in Rails with I18n, validations, attachments and request information."
28
+ s.email = "contact@plataformatec.com.br"
29
+ s.homepage = "http://github.com/plataformatec/mail_form"
30
+ s.description = "Send e-mail straight from forms in Rails with I18n, validations, attachments and request information."
31
+ s.authors = ['José Valim', 'Carlos Antônio']
32
+ s.files = FileList["[A-Z]*", "{lib,views}/**/*"]
33
+ end
34
+
35
+ Jeweler::GemcutterTasks.new
36
+ rescue LoadError
37
+ puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install jeweler"
38
+ end
data/lib/mail_form.rb ADDED
@@ -0,0 +1,32 @@
1
+ require 'mail_form/base'
2
+ require 'mail_form/dsl'
3
+ require 'mail_form/errors'
4
+ require 'mail_form/notifier'
5
+
6
+ class MailForm
7
+ extend MailForm::DSL
8
+
9
+ ACCESSORS = [ :form_attributes, :form_validatable, :form_subject,
10
+ :form_attachments, :form_recipients, :form_sender,
11
+ :form_captcha, :form_headers, :form_template, :form_appendable ]
12
+
13
+ DEFAULT_MESSAGES = { :blank => "can't be blank", :invalid => "is invalid" }
14
+
15
+ class_inheritable_reader *ACCESSORS
16
+ protected *ACCESSORS
17
+
18
+ # Initialize arrays and hashes
19
+ #
20
+ write_inheritable_array :form_captcha, []
21
+ write_inheritable_array :form_appendable, []
22
+ write_inheritable_array :form_attributes, []
23
+ write_inheritable_array :form_attachments, []
24
+ write_inheritable_hash :form_validatable, {}
25
+
26
+ headers({})
27
+ sender {|c| c.email }
28
+ subject{|c| c.class.human_name }
29
+ template 'contact'
30
+ end
31
+
32
+ MailForm::Notifier.template_root = File.join(File.dirname(__FILE__), '..', 'views')
@@ -0,0 +1,126 @@
1
+ class MailForm
2
+ attr_accessor :request
3
+
4
+ # Initialize assigning the parameters given as hash (just as in ActiveRecord).
5
+ #
6
+ # It also accepts the request object as second parameter which must be sent
7
+ # whenever :append is called.
8
+ #
9
+ def initialize(params={}, request=nil)
10
+ @request = request
11
+ params.each_pair do |attr, value|
12
+ self.send(:"#{attr}=", value)
13
+ end unless params.blank?
14
+ end
15
+
16
+ # In development, raises an error if the captcha field is not blank. This is
17
+ # is good to remember that the field should be hidden with CSS and shown only
18
+ # to robots.
19
+ #
20
+ # In test and in production, it returns true if aall captcha field are blank,
21
+ # returns false otherwise.
22
+ #
23
+ def spam?
24
+ form_captcha.each do |field|
25
+ next if send(field).blank?
26
+
27
+ if RAILS_ENV == 'development'
28
+ raise ScriptError, "The captcha field #{field} was supposed to be blank"
29
+ else
30
+ return true
31
+ end
32
+ end
33
+
34
+ return false
35
+ end
36
+
37
+ def not_spam?
38
+ !spam?
39
+ end
40
+
41
+ # To check if the form is valid, we run the validations.
42
+ #
43
+ # If the validation is true, we just check if the field is not blank. If it's
44
+ # a regexp, we check if it is not blank AND if the Regexp matches.
45
+ #
46
+ # You can have totally custom validations by sending a symbol. Then the method
47
+ # given as symbol will be called and then you cann hook your validations there.
48
+ #
49
+ def valid?
50
+ return false unless errors.empty?
51
+
52
+ form_validatable.each_pair do |field, validation|
53
+ next unless validation
54
+
55
+ if validation.is_a?(Symbol)
56
+ send(validation)
57
+ elsif send(field).blank?
58
+ errors.add(field, :blank)
59
+ elsif validation.is_a?(Regexp)
60
+ errors.add(field, :invalid) unless send(field) =~ validation
61
+ end
62
+ end
63
+
64
+ errors.empty?
65
+ end
66
+
67
+ def invalid?
68
+ !valid?
69
+ end
70
+
71
+ # Always return true so when using form_for, the default method will be post.
72
+ #
73
+ def new_record?
74
+ true
75
+ end
76
+
77
+ # Always return nil so when using form_for, the default method will be post.
78
+ #
79
+ def id
80
+ nil
81
+ end
82
+
83
+ # If is not spam and the form is valid, we send the e-mail and returns true.
84
+ # Otherwise returns false.
85
+ #
86
+ def deliver(run_validations=true)
87
+ if !run_validations || (self.not_spam? && self.valid?)
88
+ MailForm::Notifier.deliver_contact(self)
89
+ return true
90
+ else
91
+ return false
92
+ end
93
+ end
94
+ alias :save :deliver
95
+
96
+ # Add a human attribute name interface on top of I18n. If email is received as
97
+ # attribute, it will look for a translated name on:
98
+ #
99
+ # mail_form:
100
+ # attributes:
101
+ # email: E-mail
102
+ #
103
+ def self.human_attribute_name(attribute, options={})
104
+ I18n.translate("attributes.#{attribute}", options.merge(:default => attribute.to_s.humanize, :scope => [:mail_form]))
105
+ end
106
+
107
+ # Add a human name interface on top of I18n. If you have a model named
108
+ # MailForm, it will search for the localized name on:
109
+ #
110
+ # mail_form:
111
+ # models:
112
+ # contact_form: Contact form
113
+ #
114
+ def self.human_name(options={})
115
+ underscored = self.name.demodulize.underscore
116
+ I18n.translate("models.#{underscored}", options.merge(:default => underscored.humanize, :scope => [:mail_form]))
117
+ end
118
+
119
+ # Return the errors in this form. The object returned as the same API as the
120
+ # ActiveRecord one.
121
+ #
122
+ def errors
123
+ @errors ||= MailForm::Errors.new(self)
124
+ end
125
+
126
+ end
@@ -0,0 +1,171 @@
1
+ class MailForm
2
+ module DSL
3
+
4
+ protected
5
+
6
+ # Declare your form attributes. All attributes declared here will be appended
7
+ # to the e-mail, except the ones captcha is true.
8
+ #
9
+ # == Options
10
+ #
11
+ # * <tt>:validate</tt> - When true, validates the attributes can't be blank.
12
+ # When a regexp is given, check if the attribute matches is not blank and
13
+ # then if it matches the regexp.
14
+ #
15
+ # Whenever :validate is a symbol, the method given as symbol will be
16
+ # called. You can then add validations as you do in ActiveRecord (errors.add).
17
+ #
18
+ # * <tt>:attachment</tt> - When given, expects a file to be sent and attaches
19
+ # it to the e-mail. Don't forget to set your form to multitype.
20
+ #
21
+ # * <tt>:captcha</tt> - When true, validates the attributes must be blank
22
+ # This is a simple way to avoid spam
23
+ #
24
+ # == Examples
25
+ #
26
+ # class ContactForm < MailForm
27
+ # attributes :name, :validate => true
28
+ # attributes :email, :validate => /^([^@]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
29
+ # attributes :message
30
+ # attributes :type
31
+ # attributes :screenshot, :attachment => true, :validate => :interface_bug?
32
+ # attributes :nickname, :captcha => true
33
+ #
34
+ # def interface_bug?
35
+ # if type == 'Interface bug' && screenshot.nil?
36
+ # self.errors.add(:screenshot, "can't be blank when you are reporting an interface bug")
37
+ # end
38
+ # end
39
+ # end
40
+ #
41
+ def attribute(*accessors)
42
+ options = accessors.extract_options!
43
+
44
+ attr_accessor *accessors
45
+
46
+ if options[:attachment]
47
+ write_inheritable_array(:form_attachments, accessors)
48
+ elsif options[:captcha]
49
+ write_inheritable_array(:form_captcha, accessors)
50
+ else
51
+ write_inheritable_array(:form_attributes, accessors)
52
+ end
53
+
54
+ if options[:validate]
55
+ validations = {}
56
+ accessors.each{ |a| validations[a] = options[:validate] }
57
+
58
+ write_inheritable_hash(:form_validatable, validations)
59
+ end
60
+ end
61
+ alias :attributes :attribute
62
+
63
+ # Declares contact email sender. It can be a string or a proc or a symbol.
64
+ #
65
+ # When a symbol is given, it will call a method on the form object with
66
+ # the same name as the symbol. As a proc, it receives a simple form
67
+ # instance. By default is the class human name.
68
+ #
69
+ # == Examples
70
+ #
71
+ # class ContactForm < MailForm
72
+ # subject "My Contact Form"
73
+ # end
74
+ #
75
+ def subject(duck=nil, &block)
76
+ write_inheritable_attribute(:form_subject, duck || block)
77
+ end
78
+
79
+ # Declares contact email sender. It can be a string or a proc or a symbol.
80
+ #
81
+ # When a symbol is given, it will call a method on the form object with
82
+ # the same name as the symbol. As a proc, it receives a simple form
83
+ # instance. By default is:
84
+ #
85
+ # sender{ |c| c.email }
86
+ #
87
+ # This requires that your MailForm object have an email attribute.
88
+ #
89
+ # == Examples
90
+ #
91
+ # class ContactForm < MailForm
92
+ # # Change sender to include also the name
93
+ # sender { |c| %{"#{c.name}" <#{c.email}>} }
94
+ # end
95
+ #
96
+ def sender(duck=nil, &block)
97
+ write_inheritable_attribute(:form_sender, duck || block)
98
+ end
99
+ alias :from :sender
100
+
101
+ # Who will receive the e-mail. Can be a string or array or a symbol or a proc.
102
+ #
103
+ # When a symbol is given, it will call a method on the form object with
104
+ # the same name as the symbol. As a proc, it receives a simple form instance.
105
+ #
106
+ # Both the proc and the symbol must return a string or an array. By default
107
+ # is nil.
108
+ #
109
+ # == Examples
110
+ #
111
+ # class ContactForm < MailForm
112
+ # recipients [ "first.manager@domain.com", "second.manager@domain.com" ]
113
+ # end
114
+ #
115
+ def recipients(duck=nil, &block)
116
+ write_inheritable_attribute(:form_recipients, duck || block)
117
+ end
118
+ alias :to :recipients
119
+
120
+ # Additional headers to your e-mail.
121
+ #
122
+ # == Examples
123
+ #
124
+ # class ContactForm < MailForm
125
+ # headers { :content_type => 'text/html' }
126
+ # end
127
+ #
128
+ def headers(hash)
129
+ write_inheritable_hash(:form_headers, hash)
130
+ end
131
+
132
+ # Customized template for your e-mail, if you don't want to use default
133
+ # 'contact' template or need more than one contact form with different
134
+ # template layouts.
135
+ #
136
+ # When a symbol is given, it will call a method on the form object with
137
+ # the same name as the symbol. As a proc, it receives a simple form
138
+ # instance. Both method and proc must return a string with the template
139
+ # name. Defaults to 'contact'.
140
+ #
141
+ # == Examples
142
+ #
143
+ # class ContactForm < MailForm
144
+ # # look for a template in views/mail_form/notifier/my_template.erb
145
+ # template 'my_template'
146
+ # end
147
+ #
148
+ def template(new_template)
149
+ write_inheritable_attribute(:form_template, new_template)
150
+ end
151
+
152
+ # Values from request object to be appended to the contact form.
153
+ # Whenever used, you have to send the request object when initializing the object:
154
+ #
155
+ # @contact_form = ContactForm.new(params[:contact_form], request)
156
+ #
157
+ # You can get the values to be appended from the AbstractRequest
158
+ # documentation (http://api.rubyonrails.org/classes/ActionController/AbstractRequest.html)
159
+ #
160
+ # == Examples
161
+ #
162
+ # class ContactForm < MailForm
163
+ # append :remote_ip, :user_agent, :session, :cookies
164
+ # end
165
+ #
166
+ def append(*values)
167
+ write_inheritable_array(:form_appendable, values)
168
+ end
169
+
170
+ end
171
+ end
@@ -0,0 +1,59 @@
1
+ # Provides an Errors class similar with ActiveRecord ones.
2
+ #
3
+ # class ContactForm < MailForm
4
+ # attributes :name, :validate => true
5
+ # attributes :email, :validate => /^([^@]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
6
+ # attributes :message
7
+ # attributes :nickname, :captcha => true
8
+ # end
9
+ #
10
+ # When validating an attribute name as above, it will search for messages in
11
+ # the following order:
12
+ #
13
+ # mail_form.messages.name
14
+ # mail_form.messages.blank
15
+ #
16
+ # When validating email, it will search for:
17
+ #
18
+ # mail_form.messages.name
19
+ # mail_form.messages.invalid
20
+ #
21
+ # If the message is not available, it will output: "can't be blank" in the first
22
+ # case and "is invalid" in the second.
23
+ #
24
+ class MailForm
25
+ class Errors < Hash
26
+
27
+ def initialize(base, *args)
28
+ @base = base
29
+ super(*args)
30
+ end
31
+
32
+ alias :add :store
33
+ alias :count :size
34
+ alias :get :[]
35
+
36
+ def on(attribute)
37
+ attribute = attribute.to_sym
38
+ return nil unless get(attribute)
39
+
40
+ generate_message_for(attribute, get(attribute))
41
+ end
42
+ alias :[] :on
43
+
44
+ def full_messages
45
+ map do |attribute, message|
46
+ next if message.nil?
47
+ attribute = attribute.to_sym
48
+ "#{@base.class.human_attribute_name(attribute)} #{generate_message_for(attribute, message)}"
49
+ end.compact.reverse
50
+ end
51
+
52
+ protected
53
+
54
+ def generate_message_for(attribute, message)
55
+ I18n.t(attribute, :default => [ message, DEFAULT_MESSAGES[message] ], :scope => [:mail_form, :messages])
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,48 @@
1
+ # This is the class responsable to send the e-mails.
2
+ #
3
+ class MailForm
4
+ class Notifier < ActionMailer::Base
5
+
6
+ def contact(form)
7
+ @from = get_from_class_and_eval(form, :form_sender)
8
+ @subject = get_from_class_and_eval(form, :form_subject)
9
+ @recipients = get_from_class_and_eval(form, :form_recipients)
10
+ @template = get_from_class_and_eval(form, :form_template)
11
+
12
+ raise ScriptError, "You forgot to setup #{form.class.name} recipients" if @recipients.blank?
13
+ raise ScriptError, "You set :append values but forgot to give me the request object" if form.request.nil? && !form.class.form_appendable.blank?
14
+
15
+ @body['form'] = form
16
+ @body['subject'] = @subject
17
+
18
+ @sent_on = Time.now.utc
19
+ @headers = form.class.form_headers
20
+ @content_type = 'text/html'
21
+
22
+ form.class.form_attachments.each do |attribute|
23
+ value = form.send(attribute)
24
+ if value.respond_to?(:read)
25
+ attachment value.content_type.to_s do |att|
26
+ att.filename = value.original_filename
27
+ att.body = value.read
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ protected
34
+
35
+ def get_from_class_and_eval(form, method)
36
+ duck = form.class.send(method)
37
+
38
+ if duck.is_a?(Proc)
39
+ duck.call(form)
40
+ elsif duck.is_a?(Symbol)
41
+ form.send(duck)
42
+ else
43
+ duck
44
+ end
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,3 @@
1
+ module MailForm
2
+ VERSION = "1.0.0".freeze
3
+ end
data/test/base_test.rb ADDED
@@ -0,0 +1,110 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class MailFormBaseTest < ActiveSupport::TestCase
4
+
5
+ def setup
6
+ ActionMailer::Base.deliveries = []
7
+ end
8
+
9
+ def test_id_is_nil
10
+ assert_equal nil, ContactForm.new.id
11
+ end
12
+
13
+ def test_is_always_a_new_record
14
+ assert ContactForm.new.new_record?
15
+ end
16
+
17
+ def test_initialize_with_options
18
+ form = ContactForm.new(:name => 'José', :email => 'jose@my.email.com')
19
+ assert_equal 'José', form.name
20
+ assert_equal 'jose@my.email.com', form.email
21
+ end
22
+
23
+ def test_spam_is_true_when_captcha_field_is_set
24
+ form = ContactForm.new(:nickname => 'not_blank')
25
+ assert form.spam?
26
+ assert !form.not_spam?
27
+ end
28
+
29
+ def test_spam_is_false_when_captcha_field_is_not_set
30
+ form = ContactForm.new
31
+ assert !form.spam?
32
+ assert form.not_spam?
33
+ end
34
+
35
+ def test_is_not_valid_when_validatable_attributes_are_blank
36
+ form = ContactForm.new
37
+ assert !form.valid?
38
+ assert form.invalid?
39
+
40
+ assert_equal(2, form.errors.count)
41
+ assert_equal({:email=>:blank, :name=>:blank}, form.errors)
42
+ end
43
+
44
+ def test_is_not_valid_when_validatable_regexp_does_not_match
45
+ form = ContactForm.new(:name => 'Jose', :email => 'not_valid')
46
+ assert !form.valid?
47
+ assert form.invalid?
48
+
49
+ assert_equal(1, form.errors.count)
50
+ assert_equal({:email=>:invalid}, form.errors)
51
+ end
52
+
53
+ def test_is_valid_when_validatable_attributes_are_valid
54
+ form = ContactForm.new(:name => 'Jose', :email => 'is.valid@email.com')
55
+ assert form.valid?
56
+ assert !form.invalid?
57
+ end
58
+
59
+ def test_symbols_given_to_validate_are_called
60
+ form = ContactForm.new
61
+ assert form.instance_variable_get('@_callback_run').nil?
62
+ form.valid?
63
+ assert form.instance_variable_get('@_callback_run')
64
+ end
65
+
66
+ def test_deliver_is_false_when_is_a_spam
67
+ form = ContactForm.new(:name => 'Jose', :email => 'is.valid@email.com', :nickname => 'not_blank')
68
+ assert form.valid?
69
+ assert form.spam?
70
+ assert !form.deliver
71
+ end
72
+
73
+ def test_deliver_is_false_when_is_invalid
74
+ form = ContactForm.new(:name => 'Jose', :email => 'is.com')
75
+ assert form.invalid?
76
+ assert form.not_spam?
77
+ assert !form.deliver
78
+ end
79
+
80
+ def test_deliver_is_true_when_is_not_spam_and_valid
81
+ form = ContactForm.new(:name => 'Jose', :email => 'is.valid@email.com')
82
+ assert form.valid?
83
+ assert form.not_spam?
84
+ assert form.deliver
85
+ assert_equal 1, ActionMailer::Base.deliveries.size
86
+ end
87
+
88
+ def test_human_name_returns_a_humanized_name
89
+ assert_equal 'Contact form', ContactForm.human_name
90
+ end
91
+
92
+ def test_human_name_can_be_localized
93
+ I18n.backend.store_translations(:en, :mail_form => { :models => { :contact_form => 'Formulário de contato' } })
94
+ assert_equal 'Formulário de contato', ContactForm.human_name
95
+ end
96
+
97
+ def test_human_attribute_name_returns_a_humanized_attribute
98
+ assert_equal 'Message', ContactForm.human_attribute_name(:message)
99
+ end
100
+
101
+ def test_human_attribute_name_can_be_localized
102
+ I18n.backend.store_translations(:en, :mail_form => { :attributes => { :message => 'Mensagem' } })
103
+ assert_equal 'Mensagem', ContactForm.human_attribute_name(:message)
104
+ end
105
+
106
+ def teardown
107
+ I18n.reload!
108
+ end
109
+
110
+ end
@@ -0,0 +1,85 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class MailFormErrorsTest < ActiveSupport::TestCase
4
+
5
+ def test_errors_respond_to_some_hash_methods
6
+ assert ContactForm.new.errors.respond_to?(:each)
7
+ assert ContactForm.new.errors.respond_to?(:each_pair)
8
+ assert ContactForm.new.errors.respond_to?(:size)
9
+ end
10
+
11
+ def test_count_is_an_alias_to_size
12
+ errors = ContactForm.new.errors
13
+ assert_equal errors.size, errors.count
14
+ end
15
+
16
+ def test_on_returns_the_message_in_the_given_attribute
17
+ form = ContactForm.new(:email => 'not_valid')
18
+ form.valid?
19
+ assert_equal "can't be blank", form.errors.on(:name)
20
+ assert_equal "is invalid", form.errors.on(:email)
21
+ assert_equal nil, form.errors.on(:message)
22
+ end
23
+
24
+ def test_on_returns_a_default_localized_message_in_the_given_attribute
25
+ I18n.backend.store_translations(:en, :mail_form => { :messages => { :invalid => 'is not valid', :blank => 'should be filled' } })
26
+
27
+ form = ContactForm.new(:email => 'not_valid')
28
+ form.valid?
29
+
30
+ assert_equal "should be filled", form.errors.on(:name)
31
+ assert_equal "is not valid", form.errors.on(:email)
32
+ assert_equal nil, form.errors.on(:message)
33
+ end
34
+
35
+ def test_on_returns_an_attribute_localized_message_in_the_given_attribute
36
+ I18n.backend.store_translations(:en, :mail_form => { :messages => { :email => 'fill in the email', :name => 'fill in the name' } })
37
+
38
+ form = ContactForm.new(:email => 'not_valid')
39
+ form.valid?
40
+
41
+ assert_equal "fill in the name", form.errors.on(:name)
42
+ assert_equal "fill in the email", form.errors.on(:email)
43
+ assert_equal nil, form.errors.on(:message)
44
+ end
45
+
46
+ def test_array_like_option_acts_as_an_alias_for_on
47
+ form = ContactForm.new(:email => 'not_valid')
48
+ form.valid?
49
+ assert_equal "can't be blank", form.errors[:name]
50
+ assert_equal form.errors.on(:name), form.errors[:name]
51
+ assert_equal "is invalid", form.errors[:email]
52
+ assert_equal form.errors.on(:email), form.errors[:email]
53
+ assert_equal nil, form.errors[:message]
54
+ end
55
+
56
+ def test_get_returns_the_real_value_in_the_given_attribute
57
+ form = ContactForm.new(:email => 'not_valid')
58
+ form.valid?
59
+ assert_equal :blank, form.errors.get(:name)
60
+ assert_equal :invalid, form.errors.get(:email)
61
+ assert_equal nil, form.errors.get(:message)
62
+ end
63
+
64
+ def test_full_messages
65
+ form = ContactForm.new(:email => 'not_valid')
66
+ form.valid?
67
+
68
+ assert form.errors.full_messages.include?("Name can't be blank")
69
+ assert form.errors.full_messages.include?("Email is invalid")
70
+ end
71
+
72
+ def test_full_localized_messages
73
+ I18n.backend.store_translations(:en, :mail_form => { :messages => { :email => 'is not valid', :blank => 'should be filled' }, :attributes => { :email => 'E-mail' } })
74
+
75
+ form = ContactForm.new(:email => 'not_valid')
76
+ form.valid?
77
+
78
+ assert form.errors.full_messages.include?("Name should be filled")
79
+ assert form.errors.full_messages.include?("E-mail is not valid")
80
+ end
81
+
82
+ def teardown
83
+ I18n.reload!
84
+ end
85
+ end
@@ -0,0 +1,183 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class MailFormNotifierTest < ActiveSupport::TestCase
4
+
5
+ def setup
6
+ @form = ContactForm.new(:name => 'José', :email => 'my.email@my.domain.com', :message => 'Cool')
7
+
8
+ @request = ActionController::TestRequest.new
9
+ @valid_attributes = { :name => 'José', :email => 'my.email@my.domain.com', :message => "Cool\nno?" }
10
+ @advanced = AdvancedForm.new(@valid_attributes, @request)
11
+
12
+ test_file = ActionController::TestUploadedFile.new(File.join(File.dirname(__FILE__), 'test_file.txt'))
13
+ @with_file = FileForm.new(:name => 'José', :email => 'my.email@my.domain.com', :message => "Cool", :file => test_file)
14
+
15
+ @template = TemplateForm.new(@valid_attributes)
16
+
17
+ ActionMailer::Base.deliveries = []
18
+ end
19
+
20
+ def test_email_is_sent
21
+ @form.deliver
22
+ assert_equal 1, ActionMailer::Base.deliveries.size
23
+ end
24
+
25
+ def test_subject_defaults_to_class_human_name
26
+ @form.deliver
27
+ assert_equal 'Contact form', ActionMailer::Base.deliveries.first.subject
28
+ end
29
+
30
+ def test_subject_is_a_string
31
+ @advanced.deliver
32
+ assert_equal 'My Advanced Form', ActionMailer::Base.deliveries.first.subject
33
+ end
34
+
35
+ def test_sender_defaults_to_form_email
36
+ @form.deliver
37
+ assert_equal ['my.email@my.domain.com'], ActionMailer::Base.deliveries.first.from
38
+ end
39
+
40
+ def test_error_is_raised_when_recipients_is_nil
41
+ assert_raise ScriptError do
42
+ NullRecipient.new.deliver
43
+ end
44
+ end
45
+
46
+ def test_recipients_is_a_string
47
+ @form.deliver
48
+ assert_equal ['my.email@my.domain.com'], ActionMailer::Base.deliveries.first.to
49
+ end
50
+
51
+ def test_recipients_is_an_array
52
+ @advanced.deliver
53
+ assert_equal ['my.first@email.com', 'my.second@email.com'], ActionMailer::Base.deliveries.first.to
54
+ end
55
+
56
+ def test_recipients_is_a_symbold
57
+ @with_file.deliver
58
+ assert_equal ['contact_file@my.domain.com'], ActionMailer::Base.deliveries.first.to
59
+ end
60
+
61
+ def test_headers_is_a_hash
62
+ @advanced.deliver
63
+ assert_equal '<mypath>', ActionMailer::Base.deliveries.first.header['return-path'].to_s
64
+ end
65
+
66
+ def test_body_contains_subject
67
+ @form.deliver
68
+ assert_match /Contact form/, ActionMailer::Base.deliveries.first.body
69
+ end
70
+
71
+ def test_body_contains_attributes_values
72
+ @form.deliver
73
+ assert_match /José/, ActionMailer::Base.deliveries.first.body
74
+ assert_match /my.email@my.domain.com/, ActionMailer::Base.deliveries.first.body
75
+ assert_match /Cool/, ActionMailer::Base.deliveries.first.body
76
+ end
77
+
78
+ def test_body_contains_attributes_names
79
+ @form.deliver
80
+ assert_match /Name:/, ActionMailer::Base.deliveries.first.body
81
+ assert_match /Email:/, ActionMailer::Base.deliveries.first.body
82
+ assert_match /Message:/, ActionMailer::Base.deliveries.first.body
83
+ end
84
+
85
+ def test_body_contains_localized_attributes_names
86
+ I18n.backend.store_translations(:en, :mail_form => { :attributes => { :message => 'Sent message' } })
87
+ @form.deliver
88
+ assert_match /Sent message:/, ActionMailer::Base.deliveries.first.body
89
+ assert_no_match /Message:/, ActionMailer::Base.deliveries.first.body
90
+ end
91
+
92
+ def test_body_mail_format_messages_with_break_line
93
+ @form.deliver
94
+ assert_no_match /<p>Cool/, ActionMailer::Base.deliveries.first.body
95
+
96
+ @advanced.deliver
97
+ assert_match /<p>Cool/, ActionMailer::Base.deliveries.last.body
98
+ end
99
+
100
+ def test_body_does_not_append_request_if_append_is_not_called
101
+ @form.deliver
102
+ assert_no_match /Request information/, ActionMailer::Base.deliveries.first.body
103
+ end
104
+
105
+ def test_body_does_append_request_if_append_is_called
106
+ @advanced.deliver
107
+ assert_match /Request information/, ActionMailer::Base.deliveries.last.body
108
+ end
109
+
110
+ def test_request_title_is_localized
111
+ I18n.backend.store_translations(:en, :mail_form => { :request => { :title => 'Information about the request' } })
112
+ @advanced.deliver
113
+ assert_no_match /Request information/, ActionMailer::Base.deliveries.last.body
114
+ assert_match /Information about the request/, ActionMailer::Base.deliveries.last.body
115
+ end
116
+
117
+ def test_request_info_attributes_are_printed
118
+ @advanced.deliver
119
+ assert_match /Remote ip/, ActionMailer::Base.deliveries.last.body
120
+ assert_match /User agent/, ActionMailer::Base.deliveries.last.body
121
+ end
122
+
123
+ def test_request_info_attributes_are_localized
124
+ I18n.backend.store_translations(:en, :mail_form => { :request => { :remote_ip => 'IP Address' } })
125
+ @advanced.deliver
126
+ assert_match /IP Address/, ActionMailer::Base.deliveries.last.body
127
+ assert_no_match /Remote ip/, ActionMailer::Base.deliveries.last.body
128
+ end
129
+
130
+ def test_request_info_values_are_printed
131
+ @advanced.deliver
132
+ assert_match /0\.0\.0\.0/, ActionMailer::Base.deliveries.last.body
133
+ assert_match /Rails Testing/, ActionMailer::Base.deliveries.last.body
134
+ end
135
+
136
+ def test_request_info_hashes_are_print_inside_lis
137
+ @request.session = { :my => :session, :user => "data" }
138
+ @advanced.deliver
139
+ assert_match /<li>my: :session<\/li>/, ActionMailer::Base.deliveries.last.body
140
+ assert_match /<li>user: &quot;data&quot;<\/li>/, ActionMailer::Base.deliveries.last.body
141
+ end
142
+
143
+ def test_error_is_raised_when_append_is_given_but_no_request_is_given
144
+ assert_raise ScriptError do
145
+ @advanced.request = nil
146
+ @advanced.deliver
147
+ end
148
+ end
149
+
150
+ def test_form_with_file_includes_an_attachment
151
+ @with_file.deliver
152
+
153
+ #For some reason I need to encode the mail before the attachments array returns values
154
+ ActionMailer::Base.deliveries.first.to_s
155
+ assert_equal 1, ActionMailer::Base.deliveries.first.attachments.size
156
+ end
157
+
158
+ def test_form_with_file_does_not_output_attachment_as_attribute
159
+ @with_file.deliver
160
+ assert_no_match /File/, ActionMailer::Base.deliveries.first.body
161
+ end
162
+
163
+ def test_form_with_customized_template_raise_missing_template_if_not_found
164
+ assert_raise ActionView::MissingTemplate do
165
+ @template.deliver
166
+ end
167
+ end
168
+
169
+ def test_form_with_customized_template_render_correct_template
170
+ begin
171
+ default_template_root = MailForm::Notifier.template_root
172
+ MailForm::Notifier.template_root = File.join(File.dirname(__FILE__), 'views')
173
+ @template.deliver
174
+ assert_match 'Hello from my cystom template!', ActionMailer::Base.deliveries.last.body
175
+ ensure
176
+ MailForm::Notifier.template_root = default_template_root
177
+ end
178
+ end
179
+
180
+ def teardown
181
+ I18n.reload!
182
+ end
183
+ end
@@ -0,0 +1,64 @@
1
+ require 'test/unit'
2
+ require 'rubygems'
3
+
4
+ RAILS_ENV = ENV["RAILS_ENV"] = "test"
5
+
6
+ require 'active_support'
7
+ require 'active_support/test_case'
8
+ require 'action_mailer'
9
+ require 'action_controller/test_case'
10
+
11
+ ActionMailer::Base.delivery_method = :test
12
+
13
+ # Load respond_to before defining ApplicationController
14
+ require File.dirname(__FILE__) + '/../lib/mail_form.rb'
15
+
16
+ class ContactForm < MailForm
17
+ recipients 'my.email@my.domain.com'
18
+
19
+ attribute :name, :validate => true
20
+ attribute :email, :validate => /[^@]+@[^\.]+\.[\w\.\-]+/
21
+ attribute :nickname, :captcha => true
22
+ attributes :tellphone, :message, :validate => :callback
23
+
24
+ def callback
25
+ @_callback_run = true
26
+ end
27
+ end
28
+
29
+ class AdvancedForm < ContactForm
30
+ append :remote_ip, :user_agent, :session
31
+
32
+ recipients [ 'my.first@email.com', 'my.second@email.com' ]
33
+ subject 'My Advanced Form'
34
+ sender{|c| %{"#{c.name}" <#{c.email}>} }
35
+ headers 'return-path' => 'mypath'
36
+ end
37
+
38
+ class FileForm < ContactForm
39
+ attribute :file, :attachment => true, :validate => true
40
+ recipients :set_recipient
41
+
42
+ def set_recipient
43
+ if file
44
+ "contact_file@my.domain.com"
45
+ else
46
+ "contact@my.domain.com"
47
+ end
48
+ end
49
+ end
50
+
51
+ class NullRecipient < MailForm
52
+ sender 'my.email@my.domain.com'
53
+ end
54
+
55
+ class TemplateForm < ContactForm
56
+ template 'custom_template'
57
+ end
58
+
59
+ # Needed to correctly test an uploaded file
60
+ class ActionController::TestUploadedFile
61
+ def read
62
+ @tempfile.read
63
+ end
64
+ end
@@ -0,0 +1,30 @@
1
+ <h4 style="text-decoration:underline"><%=h @subject %></h4>
2
+
3
+ <% @form.class.form_attributes.each do |attribute|
4
+ value = @form.send(attribute)
5
+ next if value.blank? %>
6
+
7
+ <p><b><%= @form.class.human_attribute_name(attribute) %>:</b>
8
+ <%= value.include?("\n") ? simple_format(h(value)) : h(value) %></p>
9
+ <% end %>
10
+
11
+ <% unless @form.class.form_appendable.blank? %>
12
+ <br /><h4 style="text-decoration:underline"><%= I18n.t :title, :scope => [ :mail_form, :request ], :default => 'Request information' %></h4>
13
+
14
+ <% @form.class.form_appendable.each do |attribute|
15
+ value = @form.request.send(attribute)
16
+
17
+ value = if value.is_a?(Hash) && !value.empty?
18
+ content_tag(:ul, value.to_a.map{|k,v| content_tag(:li, h("#{k}: #{v.inspect}")) }.join("\n"), :style => "list-style:none;")
19
+ elsif value.is_a?(String)
20
+ h(value)
21
+ else
22
+ h(value.inspect)
23
+ end
24
+ %>
25
+
26
+ <p><b><%= I18n.t attribute, :scope => [ :mail_form, :request ], :default => attribute.to_s.humanize %>:</b>
27
+ <%= value.include?("\n") ? simple_format(value) : value %></p>
28
+ <% end %>
29
+ <br />
30
+ <% end %>
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mail_form
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - "Jos\xC3\xA9 Valim"
8
+ - "Carlos Ant\xC3\xB4nio"
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2009-12-24 00:00:00 +01:00
14
+ default_executable:
15
+ dependencies: []
16
+
17
+ description: Send e-mail straight from forms in Rails with I18n, validations, attachments and request information.
18
+ email: contact@plataformatec.com.br
19
+ executables: []
20
+
21
+ extensions: []
22
+
23
+ extra_rdoc_files:
24
+ - README.rdoc
25
+ files:
26
+ - CHANGELOG
27
+ - MIT-LICENSE
28
+ - README.rdoc
29
+ - Rakefile
30
+ - lib/mail_form.rb
31
+ - lib/mail_form/base.rb
32
+ - lib/mail_form/dsl.rb
33
+ - lib/mail_form/errors.rb
34
+ - lib/mail_form/notifier.rb
35
+ - lib/mail_form/version.rb
36
+ - views/mail_form/notifier/contact.erb
37
+ has_rdoc: true
38
+ homepage: http://github.com/plataformatec/mail_form
39
+ licenses: []
40
+
41
+ post_install_message:
42
+ rdoc_options:
43
+ - --charset=UTF-8
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: "0"
51
+ version:
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ version:
58
+ requirements: []
59
+
60
+ rubyforge_project:
61
+ rubygems_version: 1.3.5
62
+ signing_key:
63
+ specification_version: 3
64
+ summary: Send e-mail straight from forms in Rails with I18n, validations, attachments and request information.
65
+ test_files:
66
+ - test/base_test.rb
67
+ - test/errors_test.rb
68
+ - test/notifier_test.rb
69
+ - test/test_helper.rb