Channels
Displaying page 81
Posted 8 months ago at lindsaar.net - a different view

The past month has seen a flurry in activity on the Mail gem but I just pushed 2.0.3 to GemCutter, it is quite a release!

If you haven’t heard of mail, you can get some background here, here, here and here

Mail is a very Ruby way to handle emails.

Version 2.0.3 is the first gem release I have in the last couple of weeks, this is because I went through and (in good BDD style) refactored major parts of the Mail gem so it handles better.

Some of the major things were:

SMTP Delivery Agent revamped

I went through the SMTP delivery agent and cleaned it up, also adding examples for how you use Mail with GMail and MobileMe so there is no more guess work here.

Delivery agents are now instance based

This means that each mail object that you instantiate can have its own delivery method. Why is this important?

Well, say you are writing a web based email client for multiple users, but each user has their own SMTP hosts, when you make a mail object for that user, you could assign the delivery method for that user to that mail object, then delivery is just calling .deliver! on the mail object, and away it goes.

There is still default class wide settings for all the major delivery agents (SMTP, Sendmail and File) however, you can now over ride these.

mail1 = Mail.new
mail1.delivery_method #=> #<Mail::SMTP:0x101381c18 @setting
mail2 = Mail.new
mail2.delivery_method :sendmail
mail2.delivery_method #=> #<Mail::Sendmail:0x101381c18 @setting
mail1.delivery_method #=> #<Mail::SMTP:0x101381c18 @setting

Attachments are now just parts

Before, an Attachment had its own object type in Mail. This was nice and all, but was just added cruft that got in there during the BDD cycle. I ripped out the entire attachment class, and an attachment is now just a plain old Mail::Part. This makes the code simpler, which is good for everyone.

Mail also now has a very cool attachments API:

mail = Mail.new
mail.attachments #=> []
mail.attachments['filename.jpg'] = File.read('filename.jpg')
mail.attachments['file.pdf'] = {:content_type => 'application/x-pdf',
                                :content => File.read('file')
mail.attachments.length #=> 2
mail.attachments[0].filename #=> 'filename.jpg'
mail.attachments['filename.jpg'] #=> <# Mail::Part, filename = 'filename.jpg' ...>

Yes, that is an ArrayHashThingy™ class, and, it rocks :) It is actually an AttachmentsList object that inherits from Array and implements a custom [] class.

Thanks to David and Yehuda who were brainstorming on the new ActionMailer 3.0 API with me, which I used for inspiration for this implementation. (more on the ActionMailer 3.0 API that I am pair programming with José later :)

Mail returns default values for fields, that can be modified

Mail returns an array of address specs when you call mail.to and would re-initialize that array with new values when you called mail.to=.

However, this array object was just a result of a method, it was not a representation of the addresses within the address field, so then doing mail.to << value would seem to work (no error) but the address would get lost, for example:

# Old (unintuitive method)
mail = Mail.new("To: mikel@test.lindsaar.net")
mail.to #=> ['mikel@test.lindsaar.net']
mail.to << 'ada@test.lindsaar.net'
mail.to #=> ['mikel@test.lindsaar.net']

This now works on all Address fields that can take more than one address, so

# New (intuitive) method
mail = Mail.new("To: mikel@test.lindsaar.net")
mail.to #=> ['mikel@test.lindsaar.net']
mail.to << 'ada@test.lindsaar.net'
mail.to #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']

Thanks to Sam for suggesting this feature.

Access returned to the Address objects

When you call mail.to you get a list of address spec strings (‘mikel@test.lindsaar.net’ for example), but it will not give you the display name, or formatted address etc.

To handle this, you can now call mail[:to] to get the actual ToField object, and then you can call #display_names, #formatted, and #addrs, the last of which will give you the actual Address objects in an array (the original behaviour of Mail), like so:

mail = Mail.new("To: Mikel Lindsaar <mikel@test.lindsaar.net>")
mail.to #=> ['mikel@test.lindsaar.net']
to = mail[:to]  #=> #<Mail::Field:0x10137c718...
to.addrs #=> [#<Mail::Address:2165620900 Address: |mikel@test.lindsaar.net| >]
to.addrs.each do |addr|
  puts "Formatted: #{addr.format}"
  puts "Display:   #{addr.display_name}"
  puts "Address:   #{addr.address}"
