Review of selected topics ActionMailer Top 10 RoR coding pitfalls - PowerPoint PPT Presentation

1 / 33
About This Presentation
Title:

Review of selected topics ActionMailer Top 10 RoR coding pitfalls

Description:

Create view(s) & send email. To send a plain text email (from any controller method) ... One way: manually cat a raw email to the receive script ... – PowerPoint PPT presentation

Number of Views:105
Avg rating:3.0/5.0
Slides: 34
Provided by: radlabCs
Category:

less

Transcript and Presenter's Notes

Title: Review of selected topics ActionMailer Top 10 RoR coding pitfalls


1
Review of selected topicsActionMailerTop 10 RoR
coding pitfalls
  • CS 98-10/CS 198-10
  • Web 2.0 Programming Using Ruby on Rails
  • Armando Fox

2
Today...
  • Review authentication
  • Review style tips
  • Using ActionMailer to send receive email
  • Top 10 pitfalls programming in RoR
  • (Next time REST)

3
Example flow authentication
  • Ties together several key Rails features
  • form submission
  • virtual attributes
  • the session
  • filters
  • Strategy
  • special key sessionuserid will contain users
    (primary key) ID if logged in, nil otherwise
  • users password stored encrypted password
    entered on form encrypted compared

4
password virtual attribute
  • Old, bad way
  • Login handling code in controller does
    encryption, DB access, etc.
  • New, RoR way
  • encryption is an implementation detail of the
    user password gt belongs in User model
  • Think in terms of what controller methods we
    would like
  • set users password to p (e.g., when signing up
    new user or when user changes own password)
  • attempt login of user u with password p
  • Question where (model vs. controller) does the
    code go that manipulates sessionuid? Why?

5
Making a virtual attribute
  • For password, want to be able to set it but not
    (generally) read it. So, in the User model....
  • call the underlying DB field encrypted_password
    (or similar)
  • def password(pass) ... end declares that
    therelets us write a method password that in
    reality sets the value of encrypted_password
  • attr_protected encrypted_password(optional)
    prevents code outside the model from directly
    setting encrypted_password eg as result of mass
    assignment from form submission
  • Q what happens if (outside model) we try to
    dereference u.password ?

6
Mechanics of password setter
  • Given a plain-text password, set the underlying
    DB field hashed_password to an encrypted value
  • hint use self.encrypted_pass for assignment
  • hint salt the passwords
  • hint require 'digest/sha1' and use SHA1Digest
    to do simple encryption

7
Mechanics of authentication function
  • One approach return User object, or string
    describing error
  • caller uses kind_of?(User) to distinguish
  • More opaque return User model, or nil
  • Auth function will take username u plaintext
    password p...
  • encrypt password with users salt
  • compare encrypted password
  • return something

8
Enforcing must be logged in
  • Remember controller filters?
  • take no args, but can access controller object
    instance session
  • must return true or false
  • before_filter is_logged_in, only gt ... (or
    except gt ...)
  • define filter in application.rb
  • def is_logged_in
  • User.find_by_id(sessionuid).kind_of?(User)
  • end
  • Caution!! Bad!! why?
  • def is_logged_in
  • User.find_by_id(sessionuid).priv_level gt 0
  • end

9
When to log out?
  • Recall log out remove uid key from session
  • sessionuid nil
  • also consider ActionControllerBase.reset_session
  • One possibility Log me out button
  • Another possibility inactivity timeout of n
    seconds
  • def is_logged_in
  • if (User.find_by_id(sessionuid).kind_of?(User
    ))
  • if (Time.now-(sessionlast_action
    1.day.ago))
  • lt SESSION_TIMEOUT)
  • sessionlast_action Time.now
  • end
  • end
  • end

10
Dont put code in views
  • Keep code out of your views as much as possible
  • Some code can usually go to model
  • e.g. different ways of displaying (aspects of) a
    model object
  • a good litmus test options_from_collection_for_se
    lect
  • Some code belongs in controller
  • e.g. sort, filter, or arrange a collection of
    items that will be passed to a view
  • compromise for sorting include Comparable module
    and define instance method ltgt(other)

11
Know your helpers
  • A lot of helpers are available for your views
  • number_to_currency, number_to_percentage,
    number_to_phone
  • cycle(), reset_cycle()
  • distance_of_time_in_words_to_now()
  • date.to_formatted_s(short long db)
  • options_for_select, options_from_collection_for_se
    lect
  • Create your own helpers for common tasks
  • app/helpers/foo_helper.rb for model/view-specific
    helpers
  • app/helpers/application_helper.rb for generally
    useful stuff

