griddler 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,10 +1,12 @@
1
+
1
2
  Griddler
2
3
  ========
3
4
 
5
+ ### Receive emails in your Rails app
6
+
4
7
  Griddler is a Rails engine (full plugin) that provides an endpoint for the
5
- [Sendgrid parse
6
- api](http://sendgrid.com/docs/API%20Reference/Webhooks/parse.html) that hands
7
- off a built email object to a class implemented by you.
8
+ [Sendgrid parse api](http://sendgrid.com/docs/API%20Reference/Webhooks/parse.html)
9
+ that hands off a built email object to a class implemented by you.
8
10
 
9
11
  Installation
10
12
  ------------
@@ -15,10 +17,10 @@ Add griddler to your application's Gemfile and run `bundle install`:
15
17
  gem 'griddler'
16
18
  ```
17
19
 
18
- Griddler comes with a default endpoint that will be displayed at the bottom of
19
- the output of `rake routes`. If there is a previously defined route that matches
20
- `/email_processor`–or you would like to rename the matched path–you may
21
- add the route to the desired position in routes.rb with the following:
20
+ Griddler comes with a default endpoint that will be displayed at the bottom
21
+ of the output of `rake routes`. If there is a previously defined route that
22
+ matches `/email_processor`–or you would like to rename the matched path–you
23
+ may add the route to the desired position in routes.rb with the following:
22
24
 
23
25
  ```ruby
24
26
  match '/email_processor' => 'griddler/emails#create', via: :post
@@ -28,70 +30,95 @@ Defaults
28
30
  --------
29
31
 
30
32
  By default Griddler will look for a class to be created in your application
31
- called EmailProcessor with a class method implemented named process, taking in
32
- one argument (presumably `email`). For example, in `./lib/email_processor.rb`:
33
+ called EmailProcessor with a class method implemented named process, taking
34
+ in one argument (presumably `email`). For example, in `./lib/email_processor.rb`:
33
35
 
34
36
  ```ruby
35
37
  class EmailProcessor
36
38
  def self.process(email)
37
- # all of your application-specific code here - creating models, processing
38
- # reports, etc
39
+ # all of your application-specific code here - creating models,
40
+ # processing reports, etc
39
41
  end
40
42
  end
41
43
  ```
42
44
 
43
- The contents of the `email` object passed into your process method is a hash
44
- containing:
45
+ The contents of the `email` object passed into your process method is an object
46
+ that responds to:
45
47
 
46
- * `:to`
47
- * `:from`
48
- * `:subject`
49
- * `:body`
48
+ * `.to`
49
+ * `.from`
50
+ * `.subject`
51
+ * `.body`
52
+ * `.raw_body`
50
53
 
51
54
  Each of those has some sensible defaults.
52
55
 
53
- `:from` and `:subject` will contain the obvious values found in the email, the
54
- raw from and subject values.
56
+ `.from`, `.raw_body` and `.subject` will contain the obvious values found in the email, the raw values from those fields.
55
57
 
56
- `:body` will contain the full contents of the email body **unless** there is a
58
+ `.body` will contain the full contents of the email body **unless** there is a
57
59
  line in the email containing the string `-- Reply ABOVE THIS LINE --`. In that
58
- case `:body` will contain everything before that line.
60
+ case `.body` will contain everything before that line.
59
61
 
60
- `:to` will contain all of the text before the email's "@" character. We've found
61
- that this is the most often use portion of the email address and consider it to
62
+ `.to` will contain all of the text before the email's "@" character. We've found
63
+ that this is the most often used portion of the email address and consider it to
62
64
  be the token we'll key off of for interaction with our application.
63
65
 
64
66
  Configuration Options
65
67
  ---------------------
66
68
 
67
- An initializer can be created to control some of the options in Griddler.
68
- Defaults are shown below with sample overrides following. In
69
- `config/initializer/griddler.rb`:
69
+ An initializer can be created to control some of the options in Griddler. Defaults
70
+ are shown below with sample overrides following. In `config/initializer/griddler.rb`:
70
71
 
71
72
  ```ruby
72
73
  Griddler.configure do |config|
73
- config.handler_class = EmailProcessor # MyEmailProcessor
74
- config.handler_method = :process # :go
75
- config.raw_body = false # true
74
+ config.processor_class = EmailProcessor # MyEmailProcessor
76
75
  config.to = :token # :raw, :email, :hash
77
76
  # :raw => 'AppName <s13.6b2d13dc6a1d33db7644@mail.myapp.com>'
78
77
  # :email => 's13.6b2d13dc6a1d33db7644@mail.myapp.com'
79
78
  # :token => 's13.6b2d13dc6a1d33db7644'
80
79
  # :hash => { raw: '', email: '', token: '', host: '' }
81
- config.reply_delimeter = '-- REPLY ABOVE THIS LINE --'
80
+ config.reply_delimiter = '-- REPLY ABOVE THIS LINE --'
82
81
  end
83
82
  ```
84
83
 
85
- * `config.handler_class` change the class Griddler will use to handle your
86
- incoming emails.
87
- * `config.handler_method` change the class method called on
88
- `config.handler_class`.
89
- * `config.reply_delimeter` change the string searched for that will split your
90
- body.
91
- * `config.raw_body` use the full email body whether or not
92
- `config.reply_delimeter` is set.
93
- * `config.to` change the format of the returned value for the `:to` key in the
94
- email object. `:hash` will return all options within a (surprise!) - hash.
84
+ * `config.processor_class` change the class Griddler will use to handle your incoming emails.
85
+ * `config.reply_delimiter` change the string searched for that will split your body.
86
+ * `config.to` change the format of the returned value for the `:to` key in
87
+ the email object. `:hash` will return all options within a -- (surprise!) -- hash.
88
+
89
+ Testing In Your App
90
+ -------------------
91
+
92
+ You may want to create a factory for when testing the integration of Griddler into
93
+ your application. If you're using factory_girl this can be accomplished with the
94
+ following sample factory.
95
+
96
+ ```ruby
97
+ factory :email, class: OpenStruct do
98
+ to 'email-token'
99
+ from 'user@email.com'
100
+ subject 'email subject'
101
+ body 'Hello!'
102
+ attachments {[]}
103
+
104
+ trait :with_attachment do
105
+ attachments {[
106
+ ActionDispatch::Http::UploadedFile.new({
107
+ filename: 'img.png',
108
+ type: 'image/png',
109
+ tempfile: File.new("#{File.expand_path File.dirname(__FILE__)}/fixtures/img.png")
110
+ })
111
+ ]}
112
+ end
113
+ end
114
+ ```
115
+
116
+ Bear in mind, if you plan on using the :with_attachment trait, that this
117
+ example assumes your factories are in spec/factories.rb and you have
118
+ an image file in spec/fixtures/
119
+
120
+ To use it in your test(s) just build with `email = build(:email)`
121
+ or `email = build(:email, :with_attachment)`
95
122
 
96
123
  More Information
97
124
  ----------------
data/lib/griddler.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'griddler/errors'
1
2
  require 'griddler/engine'
2
3
  require 'griddler/email'
3
4
  require 'griddler/email_format'
@@ -18,22 +18,14 @@ module Griddler
18
18
  end
19
19
 
20
20
  class Configuration
21
- attr_accessor :handler_class, :handler_method, :raw_body, :reply_delimiter, :to
21
+ attr_accessor :processor_class, :reply_delimiter, :to
22
22
 
23
23
  def to
24
24
  @to ||= :token
25
25
  end
26
26
 
27
- def handler_class
28
- @handler_class ||= EmailProcessor
29
- end
30
-
31
- def handler_method
32
- @handler_method ||= :process
33
- end
34
-
35
- def raw_body
36
- @raw_body ||= false
27
+ def processor_class
28
+ @processor_class ||= EmailProcessor
37
29
  end
38
30
 
39
31
  def reply_delimiter
@@ -1,18 +1,19 @@
1
- require 'iconv'
1
+ require 'htmlentities'
2
2
 
3
3
  class Griddler::Email
4
- attr_accessor :to, :from, :body, :subject, :attachments
4
+ include ActionView::Helpers::SanitizeHelper
5
+ attr_accessor :to, :from, :body, :raw_body, :subject, :attachments
5
6
 
6
7
  def initialize(params)
7
8
  @to = extract_address(params[:to], config.to)
8
9
  @from = extract_address(params[:from], :email)
9
10
  @subject = params[:subject]
10
- @body = raw_or_extracted_body(params)
11
+ @body = extract_body(params)
12
+ @raw_body = params[:text] || params[:html]
11
13
  @attachments = extract_attachments(params)
12
14
 
13
- handler_class = config.handler_class
14
- handler_method = config.handler_method
15
- handler_class.send(handler_method, self)
15
+ processor_class = config.processor_class
16
+ processor_class.process(self)
16
17
  end
17
18
 
18
19
  private
@@ -41,20 +42,26 @@ class Griddler::Email
41
42
  attachment_files
42
43
  end
43
44
 
44
- def extract_body(body_text, charsets)
45
+ def extract_body(params)
46
+ body_text = text_or_sanitized_html(params)
47
+ charsets = params[:charsets]
48
+
45
49
  if charsets.present?
46
50
  charsets = ActiveSupport::JSON.decode(charsets)
47
- body_text = Iconv.new('utf-8', charsets['text']).iconv(body_text)
51
+ body_text = body_text.encode('UTF-8', invalid: :replace,
52
+ undef: :replace, replace: '').force_encoding('UTF-8')
48
53
  end
49
54
 
50
55
  EmailParser.extract_reply_body(body_text)
51
56
  end
52
57
 
53
- def raw_or_extracted_body(params)
54
- if config.raw_body
55
- @body = params[:text]
58
+ def text_or_sanitized_html(params)
59
+ if params.key? :text
60
+ params[:text]
61
+ elsif params.key? :html
62
+ HTMLEntities.new.decode(strip_tags(params[:html]))
56
63
  else
57
- @body = extract_body(params[:text], params[:charsets])
64
+ raise Griddler::Errors::EmailBodyNotFound
58
65
  end
59
66
  end
60
67
  end
@@ -26,9 +26,9 @@ module EmailParser
26
26
  body.split(delimeter).first.
27
27
  split(/^\s*[-]+\s*Original Message\s*[-]+\s*$/).first.
28
28
  split(/^\s*--\s*$/).first.
29
+ gsub(/On.*wrote:/, '').
29
30
  split(/[\r]*\n/).reject do |line|
30
31
  line =~ /^\s*>/ ||
31
- line =~ /^\s*On.*wrote:$/ ||
32
32
  line =~ /^\s*Sent from my /
33
33
  end.
34
34
  join("\n").
@@ -0,0 +1,9 @@
1
+ module Griddler
2
+ class Error < StandardError
3
+ end
4
+
5
+ module Errors
6
+ class EmailBodyNotFound < Griddler::Error
7
+ end
8
+ end
9
+ end
@@ -1,3 +1,3 @@
1
1
  module Griddler
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: griddler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2012-11-10 00:00:00.000000000 Z
14
+ date: 2013-01-21 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: rails
@@ -30,14 +30,14 @@ dependencies:
30
30
  - !ruby/object:Gem::Version
31
31
  version: 3.2.0
32
32
  - !ruby/object:Gem::Dependency
33
- name: sqlite3
33
+ name: htmlentities
34
34
  requirement: !ruby/object:Gem::Requirement
35
35
  none: false
36
36
  requirements:
37
37
  - - ! '>='
38
38
  - !ruby/object:Gem::Version
39
39
  version: '0'
40
- type: :development
40
+ type: :runtime
41
41
  prerelease: false
42
42
  version_requirements: !ruby/object:Gem::Requirement
43
43
  none: false
@@ -61,6 +61,22 @@ dependencies:
61
61
  - - ! '>='
62
62
  - !ruby/object:Gem::Version
63
63
  version: '0'
64
+ - !ruby/object:Gem::Dependency
65
+ name: sqlite3
66
+ requirement: !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ! '>='
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ type: :development
73
+ prerelease: false
74
+ version_requirements: !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ! '>='
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
64
80
  description:
65
81
  email:
66
82
  - cjaysson@gmail.com
@@ -77,6 +93,7 @@ files:
77
93
  - lib/griddler/email_format.rb
78
94
  - lib/griddler/email_parser.rb
79
95
  - lib/griddler/engine.rb
96
+ - lib/griddler/errors.rb
80
97
  - lib/griddler/version.rb
81
98
  - lib/griddler.rb
82
99
  - lib/tasks/griddler_tasks.rake
@@ -96,12 +113,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
96
113
  - - ! '>='
97
114
  - !ruby/object:Gem::Version
98
115
  version: '0'
116
+ segments:
117
+ - 0
118
+ hash: -3729493469419997719
99
119
  required_rubygems_version: !ruby/object:Gem::Requirement
100
120
  none: false
101
121
  requirements:
102
122
  - - ! '>='
103
123
  - !ruby/object:Gem::Version
104
124
  version: '0'
125
+ segments:
126
+ - 0
127
+ hash: -3729493469419997719
105
128
  requirements: []
106
129
  rubyforge_project:
107
130
  rubygems_version: 1.8.24