end
# Produces:
Formatted: Mikel Lindsaar <mikel@test.lindsaar.net>
Display:   Mikel Lindsaar
Address:   mikel@test.lindsaar.net
=> [#<Mail::Address:2165518560 Address: |Mikel Lindsaar <mikel@test.lindsaar.net>| >]

Thanks to Karl for suggesting this would be handy :)

More methods added to address fields

You can also access an array of the formatted, display_names or addresses directly from address fields:

mail = Mail.new("To: Mikel Lindsaar <mikel@test.lindsaar.net>")
mail[:to].addresses   
#=> ["mikel@test.lindsaar.net"]
mail[:to].formatted
#=> ["Mikel Lindsaar <mikel@test.lindsaar.net>"]
mail[:to].display_names
#=> ["Mikel Lindsaar"]

Remaining Stuff

There are a lot of other small bug fixes, parts now get sorted recursively on encode, body objects will accept an array of strings and call join on them, and many other small things that are in the commit and change logs. Check it out.

As always, tickets (and patches) are always welcome, I use GitHub’s Tracker for this. Or you can talk to us on the Mail Google Group

Happy Mailing!

blogLater

Mikel

back to top
Favorite
Posted 8 months ago at Ruby Quicktips

Did you know that error_messages_for takes a whole bunch of options for customization?

<%= error_messages_for :employee,
                       :class => "flash error",
                       :header_tag => "h4",
                       :header_message => "Sorry, the employee couldn't be created" %>

The options for the

that is returned include :header_tag, :id, :class, :header_message, :message and more.

Check out the docs on :error_messages_for for more.

back to top
Posted 8 months ago at The GitHub Blog

<!-- -*-Markdown-*- -->

Over the past couple of days, you may have noticed a new piece of UI, GitHub broadcasts:

broadcast image

We'll be using this feature to announce significant new feature additions and changes to GitHub. Once you've read the broadcast, click hide broadcast and it won't show up until we post a new broadcast. If you missed broadcasts in between logging in, we'll let you know how many you missed (view (2) new broadcasts) which you can always see at this link: http://github.com/blog/broadcasts

In case you've been living under a rock, here's some of the new stuff we've launched at GitHub over the past couple of months:

Fun fact: we've launched over 1,200 commits and 130,000 line changes since December. (and that's only to the main GitHub application!)

back to top
Favorite

I’ve been working on revamping the Active Record query interface for the last few weeks ( while taking some time off in India from consulting work, before joining 37signals ), building on top of Emilio’s GSOC project of integrating ARel and ActiveRecord. So here’s an overview of how things are going to work in Rails 3.

What’s going to be deprecated in Rails 3.1 ?

These deprecations will be effective in Rails’ 3.1 release ( NOT Rails 3 ) and will be fully removed in Rails 3.2, though there will be an official plugin to continue supporting them. Consider this an advance warning as it involves changing a lot of code.

In short, passing options hash containing :conditions, :include, :joins, :limit, :offset, :order, :select, :readonly, :group, :having, :from, :lock to any of the ActiveRecord provided class methods, is now deprecated.

Going into details, currently ActiveRecord provides the following finder methods :

  • find(id_or_array_of_ids, options)
  • find(:first, options)
  • find(:all, options)
  • first(options)
  • all(options)
  • update_all(updates, conditions, options)

And the following calculation methods :

  • count(column, options)
  • average(column, options)
  • minimum(column, options)
  • maximum(column, options)
  • sum(column, options)
  • calculate(operation, column, options)

Starting with Rails 3, supplying any option to the methods above will be deprecated. Support for supplying options will be removed from Rails 3.2. Moreover, find(:first) and find(:all) ( without any options ) are also being deprecated in favour of first and all. A tiny little exception here is that count() will still accept a :distinct option.

The following shows a few example of the deprecated usages :

1
2
3
4
5
User.find(:all, :limit => 1)
User.find(:all)
User.find(:first)
User.first(:conditions => {:name => 'lifo'})
User.all(:joins => :items)