12
Action Mailer
  • Really 2 different sets of tasks
  • your app sends email ActionMailer
  • your app receives processes incoming email
    script/runner
  • Why would you want to do this?
  • Email confirmations, etc. to users
  • Email error reports from exceptions to yourself
    (ExceptionNotifiable)

13
Action Mailer Architecture
  • Set of methods for sending related types of email
    is a model (without underlying DB table)
  • Email message template view
  • ActionMailer automatically provides a deliver_foo
    method for foo template
  • deliver_ methods actually interact with
    underlying platform to deliver mail
  • Steps
  • check ActionMailer configuration on your hosting
    site
  • create the model
  • create view templates (i.e. the actual messages)
  • test it

14
ActionMailer configuration
  • config/environment.rb
  • config.frameworks which frameworks get loaded
    (ActionController, ActionMailer,
    ActionWebService)
  • Comment out the line that removes ActionMailer
    and ActionWebService
  • config/environments/testing,development.rb
  • config.action_mailer.raise_delivery_errors true
  • config.action_mailer.delivery_method test
  • config.action_mailer.default_charset 'utf-8'
  • config/environments/production.rb
  • config.action_mailer.delivery_method smtp
  • config.action_mailer.server_settings domain
    gt 'berkeley.edu',port gt 25, address gt
    'localhost'

15
Create model
  • ruby script/generate mailer OrderMailer
    new_account confirm_password
  • Generates app/models/order_mailer.rb with method
    templates for new_account and confirm_password
  • default methods take optional sent_at parameter,
    but thats up to you
  • each method expects a corresponding view, e.g.
    new_account method gt views/order_mailer/new_accou
    nt.rhtml
  • For each method foo, you call deliver_foo to send
    the corresponding email

16
Model methods one per type of email
  • Model instance variables email metadata
  • _at_bcc, _at_cc, _at_from, _at_subject, _at_recipients... used
    to fill in email headers
  • Instance variable _at_body defines variables that
    will be available in the rhtml message body
    template
  • _at_body new_password gt newpass,
             nickname gt user.alias
  • ...makes variables _at_new_password _at_nickname
    available in .rhtml view
  • Caveat if you want (eg) _at_recipient available in
    rhtml view, have to define it as a key in _at_body
  • _at_bodyrecipient _at_recipient

17
Create view(s) send email
  • To send a plain text email (from any controller
    method)
  • Create .rhtml templates free of HTML markup
  • Note, it will render without layouts
  • OK to use lt gt, partials, etc.
  • deliver_new_account(args)
  • that's it!

18
Sending HTML email
  • Create .rhtml templates containing HTML
  • Note, will still render without layouts
  • Note, JavaScript etc. probably wont work
  • OK to include GIF bugs, etc.
  • Extra step set MIME type to text/html before
    send
  • in your controller method, instead of deliver_foo
    use
  • emailOrderMailer.create_confirm_password(newpass)
  • email.set_content_type('text/html')
  • OrderMailer.deliver(email)

19
Testing email
  • When testing, ActionMailer appends each generated
    email to array ActionMailerBase.deliveries
  • So in your functional test...
  • _at_emails ActionMailerBase.deliveries
  • _at_emails.clear empty the array
  • ...in your test case code...
  • some action that generates confirmation email
  • post(place_order, idgt_at_user)
  • assert_equal 1, _at_emails.size
  • assert_equal "you_at_berkeley.edu", _at_emails0.to0
  • assert_match /confirmation/, _at_emails0.subject
  • assert_match /confirmed/, _at_emails0.body
  • _at_emails.clear optional but useful...why??

20
Caveats
  • on many ISPs, _at_from must match delivery settings
    of ActionMailer (standard anti-spam measure)
  • some ISPs only check domain, others also check
    username
  • when rendering partials from mailer .rhtml views,
    need to explicitly give path to partial
  • Protect calls to deliver_ using rescue, e.g.
  • begin
  • deliver_new_password(user.login, newpass)
  • flashnotice "Your password was emailed to
    user.login."
  • rescue Exception gt e
  • flashnotice "Error emailing user.login
    " e.message
  • end

