3 # Adds easy defaults to writing Atom feeds with the Builder template engine (this does not work on ERb or any other
6 module Helpers
#:nodoc:
11 # ActionController::Routing::Routes.draw do |map|
12 # map.resources :posts
13 # map.root :controller => "posts"
16 # app/controllers/posts_controller.rb:
17 # class PostsController < ApplicationController::Base
21 # @posts = Post.find(:all)
23 # respond_to do |format|
30 # app/views/posts/index.atom.builder:
32 # feed.title("My great blog!")
33 # feed.updated((@posts.first.created_at))
36 # feed.entry(post) do |entry|
37 # entry.title(post.title)
38 # entry.content(post.body, :type => 'html')
40 # entry.author do |author|
47 # The options for atom_feed are:
49 # * <tt>:language</tt>: Defaults to "en-US".
50 # * <tt>:root_url</tt>: The HTML alternative that this feed is doubling for. Defaults to / on the current host.
51 # * <tt>:url</tt>: The URL for this feed. Defaults to the current URL.
52 # * <tt>:id</tt>: The id for this feed. Defaults to "tag:#{request.host},#{options[:schema_date]}:#{request.request_uri.split(".")[0]}"
53 # * <tt>:schema_date</tt>: The date at which the tag scheme for the feed was first used. A good default is the year you
54 # created the feed. See http://feedvalidator.org/docs/error/InvalidTAG.html for more information. If not specified,
55 # 2005 is used (as an "I don't care" value).
56 # * <tt>:instruct</tt>: Hash of XML processing instructions in the form {target => {attribute => value, }} or {target => [{attribute => value, }, ]}
58 # Other namespaces can be added to the root element:
60 # app/views/posts/index.atom.builder:
61 # atom_feed({'xmlns:app' => 'http://www.w3.org/2007/app',
62 # 'xmlns:openSearch' => 'http://a9.com/-/spec/opensearch/1.1/'}) do |feed|
63 # feed.title("My great blog!")
64 # feed.updated((@posts.first.created_at))
65 # feed.tag!(openSearch:totalResults, 10)
68 # feed.entry(post) do |entry|
69 # entry.title(post.title)
70 # entry.content(post.body, :type => 'html')
71 # entry.tag!('app:edited', Time.now)
73 # entry.author do |author|
80 # The Atom spec defines five elements (content rights title subtitle
81 # summary) which may directly contain xhtml content if :type => 'xhtml'
82 # is specified as an attribute. If so, this helper will take care of
83 # the enclosing div and xhtml namespace declaration. Example usage:
85 # entry.summary :type => 'xhtml' do |xhtml|
86 # xhtml.p pluralize(order.line_items.count, "line item")
87 # xhtml.p "Shipped to #{order.address}"
88 # xhtml.p "Paid by #{order.pay_type}"
92 # atom_feed yields an AtomFeedBuilder instance. Nested elements yield
93 # an AtomBuilder instance.
94 def atom_feed(options
= {}, &block
)
95 if options
[:schema_date]
96 options
[:schema_date] = options
[:schema_date].strftime("%Y-%m-%d") if options
[:schema_date].respond_to
?(:strftime)
98 options
[:schema_date] = "2005" # The Atom spec copyright date
101 xml
= options
[:xml] || eval("xml", block
.binding
)
103 if options
[:instruct]
104 options
[:instruct].each
do |target
,attrs
|
105 if attrs
.respond_to
?(:keys)
106 xml
.instruct
!(target
, attrs
)
107 elsif attrs
.respond_to
?(:each)
108 attrs
.each
{ |attr_group
| xml
.instruct
!(target
, attr_group
) }
113 feed_opts
= {"xml:lang" => options
[:language] || "en-US", "xmlns" => 'http://www.w3.org/2005/Atom'}
114 feed_opts
.merge
!(options
).reject
!{|k
,v
| !k
.to_s
.match(/^xml/)}
116 xml
.feed(feed_opts
) do
117 xml
.id(options
[:id] || "tag:#{request.host},#{options[:schema_date]}:#{request.request_uri.split(".")[0]}")
118 xml
.link(:rel => 'alternate', :type => 'text/html', :href => options
[:root_url] || (request
.protocol
+ request
.host_with_port
))
119 xml
.link(:rel => 'self', :type => 'application/atom+xml', :href => options
[:url] || request
.url
)
121 yield AtomFeedBuilder
.new(xml
, self, options
)
126 XHTML_TAG_NAMES
= %w(content rights title subtitle summary
).to_set
133 # Delegate to xml builder, first wrapping the element in a xhtml
134 # namespaced div element if the method and arguments indicate
135 # that an xhtml_block? is desired.
136 def method_missing(method
, *arguments
, &block
)
137 if xhtml_block
?(method
, arguments
)
138 @xml.__send__(method
, *arguments
) do
139 @xml.div(:xmlns => 'http://www.w3.org/1999/xhtml') do |xhtml
|
144 @xml.__send__(method
, *arguments
, &block
)
148 # True if the method name matches one of the five elements defined
149 # in the Atom spec as potentially containing XHTML content and
150 # if :type => 'xhtml' is, in fact, specified.
151 def xhtml_block
?(method
, arguments
)
152 if XHTML_TAG_NAMES
.include?(method
.to_s
)
153 last
= arguments
.last
154 last
.is_a
?(Hash
) && last
[:type].to_s
== 'xhtml'
159 class AtomFeedBuilder
< AtomBuilder
160 def initialize(xml
, view
, feed_options
= {})
161 @xml, @view, @feed_options = xml
, view
, feed_options
164 # Accepts a Date or Time object and inserts it in the proper format. If nil is passed, current time in UTC is used.
165 def updated(date_or_time
= nil)
166 @xml.updated((date_or_time
|| Time
.now
.utc
).xmlschema
)
169 # Creates an entry tag for a specific record and prefills the id using class and id.
173 # * <tt>:published</tt>: Time first published. Defaults to the created_at attribute on the record if one such exists.
174 # * <tt>:updated</tt>: Time of update. Defaults to the updated_at attribute on the record if one such exists.
175 # * <tt>:url</tt>: The URL for this entry. Defaults to the polymorphic_url for the record.
176 # * <tt>:id</tt>: The ID for this entry. Defaults to "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}"
177 def entry(record
, options
= {})
179 @xml.id(options
[:id] || "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}")
181 if options
[:published] || (record
.respond_to
?(:created_at) && record
.created_at
)
182 @xml.published((options
[:published] || record
.created_at
).xmlschema
)
185 if options
[:updated] || (record
.respond_to
?(:updated_at) && record
.updated_at
)
186 @xml.updated((options
[:updated] || record
.updated_at
).xmlschema
)
189 @xml.link(:rel => 'alternate', :type => 'text/html', :href => options
[:url] || @view.polymorphic_url(record
))
191 yield AtomBuilder
.new(@xml)