But the following is NOT deprecated :

1
2
3
User.find(1)
User.find(1,2,3)
User.find_by_name('lifo')

Additionally, supplying options hash to named_scope is also deprecated :

1
2
named_scope :red, :conditions => { :colour => 'red' }
named_scope :red, lambda {|colour| {:conditions => { :colour => colour }} }

Supplying options hash to with_scope and with_exclusive_scope has also been deprecated :

1
2
with_scope(:find => {:conditions => {:name => 'lifo'}) { ... }
with_exclusive_scope(:find => {:limit =>1}) { ... }

New API

ActiveRecord in Rails 3 will have the following new finder methods.

  • where (:conditions)
  • select
  • group
  • order
  • limit
  • joins
  • includes (:include)
  • lock
  • readonly
  • from

1 Value in the bracket ( if different ) indicates the previous equivalent finder option.

Chainability

All of the above methods returns a Relation. Conceptually, a relation is very similar to an anonymous named scope. All these methods are defined on the Relation object as well, making it possible to chain them.

1
2
lifo = User.where(:name => 'lifo')
new_users = User.order('users.id DESC').limit(20).includes(:items)

You could also apply more finders to the existing relations :

1
2
cars = Car.where(:colour => 'black')
rich_ppls_cars = cars.order('cars.price DESC').limit(10)

Quacks like a Model

A relation quacks just like a model when it comes to the primary CRUD methods. You could call any of the following methods on a relation :

  • new(attributes)
  • create(attributes)
  • create!(attributes)
  • find(id_or_array)
  • destroy(id_or_array)
  • destroy_all
  • delete(id_or_array)
  • delete_all
  • update(ids, updates)
  • update_all(updates)
  • exists?

So the following code examples work as expected :

1
2
3
4
5
6
7
8
red_items = Item.where(:colour => 'red')
red_items.find(1)
item = red_items.new
item.colour #=> 'red'

red_items.exists? #=> true
red_items.update_all :colour => 'black'
red_items.exists? #=> false

Note that calling any of the update or delete/destroy methods would reset the relation, i.e delete the cached records used for optimizing methods like relation.size.

Lazy Loading

As it might be clear from the examples above, relations are loaded lazily – i.e you call an enumerable method on them. This is very similar to how associations and named_scopes already work.

1
2
cars = Car.where(:colour => 'black') # No Query
cars.each {|c| puts c.name } # Fires "select * from cars where ..."

This is very useful along side fragment caching. So in your controller action, you could just do :

1
2
3
def index
  @recent_items = Item.limit(10).order('created_at DESC')
end

And in your view :

1
2
3
4
5
<% cache('recent_items') do %>
  <% @recent_items.each do |item| %>
    ...
  <% end %>
<% end %>

In the above example, @recent_items are loaded on @recent_items.each call from the view. As the controller doesn’t actually fire any query, fragment caching becomes more effective without requiring any special work arounds.

Force loading – all, first & last

For the times you don’t need lazy loading, you could just call all on the relation :


cars = Car.where(:colour => 'black').all

It’s important to note that all returns an Array and not a Relation. This is similar to how things work in Rails 2.3 with named_scopes and associations.

Similarly, first and last will always return an ActiveRecord object.

1
2
3
cars = Car.order('created_at ASC')
oldest_car = cars.first
newest_car = cars.last

named_scope -> scopes

named_scope have now been renamed to just scope.

So a definition like :

1
2
3
4
class Item
  named_scope :red, :conditions => { :colour => 'red' }
  named_scope :since, lambda {|time| {:conditions => ["created_at > ?", time] }}
end

Now becomes :

1
2
3
4
class Item
  scope :red, :conditions => { :colour => 'red' }
  scope :since, lambda {|time| {:conditions => ["created_at > ?", time] }}
end

However, as using options hash is deprecated, you should write it using the new finder methods :

1
2
3
4
class Item
  scope :red, where(:colour => 'red')
  scope :since, lambda {|time| where("created_at > ?", time) }
end

Internally, named scopes are built on top of Relation, making it very easy to mix and match them with the finder methods :

1
2
3
red_items = Item.red
available_red_items = red_items.where("quantity > ?", 0)
old_red_items = Item.red.since(10.days.ago)

Model.scoped

If you want to build a complex relation/query, starting with a blank relation, Model.scoped is what you would use.

1
2
3
cars = Car.scoped
rich_ppls_cars = cars.order('cars.price DESC').limit(10)
white_cars = cars.where(:colour => 'red')

Speaking of internals, ActiveRecord::Base has the following delegations :

1
2
3
delegate :find, :first, :last, :all, :destroy, :destroy_all, :exists?, :delete, :delete_all, :update, :update_all, :to => :scoped
delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :to => :scoped
delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped

The above might give you a better insight on how ActiveRecord is doing things internally. Additionally, dynamic finder methods find_by_name, find_all_by_name_and_colour etc. are also delegated to Relation.

with_scope and with_exclusive_scope

with_scope and with_exclusive_scope are now implemented on top of Relation as well. Making it possible to use any relation with them :

1
2
3
with_scope(where(:name => 'lifo')) do
  ...
end

Or even use a named scope :

1
2
3
with_exclusive_scope(Item.red) do
  ...
end

That’s all. Please open a lighthouse ticket if you find a bug or have a patch for an improvement!

back to top
Posted 8 months ago at A Fresh Cup

12 is really too many billable hours for one day.

back to top
Favorite
Posted 8 months ago at Ruby5

This week we look into Cappuccino and Sprite. We also check out the MacRuby Debugger, Websockets with Cramp, Secure URLs, and Migrating to Rails 3.

back to top
Favorite
Posted 8 months ago at Ruby Quicktips

With a ‘named scope’, you can add reusable ActiveRecord find methods to your model.
So instead of writing this in your controller, every time you need it…

User.find(:all, :conditions => { :active => true }, :order => "first_name, last_name ASC")

…you can add a named scope to your User model that represents this find statement:

class User < ActiveRecord::Base
  named_scope :active, :conditions => { :active => true }, :order => "first_name, last_name ASC"
end

Now, you can use this named scope by calling it from within your controller like this:

User.active

Named scopes make your (controller) code more readable and shorter.
A named scope can take all options a find statement can. And - as a plus - you can even chain named scopes, which is particularly awesome.
There’s a whole lot more you can do. Check out Ryan Daigle’s introduction and the documentation.

back to top
Posted 8 months ago at The GitHub Blog

This week kneath and I (with some help from The Changelog) rolled out Explore GitHub – a new page showing trending repositories, repositories recently featured on The Changelog, and recent episodes of their weekly podcast.

The trending repos are updated every 20 minutes so have fun watching projects climb the charts as they’re blogged and tweeter about throughout the day.

back to top
Posted 8 months ago at The GitHub Blog

As of January 1, 2010 we’re operating this site as GitHub, Inc.

What does that mean for you? In a word: nothing.

We originally incorporated Logical Awesome LLC prior to launching GitHub thinking that it would be just one of a number of brands we were going to release. GitHub took off in a serious way, so instead of continuing to confuse people with an obscure company name, we made the decision to change it to the obvious choice when we converted from a LLC to a C-Corp.

Here’s to another awesome year with your favorite code host!

back to top
Posted 8 months ago at The GitHub Blog

In Paris? Our very own Scott Chacon, author of Pro Git and internationally renowned Git expert, will be teaching a Git class on February 18th, 2010.

Check out the website for more information. We’ll see you there!

back to top
Posted 8 months ago at Rails Envy » Home

Episode #104: Something new. I’m sad to say that this will be the last episode of the Rails Envy podcast. I’m happy to say that Dan and I will be doing The Ruby Show which will follow the same general flow of Rails Envy. There’s no need to update your feeds (though you can if you’d like) because the feed will redirect for the foreseeable future. We’ve also got some other fun stuff planned so stay tuned!

Sponsored by New Relic
The Rails Envy podcast is brought to you this week by NewRelic. NewRelic provides RPM which is a plugin for rails that allows you to monitor and quickly diagnose problems with your Rails application in real time. Check them out at NewRelic.com.


Show Notes

back to top
Posted 8 months ago at Ryan Tomayko

Simon Willison throws down a C100K problem for node.js. That’s a tough order for a single machine. To get even close, you’re going to need lots of system tuning way down below node.js.

# | Discuss

back to top
Posted 8 months ago at A Fresh Cup

Things are very nearly too busy, but there's still time for a few links.

back to top
Favorite
Posted 8 months ago at Ruby Quicktips

To split a string at a a given delimiter, use the split method. The default delimiter is the space ’ ‘, and an array of the words is returned.

"A string to split".split # => ["A", "string", "to", "split"]
"tag1,tag2,tag3".split(',') # => ["tag1", "tag2", "tag3"]

Ruby Documentation.

back to top
Posted 8 months ago at RubyLearning Blog

In this brief interview, Satish Talim of RubyLearning talks to Patrick McKenzie of Japan, winner of the fifth Ruby Programming Challenge For Newbies.

Patrick McKenzie

Satish>> Welcome Patrick and thanks for taking out time to share your thoughts. For the benefit of the readers, could you tell us something about your self?

Patrick>> Thanks Satish for the opportunity. My name is Patrick McKenzie and by day I work on big freaking Java web apps at a Japanese corporation in Nagoya. By night, I run a small software business, which sells desktop software written in Java and a web application written in Ruby on Rails. I also blog a bit, mostly on business and programming topics.

Satish>> How did you get involved with Ruby programming?

Patrick>> About two years ago, when I was trying to make my business’ website a little more impressive than hand-coded HTML everywhere, I thought writing myself a custom CMS (content management system) would help me produce things my customers would like faster than writing them all by hand. However, since I could only do it on nights and weekends, I couldn’t exactly find the time to do it on the J2EE stack. I bought a pair of books on Ruby on Rails, fell in love with Ruby, and the rest is history.

Satish>> Could you name three features of Ruby that you like the most, as compared to other languages? Why?

Patrick>> It would be hard to limit it to three, but here goes:

1. A lot of Ruby code functions rather like Unix: small, discrete bits of code which operate together very well. For example, to read a property file in Ruby:

File.readlines("property.txt").map {|line| line.split("#")[0]}.map
{|line| line.split("=")}.inject({}) {|hash, couplet|
hash[couplet[0]]=couplet[1]; hash}

does all the work for you. That is a trivial example in Ruby which would take me almost a page to write in Java (well, assuming I wasn’t using a library function, but you get the drift.)

2. I think the community for a language matters. The Ruby community is amazing and has been very generous with their time in producing OSS code, documentation on the Internet, and the gem infrastructure, which lets me get more impressive things done faster and with less pain.

3. I really like having IRB and its cousin the Rails console, which lets me dive right in among my objects and see the world from their eyes, so to speak. Often when coding I have IRB open in another window so that I can test my assumption of what a particular bit of code would result in prior to using it.

Satish>> How was experience of taking part in the Ruby Programming Challenge For Newbies (RPCFN)?

Patrick>> It was amazingly fun — thank you for running the challenge. As soon as I heard the maze solving challenge I thought “Well, let’s see, you can read in the maze, construct a graph from it, and then use breadth first search” but I thought that there would probably be a hundred implementations of that. So I decided to do something a little different and solve it with regular expressions. The key insight for me was that if the width of the maze is constant that you can write a regular expression which skips to the same column on the next line.

Satish>> How is the Ruby and Rails scenario in your country Japan?

Patrick>> My industry is currently dominated by expensive web applications written in Java, but I think the future for Ruby is bright. There is a certain deal of pride in the fact that Ruby is a Japanese language and probably the most well-known bit of Japanese software that is not a video game. My impression is that Rails is increasingly popular, too. I have tried to promote both internally at my company, and we’ve had some success with them.

Satish>> What are your future plans?

Patrick>> I recently made the decision to quit my day job and go full-time with my software business. I expect I will be developing more applications in Rails.

Thank you Patrick and all the very best with your software business. In case you have any queries and/or questions, kindly post your questions here (as comments to this blog post) and Patrick would be glad to answer.

Technorati Tags: Patrick McKenzie, Ruby, Ruby programming, RPCFN, Japan, Ruby Challenge

back to top