Functional tests now work properly, bearing in mind whether a user is logged in or...
[depot.git] / vendor / rails / actionpack / lib / action_view / helpers / atom_feed_helper.rb
1 require 'set'
2
3 # Adds easy defaults to writing Atom feeds with the Builder template engine (this does not work on ERb or any other
4 # template languages).
5 module ActionView
6 module Helpers #:nodoc:
7 module AtomFeedHelper
8 # Full usage example:
9 #
10 # config/routes.rb:
11 # ActionController::Routing::Routes.draw do |map|
12 # map.resources :posts
13 # map.root :controller => "posts"
14 # end
15 #
16 # app/controllers/posts_controller.rb:
17 # class PostsController < ApplicationController::Base
18 # # GET /posts.html
19 # # GET /posts.atom
20 # def index
21 # @posts = Post.find(:all)
22 #
23 # respond_to do |format|
24 # format.html
25 # format.atom
26 # end
27 # end
28 # end
29 #
30 # app/views/posts/index.atom.builder:
31 # atom_feed do |feed|
32 # feed.title("My great blog!")
33 # feed.updated((@posts.first.created_at))
34 #
35 # for post in @posts
36 # feed.entry(post) do |entry|
37 # entry.title(post.title)
38 # entry.content(post.body, :type => 'html')
39 #
40 # entry.author do |author|
41 # author.name("DHH")
42 # end
43 # end
44 # end
45 # end
46 #
47 # The options for atom_feed are:
48 #
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, }, ]}
57 #
58 # Other namespaces can be added to the root element:
59 #
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)
66 #
67 # for post in @posts
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)
72 #
73 # entry.author do |author|
74 # author.name("DHH")
75 # end
76 # end
77 # end
78 # end
79 #
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:
84 #
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}"
89 # end
90 #
91 #
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)
97 else
98 options[:schema_date] = "2005" # The Atom spec copyright date
99 end
100
101 xml = options[:xml] || eval("xml", block.binding)
102 xml.instruct!
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) }
109 end
110 end
111 end
112
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/)}
115
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)
120
121 yield AtomFeedBuilder.new(xml, self, options)
122 end
123 end
124
125 class AtomBuilder
126 XHTML_TAG_NAMES = %w(content rights title subtitle summary).to_set
127
128 def initialize(xml)
129 @xml = xml
130 end
131
132 private
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|
140 block.call(xhtml)
141 end
142 end
143 else
144 @xml.__send__(method, *arguments, &block)
145 end
146 end
147
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'
155 end
156 end
157 end
158
159 class AtomFeedBuilder < AtomBuilder
160 def initialize(xml, view, feed_options = {})
161 @xml, @view, @feed_options = xml, view, feed_options
162 end
163
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)
167 end
168
169 # Creates an entry tag for a specific record and prefills the id using class and id.
170 #
171 # Options:
172 #
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 = {})
178 @xml.entry do
179 @xml.id(options[:id] || "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}")
180
181 if options[:published] || (record.respond_to?(:created_at) && record.created_at)
182 @xml.published((options[:published] || record.created_at).xmlschema)
183 end
184
185 if options[:updated] || (record.respond_to?(:updated_at) && record.updated_at)
186 @xml.updated((options[:updated] || record.updated_at).xmlschema)
187 end
188
189 @xml.link(:rel => 'alternate', :type => 'text/html', :href => options[:url] || @view.polymorphic_url(record))
190
191 yield AtomBuilder.new(@xml)
192 end
193 end
194 end
195
196 end
197 end
198 end