Iteration Shouldn’t Spin Your Wheels! »
Created at: 27.01.2010 20:00, source: Engine Yard Blog, tagged: Technology newsletter
This article was originally included in the September issue of the Engine Yard Newsletter. To read more posts like this one, subscribe to the Engine Yard Newsletter.
In this series, Evan Phoenix, Rubinius creator and Ruby expert, presents tips and tricks to help you improve your knowledge of Ruby.
Ruby is a rich language that believes there should be more than one way to express yourself—the many ways of counting and iterating are no exception.
Most Ruby programmers are familiar with the most common one:
Integer#times 100.times { |i| p i }
Integer#times counts from 0 up to 99, yielding the current number to the block. This a simple, expressive way to execute some code a number of times.
But there are cases where you want to start counting at a number other than 0, no problem:
Integer#upto 10.upto(20) { |i| p i }
This prints out 10, 11, 12, until it hit 20. It increments by 1, and you'll notice it is inclusive, meaning that in this case we yield 11 items, not 10.
Going up is nice, but sometimes you need to go down, so use #upto's sister:
Integer#downto. 20.downto(10) { |i| p i }
If you need a little more control over your iteration, you can use:
Range#step (10..20).step(2) { |i| p i }
This will print 10, 12, 14, 16, 18, 20.
Now, in this case, we've introduced a Range, which most Ruby programmers are familiar with. It is basically an object that expresses a beginning and an end — in this case, 10 and 20. Range has another trick up it's sleeve:
(10...20).step(2) { |i| p i }
You'll notice the 3 dots instead of 2. This indicates that this range is exclusive of the end, not inclusive. So 20 is the terminator, but is not in the set of valid values itself.
Range also support #each:
(10..20).each { |i| p i }
This works exactly the same as Integer#upto. I personally prefer Integer#upto, because I feel it expresses the operation better.
Another domain is counting on a collection. Before 1.8.7 and 1.9, there was pretty much only one method to help you with doing that: Array#each_with_index.
[:foo, :bar, :baz].each_with_index { |sym, index| p [sym, index] }
This prints out [:foo, 0], [:bar, 1], and [:baz, 2].
This is nice, but it's pretty limiting because the only place you've got that index is with simple iteration. Say you wanted to map the Array and take the position into account — you'd have to do:
ary = [1, 3, 5] i = 0 ary.map { |element| x = element * i; i += 1; x }
It's kind of messy to just take the position into account. So with 1.8.7 and 1.9, Enumerator support was baked into most methods which makes this much simpler!
ary = [1,3,5] ary.map.with_index { |element, index| element * index }
For those that haven't seen Enumerators yet, you're saying "Hey! Where did the block to map go!" Well there isn't one. Array#map, when passed no block, returns a Enumerator object. This object, when you call #each, calls the original method on the original object and passes the block along. To begin with, this provides external iteration, but it also gives Ruby a place to add iteration alteration methods, such as Enumerator#with_index. Now you never need to use a while loop again!
See you next time!
more »
Render Options in Rails 3 »
Created at: 18.01.2010 21:30, source: Engine Yard Blog, tagged: Technology ActionController json newsletter rails Rails 3
This article was originally included in the October issue of the Engine Yard Newsletter. To read more posts like this one, subscribe to the Engine Yard Newsletter.
In Inside Rails, Yehuda Katz, Rails expert and core team member, and Carl Lerche, Rails expert and full-time contributor, present expert advice and insight on the Rails platform and Rails development.
In previous versions of Rails, adding a new rendering option to Rails required performing an
alias_method_chain on the render method, adding your new options, and hoping they didn't conflict with any of the other code in the rendering pipeline.
Rails 3 makes rendering options a first class citizen, and uses the same new system internally that plugins authors are expected to use. Before we get into how you can use this feature yourself, let's take a look at how Rails uses it:
ActionController.add_renderer :json do |json, options| json = ActiveSupport::JSON.encode(json) unless json.respond_to?(:to_str) json = "#{options[:callback]}(#{json})" unless options[:callback].blank? self.content_type ||= Mime::JSON self.response_body = json end
Here, we are creating a new render :json option, which behaves exactly like render :json in Rails 2.3. In the render_json method, we use the same lower-level (but still public) APIs that are used by Rails itself to set the MIME type and response body. Using a render option entirely skips the rest of the render pipeline, so you don't have to worry about blocking normal template selection and rendering, or any other internal changes that might break your added option.
Next, let's take a look at adding a new render :pdf option. We'll use JRuby and the Flying Saucer library, which takes HTML and CSS and converts it to a PDF file. The syntax for the new option will be render :pdf => "template_name", :css => %w(main.css print.pdf), :layout => "print".
In order to understand how to do this, let's first take a look at the code necessary to use Flying Saucer in JRuby. First, you'll need to grab the Flying Saucer jars (you can get them from my Muse git repo). Next, let's write the code necessary to convert HTML and CSS to PDF with Flying Saucer:
# Both of these are .jar files require "/path/to/itext" require "/path/to/core-renderer" module PdfUtils def self.string_to_pdf(input_string) io = StringIO.new renderer = org.xhtmlrenderer.pdf.ITextRenderer.new renderer.set_document_from_string_input input_string renderer.layout renderer.create_pdf(io.to_outputstream) io.string # the PDF file in a String end end
It's a bit verbose, but it does work. Now that we have a way to make the PDF, let's wire it up into a render option.
ActionController.add_renderer :pdf do |template, options| css_files = Array.wrap(options.delete(:css)) css = css_files.map {|file| File.read(file) }.join("\n") # Reuse the render semantics to get a string from the # template and options string = render_to_string template, options # Drop in the CSS before the in a style tag. # In practice, you would probably cache the file reads # and the merging of the CSS and HTML. string.gsub!(%r{}, "<style>#{css}</style>") send_data PdfUtils.string_to_pdf(string), :type => Mime::PDF end
You'd also need to register the PDF mime type:
Mime::Type.register "application/pdf", :pdf
You could now do the following in a controller:
class PostsController < ActionController::Base def show @post = Post.find(params[:id]) respond_to do |format| format.html format.pdf # or format.pdf { render :pdf => "show", :css => %w(application print) } end end end
The tricky bits of this process are now reserved to figuring out how to build and return the output, not how to inject your option into render. Pretty cool, no?
more »
