Skip to content
This repository
tree: 9ca58ba492
Fetching contributors…

Octocat-spinner-32-eaf2f5

Cannot retrieve contributors at this time

file 201 lines (183 sloc) 8.277 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
require 'set'

module ActionView
  # = Action View Atom Feed Helpers
  module Helpers #:nodoc:
    module AtomFeedHelper
      # Adds easy defaults to writing Atom feeds with the Builder template engine (this does not work on ERB or any other
      # template languages).
      #
      # Full usage example:
      #
      # config/routes.rb:
      # Basecamp::Application.routes.draw do
      # resources :posts
      # root :to => "posts#index"
      # end
      #
      # app/controllers/posts_controller.rb:
      # class PostsController < ApplicationController::Base
      # # GET /posts.html
      # # GET /posts.atom
      # def index
      # @posts = Post.all
      #
      # respond_to do |format|
      # format.html
      # format.atom
      # end
      # end
      # end
      #
      # app/views/posts/index.atom.builder:
      # atom_feed do |feed|
      # feed.title("My great blog!")
      # feed.updated(@posts[0].created_at) if @posts.length > 0
      #
      # @posts.each do |post|
      # feed.entry(post) do |entry|
      # entry.title(post.title)
      # entry.content(post.body, :type => 'html')
      #
      # entry.author do |author|
      # author.name("DHH")
      # end
      # end
      # end
      # end
      #
      # The options for atom_feed are:
      #
      # * <tt>:language</tt>: Defaults to "en-US".
      # * <tt>:root_url</tt>: The HTML alternative that this feed is doubling for. Defaults to / on the current host.
      # * <tt>:url</tt>: The URL for this feed. Defaults to the current URL.
      # * <tt>:id</tt>: The id for this feed. Defaults to "tag:#{request.host},#{options[:schema_date]}:#{request.fullpath.split(".")[0]}"
      # * <tt>:schema_date</tt>: The date at which the tag scheme for the feed was first used. A good default is the year you
      # created the feed. See http://feedvalidator.org/docs/error/InvalidTAG.html for more information. If not specified,
      # 2005 is used (as an "I don't care" value).
      # * <tt>:instruct</tt>: Hash of XML processing instructions in the form {target => {attribute => value, }} or {target => [{attribute => value, }, ]}
      #
      # Other namespaces can be added to the root element:
      #
      # app/views/posts/index.atom.builder:
      # atom_feed({'xmlns:app' => 'http://www.w3.org/2007/app',
      # 'xmlns:openSearch' => 'http://a9.com/-/spec/opensearch/1.1/'}) do |feed|
      # feed.title("My great blog!")
      # feed.updated((@posts.first.created_at))
      # feed.tag!(openSearch:totalResults, 10)
      #
      # @posts.each do |post|
      # feed.entry(post) do |entry|
      # entry.title(post.title)
      # entry.content(post.body, :type => 'html')
      # entry.tag!('app:edited', Time.now)
      #
      # entry.author do |author|
      # author.name("DHH")
      # end
      # end
      # end
      # end
      #
      # The Atom spec defines five elements (content rights title subtitle
      # summary) which may directly contain xhtml content if :type => 'xhtml'
      # is specified as an attribute. If so, this helper will take care of
      # the enclosing div and xhtml namespace declaration. Example usage:
      #
      # entry.summary :type => 'xhtml' do |xhtml|
      # xhtml.p pluralize(order.line_items.count, "line item")
      # xhtml.p "Shipped to #{order.address}"
      # xhtml.p "Paid by #{order.pay_type}"
      # end
      #
      #
      # <tt>atom_feed</tt> yields an +AtomFeedBuilder+ instance. Nested elements yield
      # an +AtomBuilder+ instance.
      def atom_feed(options = {}, &block)
        if options[:schema_date]
          options[:schema_date] = options[:schema_date].strftime("%Y-%m-%d") if options[:schema_date].respond_to?(:strftime)
        else
          options[:schema_date] = "2005" # The Atom spec copyright date
        end

        xml = options.delete(:xml) || eval("xml", block.binding)
        xml.instruct!
        if options[:instruct]
          options[:instruct].each do |target,attrs|
            if attrs.respond_to?(:keys)
              xml.instruct!(target, attrs)
            elsif attrs.respond_to?(:each)
              attrs.each { |attr_group| xml.instruct!(target, attr_group) }
            end
          end
        end

        feed_opts = {"xml:lang" => options[:language] || "en-US", "xmlns" => 'http://www.w3.org/2005/Atom'}
        feed_opts.merge!(options).reject!{|k,v| !k.to_s.match(/^xml/)}

        xml.feed(feed_opts) do
          xml.id(options[:id] || "tag:#{request.host},#{options[:schema_date]}:#{request.fullpath.split(".")[0]}")
          xml.link(:rel => 'alternate', :type => 'text/html', :href => options[:root_url] || (request.protocol + request.host_with_port))
          xml.link(:rel => 'self', :type => 'application/atom+xml', :href => options[:url] || request.url)

          yield AtomFeedBuilder.new(xml, self, options)
        end
      end

      class AtomBuilder
        XHTML_TAG_NAMES = %w(content rights title subtitle summary).to_set

        def initialize(xml)
          @xml = xml
        end

        private
          # Delegate to xml builder, first wrapping the element in a xhtml
          # namespaced div element if the method and arguments indicate
          # that an xhtml_block? is desired.
          def method_missing(method, *arguments, &block)
            if xhtml_block?(method, arguments)
              @xml.__send__(method, *arguments) do
                @xml.div(:xmlns => 'http://www.w3.org/1999/xhtml') do |xhtml|
                  block.call(xhtml)
                end
              end
            else
              @xml.__send__(method, *arguments, &block)
            end
          end

          # True if the method name matches one of the five elements defined
          # in the Atom spec as potentially containing XHTML content and
          # if :type => 'xhtml' is, in fact, specified.
          def xhtml_block?(method, arguments)
            if XHTML_TAG_NAMES.include?(method.to_s)
              last = arguments.last
              last.is_a?(Hash) && last[:type].to_s == 'xhtml'
            end
          end
      end

      class AtomFeedBuilder < AtomBuilder
        def initialize(xml, view, feed_options = {})
          @xml, @view, @feed_options = xml, view, feed_options
        end

        # Accepts a Date or Time object and inserts it in the proper format. If nil is passed, current time in UTC is used.
        def updated(date_or_time = nil)
          @xml.updated((date_or_time || Time.now.utc).xmlschema)
        end

        # Creates an entry tag for a specific record and prefills the id using class and id.
        #
        # Options:
        #
        # * <tt>:published</tt>: Time first published. Defaults to the created_at attribute on the record if one such exists.
        # * <tt>:updated</tt>: Time of update. Defaults to the updated_at attribute on the record if one such exists.
        # * <tt>:url</tt>: The URL for this entry. Defaults to the polymorphic_url for the record.
        # * <tt>:id</tt>: The ID for this entry. Defaults to "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}"
        def entry(record, options = {})
          @xml.entry do
            @xml.id(options[:id] || "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}")

            if options[:published] || (record.respond_to?(:created_at) && record.created_at)
              @xml.published((options[:published] || record.created_at).xmlschema)
            end

            if options[:updated] || (record.respond_to?(:updated_at) && record.updated_at)
              @xml.updated((options[:updated] || record.updated_at).xmlschema)
            end

            @xml.link(:rel => 'alternate', :type => 'text/html', :href => options[:url] || @view.polymorphic_url(record))

            yield AtomBuilder.new(@xml)
          end
        end
      end

    end
  end
end
Something went wrong with that request. Please try again.