21
ExceptionNotifiable a neat ActionMailer plugin
  • install the plugin
  • script/plugin install lturlgt
  • in your ApplicationController
  • include ExceptionNotifiable
  • in environments/production.rb
  • config.after_initialize do
  • ExceptionNotifier.sender_address
  • "errors_at_myapp.rorclass.org"
  • ExceptionNotifier.exception_recipients
  • "you_at_cs.berkeley.edu"
  • end
  • Works by hooking rescue_action_in_public, with
    which you should also be familiar

22
Receiving email
  • ActionMailer class method receive(raw_email)
  • converts a raw email message to TMailMail
    object
  • passes that to instance method receive() in your
    class
  • create incoming email handler class
  • subclass of ActionMailerBase
  • receive() instance method takes TMailMail
    object
  • 2. arrange to route incoming emails to the
    receive class method
  • intercept the email (different ISPs have
    different methods, well publish method for
    rorclass.org)
  • use script/runner runs a single command in the
    context of your app
  • script/runner 'MyIncomingEmailHandler.receive(STDI
    N.read)'

23
TMailMail useful methods
  • def receive(em)
  • if em.has_attachments?
  • att em.attachments Attachments
  • first_att att0
  • fn first_att.original_filename
  • puts "original attachment name was fn"
  • att_contents first_att.read
  • behaves like an IO object
  • end
  • from em.from0
  • recipients em.to an Enumerable of
    recipients
  • body em.body
  • ....

24
Receiving emailcaveats
  • Script has no UI when it runsneeds way to
    report errors
  • provide full pathname to script/runner
  • Protect everything using rescue
  • Have script generate an email report to you when
    it finishes
  • How to test?
  • One way manually cat a raw email to the receive
    script
  • cat file /home/me/rails/myapp/script/runner
    'IncomingEmailHandler.receive(STDIN.read)'

25
Top 10? RoR pitfalls
  • Confusing Ruby syntax with Javascript -
    attributes vs method calls
  • confusing _at_instance vars with local vars in
    views/partials (uninitailized instance vars don't
    throw exceptions)
  • using button_to and other constructs inside a
    form_tag
  • using disable_with on a form button, and then
    relying on button's name in your controller
    method (when multiple submit buttons on a form)

26
Top 10 cont.
  • forgetting that foo.bar will throw a violent
    exception if foo is nil (for strings, can use
    foo.blank? otherwise can define your own
    "helper" methods)
  • wrong use of braces in functions that take
    multiple hashes as args, last of which is
    optionalfoo agtb, cgtd will parse
    asfoo(agtb,cgtd) even if you
    meanfoo(agtb,cgtd)(especially problematic
    in link_to_remote, link_to and similar funcs)

27
Top 10 cont.
  • Not checking for exceptions when saving a parent
    object in an association (child object(s) will
    silently not be saved)
  • Confusing lt gt and lt gt in views. Test does
    it need to appear in HTML?lt javascript_include_
    tag defaults gt
  • your favorite here
  • your favorite here

28
CalNet authentication
  • Why?
  • Use existing Calnet ID/password rather than
    rolling your own
  • Limit access to your app to specific Calnet IDs
  • Access web services that require Calnet
    authentication

29
How CalNet auth works
  • calnet.berkeley.edu/developers
  • Need to download Rails plugin
  • https//calnet.berkeley.edu/developers/downloads/C
    alNetAwsRuby.html

30
CalNet Auth and Rails
  • On your apps authentication/login page
  • action points to CalNet Authentication Web Server
    (AWS) https//net-auth.berkeley.edu/cgi-bin/krbaw
    s-v3
  • URL-embedded parameter AppName contains name of
    your app (user will see when authenticating)
  • URL-embedded parameter AppentryURL contains URL
    of real form submission back to your app
  • CalNet will post back to your app once user has
    successfully authenticated

31
Back in your app...
  • Your app gets post-back from Calnet
  • You read the Calnet public key (from a local file
    youve previously grabbed)
  • You construct a AwsV3Verifier object using that
    public key
  • You pass the parameters AWSSignatureBase64 and
    AWSAuthToken to verifier (these came to you from
    CalNet AWS)
  • You get back an aws object (or an exception if
    authentication fails)

32
Using the AWS object
  • aws.expired? gt CalNet token has expired
  • aws.test_id? gt valid ID that is supposed to be
    used for testing only
  • aws.uid gt CalNet ID of authenticated person

33
Example Code
Write a Comment
User Comments (0)
About PowerShow.com