ahora 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
 - data/Gemfile +3 -0
 - data/README.md +117 -0
 - data/Rakefile +7 -0
 - data/ahora.gemspec +27 -0
 - data/lib/ahora/middleware/last_modified_caching.rb +88 -0
 - data/lib/ahora/middleware/request_logger.rb +85 -0
 - data/lib/ahora/middleware.rb +3 -0
 - data/lib/ahora/representation.rb +122 -0
 - data/lib/ahora/resource.rb +68 -0
 - data/lib/ahora/version.rb +3 -0
 - data/lib/ahora.rb +3 -0
 - data/test/ahora/representation_test.rb +39 -0
 - data/test/ahora_test.rb +214 -0
 - data/test/fixtures/employee.xml +6 -0
 - data/test/fixtures/user_posts.xml +42 -0
 - data/test/test_helper.rb +11 -0
 - metadata +128 -0
 
    
        data/.gitignore
    ADDED
    
    
    
        data/Gemfile
    ADDED
    
    
    
        data/README.md
    ADDED
    
    | 
         @@ -0,0 +1,117 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            Ahora
         
     | 
| 
      
 2 
     | 
    
         
            +
            =====
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            An alternative to ActiveResource for consuming Java-ish XML HTTP Resources easily. *Ahora* glues together two amazing Ruby libraries: [Faraday](https://github.com/technoweenie/faraday) and [Nibbler](https://github.com/mislav/nibbler).
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            If your project meets any of the following requirements, you might be interested.
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
             * You want to consume an external XML resource.
         
     | 
| 
      
 9 
     | 
    
         
            +
             * You want to map camelCase names to underscore_case.
         
     | 
| 
      
 10 
     | 
    
         
            +
             * You want to parse Date, Time and boolean values automatically.
         
     | 
| 
      
 11 
     | 
    
         
            +
             * You want to be a good citizen on the web and support caching (e.g. If-Modified-Since).
         
     | 
| 
      
 12 
     | 
    
         
            +
             * You want to use fragment caching on the front-end. So it should generate a cache-key.
         
     | 
| 
      
 13 
     | 
    
         
            +
             * You might have a recently cached fragment, so XML collections should be lazy loaded.
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
            This is a big list, you might not need all these requirements. At least the code can act as an example on how to approach one of these problems.
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
            Example
         
     | 
| 
      
 18 
     | 
    
         
            +
            ---
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
            Let's say you want to display to following XML on a page in a Rails project
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
            ~~~ xml
         
     | 
| 
      
 23 
     | 
    
         
            +
            <?xml version="1.0"?>
         
     | 
| 
      
 24 
     | 
    
         
            +
            <userPosts type="array">
         
     | 
| 
      
 25 
     | 
    
         
            +
              <userPost>
         
     | 
| 
      
 26 
     | 
    
         
            +
            	<hidden>false</hidden>
         
     | 
| 
      
 27 
     | 
    
         
            +
                <objectId>1</objectId>
         
     | 
| 
      
 28 
     | 
    
         
            +
                <userObjectId>1</userObjectId>
         
     | 
| 
      
 29 
     | 
    
         
            +
                <user>
         
     | 
| 
      
 30 
     | 
    
         
            +
                  <firstName>John</firstName>
         
     | 
| 
      
 31 
     | 
    
         
            +
                  <lastName>Doe</lastName>
         
     | 
| 
      
 32 
     | 
    
         
            +
                </user>
         
     | 
| 
      
 33 
     | 
    
         
            +
                <body>How is everybody today?</body>
         
     | 
| 
      
 34 
     | 
    
         
            +
                <createdAt>2011-10-26T17:01:52+02:00</createdAt>
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                <replies type="array">
         
     | 
| 
      
 37 
     | 
    
         
            +
                  <userPost>
         
     | 
| 
      
 38 
     | 
    
         
            +
                    <objectId>2</objectId>
         
     | 
| 
      
 39 
     | 
    
         
            +
                    <parentObjectId>1</parentObjectId>
         
     | 
| 
      
 40 
     | 
    
         
            +
                    <userObjectId>2</userObjectId>
         
     | 
| 
      
 41 
     | 
    
         
            +
                    <user>
         
     | 
| 
      
 42 
     | 
    
         
            +
                      <firstName>Mike</firstName>
         
     | 
| 
      
 43 
     | 
    
         
            +
                      <lastName>Smith</lastName>
         
     | 
| 
      
 44 
     | 
    
         
            +
                    </user>
         
     | 
| 
      
 45 
     | 
    
         
            +
                    <body>I am fine, thanks for asking.</body>
         
     | 
| 
      
 46 
     | 
    
         
            +
                    <createdAt>2011-10-27T9:00:00+02:00</createdAt>
         
     | 
| 
      
 47 
     | 
    
         
            +
                  </userPost>
         
     | 
| 
      
 48 
     | 
    
         
            +
                  <userPost>
         
     | 
| 
      
 49 
     | 
    
         
            +
                    <objectId>3</objectId>
         
     | 
| 
      
 50 
     | 
    
         
            +
                    <parentObjectId>1</parentObjectId>
         
     | 
| 
      
 51 
     | 
    
         
            +
                    <userObjectId>1</userObjectId>
         
     | 
| 
      
 52 
     | 
    
         
            +
                    <user>
         
     | 
| 
      
 53 
     | 
    
         
            +
                      <firstName>John</firstName>
         
     | 
| 
      
 54 
     | 
    
         
            +
                      <lastName>Doe</lastName>
         
     | 
| 
      
 55 
     | 
    
         
            +
                    </user>
         
     | 
| 
      
 56 
     | 
    
         
            +
                    <body>You are more than welcome.</body>
         
     | 
| 
      
 57 
     | 
    
         
            +
                    <createdAt>2011-10-27T9:00:00+02:00</createdAt>
         
     | 
| 
      
 58 
     | 
    
         
            +
                  </userPost>
         
     | 
| 
      
 59 
     | 
    
         
            +
                </replies>
         
     | 
| 
      
 60 
     | 
    
         
            +
              </userPost>
         
     | 
| 
      
 61 
     | 
    
         
            +
              <userPost>
         
     | 
| 
      
 62 
     | 
    
         
            +
                <hidden>true</hidden>
         
     | 
| 
      
 63 
     | 
    
         
            +
              </userPost>
         
     | 
| 
      
 64 
     | 
    
         
            +
            </userPosts>
         
     | 
| 
      
 65 
     | 
    
         
            +
            ~~~
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
            You start by defining a class with methods to retrieve the resource and another class to act as a mapper.
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
            ~~~ ruby
         
     | 
| 
      
 70 
     | 
    
         
            +
            class Post < Ahora::Representation
         
     | 
| 
      
 71 
     | 
    
         
            +
              objectid :id, :user_id, :parent_id
         
     | 
| 
      
 72 
     | 
    
         
            +
              date  :created_at
         
     | 
| 
      
 73 
     | 
    
         
            +
              element :body
         
     | 
| 
      
 74 
     | 
    
         
            +
              element 'user', :with => Ahora::Representation do
         
     | 
| 
      
 75 
     | 
    
         
            +
                string :first_name, :last_name
         
     | 
| 
      
 76 
     | 
    
         
            +
              end
         
     | 
| 
      
 77 
     | 
    
         
            +
              boolean :hidden
         
     | 
| 
      
 78 
     | 
    
         
            +
              elements 'replies/userPost' => :replies, :with => Post
         
     | 
| 
      
 79 
     | 
    
         
            +
            end
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
            class PostResource
         
     | 
| 
      
 82 
     | 
    
         
            +
              include Ahora::Resource
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
              def all
         
     | 
| 
      
 85 
     | 
    
         
            +
                collection Post, get("/api/posts.xml")
         
     | 
| 
      
 86 
     | 
    
         
            +
              end
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
              private
         
     | 
| 
      
 89 
     | 
    
         
            +
              def host
         
     | 
| 
      
 90 
     | 
    
         
            +
                'http://test.net'
         
     | 
| 
      
 91 
     | 
    
         
            +
              end
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
              def extend_middleware(builder)
         
     | 
| 
      
 94 
     | 
    
         
            +
                builder.use Ahora::Middleware::LastModifiedCaching, Rails.cache
         
     | 
| 
      
 95 
     | 
    
         
            +
              end
         
     | 
| 
      
 96 
     | 
    
         
            +
            end
         
     | 
| 
      
 97 
     | 
    
         
            +
            ~~~
         
     | 
| 
      
 98 
     | 
    
         
            +
             
     | 
| 
      
 99 
     | 
    
         
            +
            Now you can define a controller as usual.
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
            ~~~ ruby
         
     | 
| 
      
 102 
     | 
    
         
            +
            class PostsController < ApplicationController::Base
         
     | 
| 
      
 103 
     | 
    
         
            +
              def index
         
     | 
| 
      
 104 
     | 
    
         
            +
                @posts = PostResource.new.all
         
     | 
| 
      
 105 
     | 
    
         
            +
              end
         
     | 
| 
      
 106 
     | 
    
         
            +
            end
         
     | 
| 
      
 107 
     | 
    
         
            +
            ~~~
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
      
 109 
     | 
    
         
            +
            And a view template
         
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
      
 111 
     | 
    
         
            +
            ~~~ html
         
     | 
| 
      
 112 
     | 
    
         
            +
              <% cache @posts, 'index' %>
         
     | 
| 
      
 113 
     | 
    
         
            +
                <%= render(:partial => @posts) %>
         
     | 
| 
      
 114 
     | 
    
         
            +
              <% end >
         
     | 
| 
      
 115 
     | 
    
         
            +
            ~~~
         
     | 
| 
      
 116 
     | 
    
         
            +
             
     | 
| 
      
 117 
     | 
    
         
            +
            And that's all there is. The XML response will be cached, so it saves a bandwith. The XML response will only be parsed if there is no existing HTML fragment cache. All cache will be invalidated when the request to posts.xml returns a new body instead of a 304 Not Modified.
         
     | 
    
        data/Rakefile
    ADDED
    
    
    
        data/ahora.gemspec
    ADDED
    
    | 
         @@ -0,0 +1,27 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # -*- encoding: utf-8 -*-
         
     | 
| 
      
 2 
     | 
    
         
            +
            $:.push File.expand_path("../lib", __FILE__)
         
     | 
| 
      
 3 
     | 
    
         
            +
            require "ahora/version"
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            Gem::Specification.new do |s|
         
     | 
| 
      
 6 
     | 
    
         
            +
              s.name        = "ahora"
         
     | 
| 
      
 7 
     | 
    
         
            +
              s.version     = Ahora::VERSION
         
     | 
| 
      
 8 
     | 
    
         
            +
              s.platform    = Gem::Platform::RUBY
         
     | 
| 
      
 9 
     | 
    
         
            +
              s.authors     = ["Matthijs Langenberg"]
         
     | 
| 
      
 10 
     | 
    
         
            +
              s.email       = ["matthijs.langenberg@nedap.com"]
         
     | 
| 
      
 11 
     | 
    
         
            +
              s.homepage    = ""
         
     | 
| 
      
 12 
     | 
    
         
            +
              s.summary     = %q{Consume Java-ish XML HTTP Resources easily}
         
     | 
| 
      
 13 
     | 
    
         
            +
              s.description = %q{Consume Java-ish XML HTTP Resources easily}
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
              s.files         = `git ls-files`.split("\n")
         
     | 
| 
      
 16 
     | 
    
         
            +
              s.test_files    = `git ls-files -- {test}/*`.split("\n")
         
     | 
| 
      
 17 
     | 
    
         
            +
              #s.executables   = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
         
     | 
| 
      
 18 
     | 
    
         
            +
              s.require_paths = ["lib"]
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
              s.add_dependency "nibbler", '>= 1.3.0'
         
     | 
| 
      
 21 
     | 
    
         
            +
              s.add_dependency "faraday", '>= 0.7'
         
     | 
| 
      
 22 
     | 
    
         
            +
              s.add_dependency "nokogiri", "~> 1.5"
         
     | 
| 
      
 23 
     | 
    
         
            +
              s.add_dependency "activesupport"
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
              s.add_development_dependency "fakeweb"
         
     | 
| 
      
 26 
     | 
    
         
            +
              s.add_development_dependency "minitest"
         
     | 
| 
      
 27 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,88 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Ahora
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Middleware
         
     | 
| 
      
 3 
     | 
    
         
            +
                class LastModifiedCaching < Faraday::Middleware
         
     | 
| 
      
 4 
     | 
    
         
            +
                  attr_reader :cache
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                  extend Forwardable
         
     | 
| 
      
 7 
     | 
    
         
            +
                  def_delegators :'Faraday::Utils', :parse_query, :build_query
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  # Public: initialize the middleware.
         
     | 
| 
      
 10 
     | 
    
         
            +
                  #
         
     | 
| 
      
 11 
     | 
    
         
            +
                  # cache   - An object that responds to read, write and fetch (default: nil).
         
     | 
| 
      
 12 
     | 
    
         
            +
                  # options - An options Hash (default: {}):
         
     | 
| 
      
 13 
     | 
    
         
            +
                  #           :ignore_params - String name or Array names of query params
         
     | 
| 
      
 14 
     | 
    
         
            +
                  #                            that should be ignored when forming the cache
         
     | 
| 
      
 15 
     | 
    
         
            +
                  #                            key (default: []).
         
     | 
| 
      
 16 
     | 
    
         
            +
                  #           :cache_header  - String name of response header that should be
         
     | 
| 
      
 17 
     | 
    
         
            +
                  #                            used to retrieve cache timestamp from.
         
     | 
| 
      
 18 
     | 
    
         
            +
                  #                            Defaults to 'last-modified'
         
     | 
| 
      
 19 
     | 
    
         
            +
                  #           :expire_in     - Integer time to live value in seconds for a key.
         
     | 
| 
      
 20 
     | 
    
         
            +
                  #                            Defaults to never expire.
         
     | 
| 
      
 21 
     | 
    
         
            +
                  #
         
     | 
| 
      
 22 
     | 
    
         
            +
                  # Yields if no cache is given. The block should return a cache object.
         
     | 
| 
      
 23 
     | 
    
         
            +
                  def initialize(app, cache = nil, options = {})
         
     | 
| 
      
 24 
     | 
    
         
            +
                    super(app)
         
     | 
| 
      
 25 
     | 
    
         
            +
                    options, cache = cache, nil if cache.is_a? Hash and block_given?
         
     | 
| 
      
 26 
     | 
    
         
            +
                    @cache = cache || yield
         
     | 
| 
      
 27 
     | 
    
         
            +
                    @options = options
         
     | 
| 
      
 28 
     | 
    
         
            +
                  end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                  def call(env)
         
     | 
| 
      
 31 
     | 
    
         
            +
                    if env[:method] == :get
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                      timestamp_key = cache_key(env) + ':timestamp'
         
     | 
| 
      
 34 
     | 
    
         
            +
                      data_key      = cache_key(env) + ':response'
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                      if date = cache.read(timestamp_key)
         
     | 
| 
      
 37 
     | 
    
         
            +
                        # WARN FakeWeb cannot test this
         
     | 
| 
      
 38 
     | 
    
         
            +
                        env[:request_headers]['If-Modified-Since'] = Time.parse(date).httpdate
         
     | 
| 
      
 39 
     | 
    
         
            +
                      end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                      response = @app.call(env)
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                      if response.status == 304
         
     | 
| 
      
 44 
     | 
    
         
            +
                        response = cache.read data_key
         
     | 
| 
      
 45 
     | 
    
         
            +
                      elsif date = response.headers[@options[:cache_header] || 'Last-Modified']
         
     | 
| 
      
 46 
     | 
    
         
            +
                        response.env[:response_headers]['X-Ahora-Cache-Key'] = fragment_cache_key(env, date)
         
     | 
| 
      
 47 
     | 
    
         
            +
                        cache.write timestamp_key, date, :expire_in => @options[:expire_in]
         
     | 
| 
      
 48 
     | 
    
         
            +
                        cache.write data_key, response, :expire_in => @options[:expire_in]
         
     | 
| 
      
 49 
     | 
    
         
            +
                      end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                      finalize_response(response, env)
         
     | 
| 
      
 52 
     | 
    
         
            +
                    else
         
     | 
| 
      
 53 
     | 
    
         
            +
                      @app.call(env)
         
     | 
| 
      
 54 
     | 
    
         
            +
                    end
         
     | 
| 
      
 55 
     | 
    
         
            +
                  end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                  def fragment_cache_key(env, last_modified)
         
     | 
| 
      
 58 
     | 
    
         
            +
                    cache_key(env) + ":fragment_#{last_modified}"
         
     | 
| 
      
 59 
     | 
    
         
            +
                  end
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                  def cache_key(env)
         
     | 
| 
      
 62 
     | 
    
         
            +
                    url = env[:url].dup
         
     | 
| 
      
 63 
     | 
    
         
            +
                    if url.query && params_to_ignore.any?
         
     | 
| 
      
 64 
     | 
    
         
            +
                      params = parse_query url.query
         
     | 
| 
      
 65 
     | 
    
         
            +
                      params.reject! {|k,| params_to_ignore.include? k }
         
     | 
| 
      
 66 
     | 
    
         
            +
                      url.query = build_query params
         
     | 
| 
      
 67 
     | 
    
         
            +
                    end
         
     | 
| 
      
 68 
     | 
    
         
            +
                    url.normalize!
         
     | 
| 
      
 69 
     | 
    
         
            +
                    url.to_s
         
     | 
| 
      
 70 
     | 
    
         
            +
                  end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                  def params_to_ignore
         
     | 
| 
      
 73 
     | 
    
         
            +
                    @params_to_ignore ||= Array(@options[:ignore_params]).map { |p| p.to_s }
         
     | 
| 
      
 74 
     | 
    
         
            +
                  end
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                  def finalize_response(response, env)
         
     | 
| 
      
 77 
     | 
    
         
            +
                    response = response.dup if response.frozen?
         
     | 
| 
      
 78 
     | 
    
         
            +
                    env[:response] = response
         
     | 
| 
      
 79 
     | 
    
         
            +
                    unless env[:response_headers]
         
     | 
| 
      
 80 
     | 
    
         
            +
                      env.update response.env
         
     | 
| 
      
 81 
     | 
    
         
            +
                      # FIXME: omg hax
         
     | 
| 
      
 82 
     | 
    
         
            +
                      response.instance_variable_set('@env', env)
         
     | 
| 
      
 83 
     | 
    
         
            +
                    end
         
     | 
| 
      
 84 
     | 
    
         
            +
                    response
         
     | 
| 
      
 85 
     | 
    
         
            +
                  end
         
     | 
| 
      
 86 
     | 
    
         
            +
                end
         
     | 
| 
      
 87 
     | 
    
         
            +
              end
         
     | 
| 
      
 88 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,85 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Ahora
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Middleware
         
     | 
| 
      
 3 
     | 
    
         
            +
                class RequestLogger < Faraday::Response::Middleware
         
     | 
| 
      
 4 
     | 
    
         
            +
                  def initialize(app, logger)
         
     | 
| 
      
 5 
     | 
    
         
            +
                    super(app)
         
     | 
| 
      
 6 
     | 
    
         
            +
                    @logger = logger || begin
         
     | 
| 
      
 7 
     | 
    
         
            +
                      require 'logger'
         
     | 
| 
      
 8 
     | 
    
         
            +
                      Logger.new(STDOUT)
         
     | 
| 
      
 9 
     | 
    
         
            +
                    end
         
     | 
| 
      
 10 
     | 
    
         
            +
                  end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                  def call(env)
         
     | 
| 
      
 13 
     | 
    
         
            +
                    @logger.info "#{env[:method].to_s.upcase} #{env[:url].to_s}"
         
     | 
| 
      
 14 
     | 
    
         
            +
                    @started = Time.now
         
     | 
| 
      
 15 
     | 
    
         
            +
                    super
         
     | 
| 
      
 16 
     | 
    
         
            +
                  end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                  def on_complete(env)
         
     | 
| 
      
 19 
     | 
    
         
            +
                    duration = 1000.0 * (Time.now - @started)
         
     | 
| 
      
 20 
     | 
    
         
            +
                    kbytes = env[:body].to_s.length / 1024.0
         
     | 
| 
      
 21 
     | 
    
         
            +
                    @logger.info "--> %d %s %.2fKB (%.1fms)" % [env[:status], HTTP_STATUS_CODES[env[:status]], kbytes, duration]
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                  # Every standard HTTP code mapped to the appropriate message.
         
     | 
| 
      
 25 
     | 
    
         
            +
                  # Generated with:
         
     | 
| 
      
 26 
     | 
    
         
            +
                  #   curl -s http://www.iana.org/assignments/http-status-codes | \
         
     | 
| 
      
 27 
     | 
    
         
            +
                  #     ruby -ane 'm = /^(\d{3}) +(\S[^\[(]+)/.match($_) and
         
     | 
| 
      
 28 
     | 
    
         
            +
                  #                puts "      #{m[1]}  => \x27#{m[2].strip}x27,"'
         
     | 
| 
      
 29 
     | 
    
         
            +
                  HTTP_STATUS_CODES = {
         
     | 
| 
      
 30 
     | 
    
         
            +
                    100  => 'Continue',
         
     | 
| 
      
 31 
     | 
    
         
            +
                    101  => 'Switching Protocols',
         
     | 
| 
      
 32 
     | 
    
         
            +
                    102  => 'Processing',
         
     | 
| 
      
 33 
     | 
    
         
            +
                    200  => 'OK',
         
     | 
| 
      
 34 
     | 
    
         
            +
                    201  => 'Created',
         
     | 
| 
      
 35 
     | 
    
         
            +
                    202  => 'Accepted',
         
     | 
| 
      
 36 
     | 
    
         
            +
                    203  => 'Non-Authoritative Information',
         
     | 
| 
      
 37 
     | 
    
         
            +
                    204  => 'No Content',
         
     | 
| 
      
 38 
     | 
    
         
            +
                    205  => 'Reset Content',
         
     | 
| 
      
 39 
     | 
    
         
            +
                    206  => 'Partial Content',
         
     | 
| 
      
 40 
     | 
    
         
            +
                    207  => 'Multi-Status',
         
     | 
| 
      
 41 
     | 
    
         
            +
                    226  => 'IM Used',
         
     | 
| 
      
 42 
     | 
    
         
            +
                    300  => 'Multiple Choices',
         
     | 
| 
      
 43 
     | 
    
         
            +
                    301  => 'Moved Permanently',
         
     | 
| 
      
 44 
     | 
    
         
            +
                    302  => 'Found',
         
     | 
| 
      
 45 
     | 
    
         
            +
                    303  => 'See Other',
         
     | 
| 
      
 46 
     | 
    
         
            +
                    304  => 'Not Modified',
         
     | 
| 
      
 47 
     | 
    
         
            +
                    305  => 'Use Proxy',
         
     | 
| 
      
 48 
     | 
    
         
            +
                    306  => 'Reserved',
         
     | 
| 
      
 49 
     | 
    
         
            +
                    307  => 'Temporary Redirect',
         
     | 
| 
      
 50 
     | 
    
         
            +
                    400  => 'Bad Request',
         
     | 
| 
      
 51 
     | 
    
         
            +
                    401  => 'Unauthorized',
         
     | 
| 
      
 52 
     | 
    
         
            +
                    402  => 'Payment Required',
         
     | 
| 
      
 53 
     | 
    
         
            +
                    403  => 'Forbidden',
         
     | 
| 
      
 54 
     | 
    
         
            +
                    404  => 'Not Found',
         
     | 
| 
      
 55 
     | 
    
         
            +
                    405  => 'Method Not Allowed',
         
     | 
| 
      
 56 
     | 
    
         
            +
                    406  => 'Not Acceptable',
         
     | 
| 
      
 57 
     | 
    
         
            +
                    407  => 'Proxy Authentication Required',
         
     | 
| 
      
 58 
     | 
    
         
            +
                    408  => 'Request Timeout',
         
     | 
| 
      
 59 
     | 
    
         
            +
                    409  => 'Conflict',
         
     | 
| 
      
 60 
     | 
    
         
            +
                    410  => 'Gone',
         
     | 
| 
      
 61 
     | 
    
         
            +
                    411  => 'Length Required',
         
     | 
| 
      
 62 
     | 
    
         
            +
                    412  => 'Precondition Failed',
         
     | 
| 
      
 63 
     | 
    
         
            +
                    413  => 'Request Entity Too Large',
         
     | 
| 
      
 64 
     | 
    
         
            +
                    414  => 'Request-URI Too Long',
         
     | 
| 
      
 65 
     | 
    
         
            +
                    415  => 'Unsupported Media Type',
         
     | 
| 
      
 66 
     | 
    
         
            +
                    416  => 'Requested Range Not Satisfiable',
         
     | 
| 
      
 67 
     | 
    
         
            +
                    417  => 'Expectation Failed',
         
     | 
| 
      
 68 
     | 
    
         
            +
                    418  => "I'm a Teapot",
         
     | 
| 
      
 69 
     | 
    
         
            +
                    422  => 'Unprocessable Entity',
         
     | 
| 
      
 70 
     | 
    
         
            +
                    423  => 'Locked',
         
     | 
| 
      
 71 
     | 
    
         
            +
                    424  => 'Failed Dependency',
         
     | 
| 
      
 72 
     | 
    
         
            +
                    426  => 'Upgrade Required',
         
     | 
| 
      
 73 
     | 
    
         
            +
                    500  => 'Internal Server Error',
         
     | 
| 
      
 74 
     | 
    
         
            +
                    501  => 'Not Implemented',
         
     | 
| 
      
 75 
     | 
    
         
            +
                    502  => 'Bad Gateway',
         
     | 
| 
      
 76 
     | 
    
         
            +
                    503  => 'Service Unavailable',
         
     | 
| 
      
 77 
     | 
    
         
            +
                    504  => 'Gateway Timeout',
         
     | 
| 
      
 78 
     | 
    
         
            +
                    505  => 'HTTP Version Not Supported',
         
     | 
| 
      
 79 
     | 
    
         
            +
                    506  => 'Variant Also Negotiates',
         
     | 
| 
      
 80 
     | 
    
         
            +
                    507  => 'Insufficient Storage',
         
     | 
| 
      
 81 
     | 
    
         
            +
                    510  => 'Not Extended',
         
     | 
| 
      
 82 
     | 
    
         
            +
                  }
         
     | 
| 
      
 83 
     | 
    
         
            +
                end
         
     | 
| 
      
 84 
     | 
    
         
            +
              end
         
     | 
| 
      
 85 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,122 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'nibbler'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'nokogiri'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'date'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'active_support/core_ext/string'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require 'delegate'
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            module Ahora
         
     | 
| 
      
 8 
     | 
    
         
            +
              class Representation < Nibbler
         
     | 
| 
      
 9 
     | 
    
         
            +
                INTEGER_PARSER = lambda { |node| Integer(node.content) if node.content.present? }
         
     | 
| 
      
 10 
     | 
    
         
            +
                DATE_PARSER = lambda { |node| Date.parse(node.content) if node.content.present? }
         
     | 
| 
      
 11 
     | 
    
         
            +
                TIME_PARSER = lambda { |node| Time.parse(node.content) if node.content.present? }
         
     | 
| 
      
 12 
     | 
    
         
            +
                BOOL_PARSER =
         
     | 
| 
      
 13 
     | 
    
         
            +
                    lambda { |node| node.content.to_s.downcase == 'true' if node.content.present? }
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                module Definition
         
     | 
| 
      
 16 
     | 
    
         
            +
                  def element(*)
         
     | 
| 
      
 17 
     | 
    
         
            +
                    name = super
         
     | 
| 
      
 18 
     | 
    
         
            +
                    define_method "#{name}?" do
         
     | 
| 
      
 19 
     | 
    
         
            +
                      !!instance_variable_get("@#{name}")
         
     | 
| 
      
 20 
     | 
    
         
            +
                    end
         
     | 
| 
      
 21 
     | 
    
         
            +
                    name
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                  def attribute(*names)
         
     | 
| 
      
 25 
     | 
    
         
            +
                    names = names.flatten
         
     | 
| 
      
 26 
     | 
    
         
            +
                    parser = names.pop if names.last.is_a?(Proc)
         
     | 
| 
      
 27 
     | 
    
         
            +
                    names.each do |name|
         
     | 
| 
      
 28 
     | 
    
         
            +
                      selector = name
         
     | 
| 
      
 29 
     | 
    
         
            +
                      if name.is_a? Hash
         
     | 
| 
      
 30 
     | 
    
         
            +
                        selector, name = name.first
         
     | 
| 
      
 31 
     | 
    
         
            +
                      end
         
     | 
| 
      
 32 
     | 
    
         
            +
                      element selector.to_s.camelcase(:lower) => name.to_s, :with => parser
         
     | 
| 
      
 33 
     | 
    
         
            +
                    end
         
     | 
| 
      
 34 
     | 
    
         
            +
                  end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                  # Public: define Java style object id mapping
         
     | 
| 
      
 37 
     | 
    
         
            +
                  #
         
     | 
| 
      
 38 
     | 
    
         
            +
                  # *names - Array of String or Symbol ruby style names
         
     | 
| 
      
 39 
     | 
    
         
            +
                  #
         
     | 
| 
      
 40 
     | 
    
         
            +
                  # Examples
         
     | 
| 
      
 41 
     | 
    
         
            +
                  #
         
     | 
| 
      
 42 
     | 
    
         
            +
                  # objectid :id, parent_id
         
     | 
| 
      
 43 
     | 
    
         
            +
                  # # is equivalent to
         
     | 
| 
      
 44 
     | 
    
         
            +
                  # element 'objectId' => 'id'
         
     | 
| 
      
 45 
     | 
    
         
            +
                  # element 'parentObjectId' => 'parent_id'
         
     | 
| 
      
 46 
     | 
    
         
            +
                  def objectid(*names)
         
     | 
| 
      
 47 
     | 
    
         
            +
                    attribute names.map { |name| { name.to_s.gsub('id', 'object_id') => name } }, INTEGER_PARSER
         
     | 
| 
      
 48 
     | 
    
         
            +
                  end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                  def string(*names)
         
     | 
| 
      
 51 
     | 
    
         
            +
                    attribute(names)
         
     | 
| 
      
 52 
     | 
    
         
            +
                  end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                  def integer(*names)
         
     | 
| 
      
 55 
     | 
    
         
            +
                    attribute(names, INTEGER_PARSER)
         
     | 
| 
      
 56 
     | 
    
         
            +
                  end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                  def date(*names)
         
     | 
| 
      
 59 
     | 
    
         
            +
                    attribute(names, DATE_PARSER)
         
     | 
| 
      
 60 
     | 
    
         
            +
                  end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                  # FIXME test
         
     | 
| 
      
 63 
     | 
    
         
            +
                  def time(*names)
         
     | 
| 
      
 64 
     | 
    
         
            +
                    attribute(names, TIME_PARSER)
         
     | 
| 
      
 65 
     | 
    
         
            +
                  end
         
     | 
| 
      
 66 
     | 
    
         
            +
                  
         
     | 
| 
      
 67 
     | 
    
         
            +
                  def boolean(*names)
         
     | 
| 
      
 68 
     | 
    
         
            +
                    attribute(names, BOOL_PARSER)
         
     | 
| 
      
 69 
     | 
    
         
            +
                  end
         
     | 
| 
      
 70 
     | 
    
         
            +
                end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                extend Definition
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                def initialize(doc_or_atts = {})
         
     | 
| 
      
 75 
     | 
    
         
            +
                  if doc_or_atts.is_a? Hash
         
     | 
| 
      
 76 
     | 
    
         
            +
                    super(nil)
         
     | 
| 
      
 77 
     | 
    
         
            +
                    doc_or_atts.each do |key, val|
         
     | 
| 
      
 78 
     | 
    
         
            +
                      send("#{key}=", val)
         
     | 
| 
      
 79 
     | 
    
         
            +
                    end
         
     | 
| 
      
 80 
     | 
    
         
            +
                  else
         
     | 
| 
      
 81 
     | 
    
         
            +
                    super
         
     | 
| 
      
 82 
     | 
    
         
            +
                  end
         
     | 
| 
      
 83 
     | 
    
         
            +
                end
         
     | 
| 
      
 84 
     | 
    
         
            +
              end
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
              class Collection < DelegateClass(Array)
         
     | 
| 
      
 87 
     | 
    
         
            +
                NoCacheKeyAvailable = Class.new(StandardError)
         
     | 
| 
      
 88 
     | 
    
         
            +
                def initialize(instantiator, document_parser, response)
         
     | 
| 
      
 89 
     | 
    
         
            +
                  @instantiator = instantiator
         
     | 
| 
      
 90 
     | 
    
         
            +
                  @document_parser = document_parser
         
     | 
| 
      
 91 
     | 
    
         
            +
                  @response = response
         
     | 
| 
      
 92 
     | 
    
         
            +
                  @cache_key = response.env[:response_headers]['X-Ahora-Cache-Key']
         
     | 
| 
      
 93 
     | 
    
         
            +
                  super([])
         
     | 
| 
      
 94 
     | 
    
         
            +
                end
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                def cache_key
         
     | 
| 
      
 97 
     | 
    
         
            +
                  @cache_key or raise NoCacheKeyAvailable,
         
     | 
| 
      
 98 
     | 
    
         
            +
                      "No caching middleware is used or resource does not support caching."
         
     | 
| 
      
 99 
     | 
    
         
            +
                end
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
                %w( to_s to_a size each first last [] inspect pretty_print ).each do |method_name|
         
     | 
| 
      
 102 
     | 
    
         
            +
                  eval "def #{method_name}(*); kicker; super; end"
         
     | 
| 
      
 103 
     | 
    
         
            +
                end
         
     | 
| 
      
 104 
     | 
    
         
            +
             
     | 
| 
      
 105 
     | 
    
         
            +
                private
         
     | 
| 
      
 106 
     | 
    
         
            +
                def kicker
         
     | 
| 
      
 107 
     | 
    
         
            +
                  @_collection ||= __setobj__ doc.search("/*[@type='array']/*").map { |element|
         
     | 
| 
      
 108 
     | 
    
         
            +
                    @instantiator.call element
         
     | 
| 
      
 109 
     | 
    
         
            +
                  }.to_a.compact
         
     | 
| 
      
 110 
     | 
    
         
            +
                end
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
                def doc
         
     | 
| 
      
 113 
     | 
    
         
            +
                  @document_parser.call(@response.body)
         
     | 
| 
      
 114 
     | 
    
         
            +
                end
         
     | 
| 
      
 115 
     | 
    
         
            +
              end
         
     | 
| 
      
 116 
     | 
    
         
            +
             
     | 
| 
      
 117 
     | 
    
         
            +
              class XmlParser
         
     | 
| 
      
 118 
     | 
    
         
            +
                def self.parse(body)
         
     | 
| 
      
 119 
     | 
    
         
            +
                  Nokogiri::XML.parse(body, nil, nil, Nokogiri::XML::ParseOptions::STRICT)
         
     | 
| 
      
 120 
     | 
    
         
            +
                end
         
     | 
| 
      
 121 
     | 
    
         
            +
              end
         
     | 
| 
      
 122 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,68 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'faraday'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Ahora
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Resource
         
     | 
| 
      
 5 
     | 
    
         
            +
                attr_writer :document_parser
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                def get(url, params = {})
         
     | 
| 
      
 8 
     | 
    
         
            +
                  connection.get do |req|
         
     | 
| 
      
 9 
     | 
    
         
            +
                    set_common_headers(req)
         
     | 
| 
      
 10 
     | 
    
         
            +
                    req.url url, params
         
     | 
| 
      
 11 
     | 
    
         
            +
                  end
         
     | 
| 
      
 12 
     | 
    
         
            +
                end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                # FIXME test
         
     | 
| 
      
 15 
     | 
    
         
            +
                def post(url, body)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  connection.post do |req|
         
     | 
| 
      
 17 
     | 
    
         
            +
                    set_common_headers(req)
         
     | 
| 
      
 18 
     | 
    
         
            +
                    req.url url
         
     | 
| 
      
 19 
     | 
    
         
            +
                    req.body = body
         
     | 
| 
      
 20 
     | 
    
         
            +
                  end
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                # FIXME test
         
     | 
| 
      
 24 
     | 
    
         
            +
                def put(url, body)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  connection.put do |req|
         
     | 
| 
      
 26 
     | 
    
         
            +
                    set_common_headers(req)
         
     | 
| 
      
 27 
     | 
    
         
            +
                    req.url url
         
     | 
| 
      
 28 
     | 
    
         
            +
                    req.body = body
         
     | 
| 
      
 29 
     | 
    
         
            +
                  end
         
     | 
| 
      
 30 
     | 
    
         
            +
                end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                def connection
         
     | 
| 
      
 33 
     | 
    
         
            +
                  conn = Faraday.new(host, :ssl => { :verify => false }) do |builder|
         
     | 
| 
      
 34 
     | 
    
         
            +
                    builder.use Faraday::Response::RaiseError
         
     | 
| 
      
 35 
     | 
    
         
            +
                    extend_middleware(builder)
         
     | 
| 
      
 36 
     | 
    
         
            +
                    builder.adapter Faraday.default_adapter
         
     | 
| 
      
 37 
     | 
    
         
            +
                  end
         
     | 
| 
      
 38 
     | 
    
         
            +
                  conn.headers['User-Agent'] = 'Ahora'
         
     | 
| 
      
 39 
     | 
    
         
            +
                  conn
         
     | 
| 
      
 40 
     | 
    
         
            +
                end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                # @abstract override to use custom Faraday middleware
         
     | 
| 
      
 43 
     | 
    
         
            +
                def extend_middleware(builder); end;
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                def collection(*args, &block)
         
     | 
| 
      
 46 
     | 
    
         
            +
                  if args.size == 2
         
     | 
| 
      
 47 
     | 
    
         
            +
                    klass, response = args
         
     | 
| 
      
 48 
     | 
    
         
            +
                    instantiator = lambda do |doc|
         
     | 
| 
      
 49 
     | 
    
         
            +
                      klass.parse(doc)
         
     | 
| 
      
 50 
     | 
    
         
            +
                    end
         
     | 
| 
      
 51 
     | 
    
         
            +
                  else
         
     | 
| 
      
 52 
     | 
    
         
            +
                    response = args.first
         
     | 
| 
      
 53 
     | 
    
         
            +
                    instantiator = block
         
     | 
| 
      
 54 
     | 
    
         
            +
                  end
         
     | 
| 
      
 55 
     | 
    
         
            +
                  Collection.new instantiator, document_parser, response
         
     | 
| 
      
 56 
     | 
    
         
            +
                end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                private
         
     | 
| 
      
 59 
     | 
    
         
            +
                def document_parser
         
     | 
| 
      
 60 
     | 
    
         
            +
                  @document_parser ||= XmlParser.method(:parse)
         
     | 
| 
      
 61 
     | 
    
         
            +
                end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                def set_common_headers(req)
         
     | 
| 
      
 64 
     | 
    
         
            +
                  req.headers['Content-Type'] = 'application/xml'
         
     | 
| 
      
 65 
     | 
    
         
            +
                  req.headers['Accept'] = 'application/xml'
         
     | 
| 
      
 66 
     | 
    
         
            +
                end
         
     | 
| 
      
 67 
     | 
    
         
            +
              end
         
     | 
| 
      
 68 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/ahora.rb
    ADDED
    
    
| 
         @@ -0,0 +1,39 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'minitest/autorun'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'minitest/pride'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require_relative '../test_helper'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require_relative '../../lib/ahora/representation'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            class Employee < Ahora::Representation
         
     | 
| 
      
 7 
     | 
    
         
            +
              boolean :is_rockstar, :slacker, :fired
         
     | 
| 
      
 8 
     | 
    
         
            +
            end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            describe "boolean elements" do
         
     | 
| 
      
 11 
     | 
    
         
            +
              let(:employee) { Employee.parse(fixture('employee').read) }
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
              it "parsing 'true' adds the correct reader" do
         
     | 
| 
      
 14 
     | 
    
         
            +
                employee.is_rockstar.must_equal true
         
     | 
| 
      
 15 
     | 
    
         
            +
                employee.is_rockstar = false
         
     | 
| 
      
 16 
     | 
    
         
            +
                employee.is_rockstar.must_equal false
         
     | 
| 
      
 17 
     | 
    
         
            +
                employee.is_rockstar?.must_equal false
         
     | 
| 
      
 18 
     | 
    
         
            +
              end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
              it "parsing 'false' adds the correct reader" do
         
     | 
| 
      
 21 
     | 
    
         
            +
                employee.slacker.must_equal false
         
     | 
| 
      
 22 
     | 
    
         
            +
              end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
              it "parsing missing value adds the correct reader" do
         
     | 
| 
      
 25 
     | 
    
         
            +
                employee.fired.must_equal nil
         
     | 
| 
      
 26 
     | 
    
         
            +
              end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
              it "parsing 'true' adds the correct question mark reader" do
         
     | 
| 
      
 29 
     | 
    
         
            +
                employee.is_rockstar?.must_equal true
         
     | 
| 
      
 30 
     | 
    
         
            +
              end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
              it "parsing 'false' adds the correct question mark reader" do
         
     | 
| 
      
 33 
     | 
    
         
            +
                employee.slacker?.must_equal false
         
     | 
| 
      
 34 
     | 
    
         
            +
              end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
              it "parsing missing value adds the correct question mark reader" do
         
     | 
| 
      
 37 
     | 
    
         
            +
                employee.fired?.must_equal false
         
     | 
| 
      
 38 
     | 
    
         
            +
              end
         
     | 
| 
      
 39 
     | 
    
         
            +
            end
         
     | 
    
        data/test/ahora_test.rb
    ADDED
    
    | 
         @@ -0,0 +1,214 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'fakeweb'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'minitest/autorun'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'minitest/pride'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require_relative 'test_helper'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require_relative '../lib/ahora'
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            FakeWeb.allow_net_connect = false
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            require 'singleton'
         
     | 
| 
      
 10 
     | 
    
         
            +
            class MemCache
         
     | 
| 
      
 11 
     | 
    
         
            +
              include Singleton
         
     | 
| 
      
 12 
     | 
    
         
            +
              def initialize
         
     | 
| 
      
 13 
     | 
    
         
            +
                @store = {}
         
     | 
| 
      
 14 
     | 
    
         
            +
              end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
              def read(key)
         
     | 
| 
      
 17 
     | 
    
         
            +
                @store[key] ? Marshal.load(@store[key]) : nil
         
     | 
| 
      
 18 
     | 
    
         
            +
              end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
              def write(key, value, options = {})
         
     | 
| 
      
 21 
     | 
    
         
            +
                @store[key] = Marshal.dump(value)
         
     | 
| 
      
 22 
     | 
    
         
            +
              end
         
     | 
| 
      
 23 
     | 
    
         
            +
            end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
            class PostRepository
         
     | 
| 
      
 26 
     | 
    
         
            +
              include Ahora::Resource
         
     | 
| 
      
 27 
     | 
    
         
            +
              USERNAME = 'user'
         
     | 
| 
      
 28 
     | 
    
         
            +
              PASSWORD = 'pass'
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
              def find_by_user_id(id)
         
     | 
| 
      
 31 
     | 
    
         
            +
                collection Post, get("/users/#{id}/posts.xml")
         
     | 
| 
      
 32 
     | 
    
         
            +
              end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
              def extend_middleware(builder)
         
     | 
| 
      
 35 
     | 
    
         
            +
                builder.use Faraday::Request::BasicAuthentication, USERNAME, PASSWORD
         
     | 
| 
      
 36 
     | 
    
         
            +
                builder.use Ahora::Middleware::LastModifiedCaching, MemCache.instance
         
     | 
| 
      
 37 
     | 
    
         
            +
              end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
              def host
         
     | 
| 
      
 40 
     | 
    
         
            +
                'http://test.net/'
         
     | 
| 
      
 41 
     | 
    
         
            +
              end
         
     | 
| 
      
 42 
     | 
    
         
            +
            end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
            class Post < Ahora::Representation
         
     | 
| 
      
 45 
     | 
    
         
            +
              objectid :id, :user_id, :parent_id
         
     | 
| 
      
 46 
     | 
    
         
            +
              date  :created_at
         
     | 
| 
      
 47 
     | 
    
         
            +
              element :body
         
     | 
| 
      
 48 
     | 
    
         
            +
              element 'user', :with => Ahora::Representation do
         
     | 
| 
      
 49 
     | 
    
         
            +
                string :first_name, :last_name
         
     | 
| 
      
 50 
     | 
    
         
            +
              end
         
     | 
| 
      
 51 
     | 
    
         
            +
              boolean :hidden
         
     | 
| 
      
 52 
     | 
    
         
            +
              elements 'replies/userPost' => :replies, :with => Post
         
     | 
| 
      
 53 
     | 
    
         
            +
            end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
            class PostDomainRepository < PostRepository
         
     | 
| 
      
 56 
     | 
    
         
            +
              def find_by_user_id(id)
         
     | 
| 
      
 57 
     | 
    
         
            +
                collection PostDomain, get("/users/#{id}/posts.xml")
         
     | 
| 
      
 58 
     | 
    
         
            +
              end
         
     | 
| 
      
 59 
     | 
    
         
            +
            end
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
            class PostDomain < DelegateClass(Post)
         
     | 
| 
      
 62 
     | 
    
         
            +
              def self.parse(doc)
         
     | 
| 
      
 63 
     | 
    
         
            +
                post = Post.parse(doc)
         
     | 
| 
      
 64 
     | 
    
         
            +
                post.hidden? ? nil : new(post)
         
     | 
| 
      
 65 
     | 
    
         
            +
              end
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
              def initialize(post)
         
     | 
| 
      
 68 
     | 
    
         
            +
                super(post)
         
     | 
| 
      
 69 
     | 
    
         
            +
              end
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
              def published?
         
     | 
| 
      
 72 
     | 
    
         
            +
                created_at < Date.today
         
     | 
| 
      
 73 
     | 
    
         
            +
              end
         
     | 
| 
      
 74 
     | 
    
         
            +
            end
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
            class BlogPostRepository < PostRepository
         
     | 
| 
      
 77 
     | 
    
         
            +
              include Ahora::Resource
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
              def all
         
     | 
| 
      
 80 
     | 
    
         
            +
                collection get("/users/1/posts.xml") do |doc|
         
     | 
| 
      
 81 
     | 
    
         
            +
                  Post.parse(doc)
         
     | 
| 
      
 82 
     | 
    
         
            +
                end
         
     | 
| 
      
 83 
     | 
    
         
            +
              end
         
     | 
| 
      
 84 
     | 
    
         
            +
            end
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
            describe "requesting a collection" do
         
     | 
| 
      
 87 
     | 
    
         
            +
              before do
         
     | 
| 
      
 88 
     | 
    
         
            +
                fake_http '/users/1/posts.xml', fixture('user_posts')
         
     | 
| 
      
 89 
     | 
    
         
            +
                @posts = PostRepository.new.find_by_user_id(1)
         
     | 
| 
      
 90 
     | 
    
         
            +
              end
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
              it "has the right size" do
         
     | 
| 
      
 93 
     | 
    
         
            +
                @posts.size.must_equal 2
         
     | 
| 
      
 94 
     | 
    
         
            +
              end
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
              it "raises on #cache_key, because the backend does not has support for it" do
         
     | 
| 
      
 97 
     | 
    
         
            +
                -> { @posts.cache_key }.must_raise(Ahora::Collection::NoCacheKeyAvailable)
         
     | 
| 
      
 98 
     | 
    
         
            +
              end
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
              describe "a single post from the collection" do
         
     | 
| 
      
 101 
     | 
    
         
            +
                subject { @posts.first }
         
     | 
| 
      
 102 
     | 
    
         
            +
             
     | 
| 
      
 103 
     | 
    
         
            +
                it "has the right body" do
         
     | 
| 
      
 104 
     | 
    
         
            +
                  subject.body.must_equal "How is everybody today?"
         
     | 
| 
      
 105 
     | 
    
         
            +
                end
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
                it "generates a questionmark method" do
         
     | 
| 
      
 108 
     | 
    
         
            +
                  subject.body?.must_equal true
         
     | 
| 
      
 109 
     | 
    
         
            +
                end
         
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
      
 111 
     | 
    
         
            +
                it "renames foreign element names and converts integers" do
         
     | 
| 
      
 112 
     | 
    
         
            +
                  subject.id.must_equal 1
         
     | 
| 
      
 113 
     | 
    
         
            +
                  subject.user_id.must_equal 1
         
     | 
| 
      
 114 
     | 
    
         
            +
                end
         
     | 
| 
      
 115 
     | 
    
         
            +
             
     | 
| 
      
 116 
     | 
    
         
            +
                it "must handle date conversion" do
         
     | 
| 
      
 117 
     | 
    
         
            +
                  subject.created_at.must_equal Date.parse("2011-10-26T17:01:52+02:00")
         
     | 
| 
      
 118 
     | 
    
         
            +
                end
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
                it "handles nested resources" do
         
     | 
| 
      
 121 
     | 
    
         
            +
                  subject.user.first_name.must_equal "John"
         
     | 
| 
      
 122 
     | 
    
         
            +
                end
         
     | 
| 
      
 123 
     | 
    
         
            +
             
     | 
| 
      
 124 
     | 
    
         
            +
                it "handles nested collection resources" do
         
     | 
| 
      
 125 
     | 
    
         
            +
                  subject.replies.size.must_equal 2
         
     | 
| 
      
 126 
     | 
    
         
            +
                end
         
     | 
| 
      
 127 
     | 
    
         
            +
             
     | 
| 
      
 128 
     | 
    
         
            +
                describe "a single reply" do
         
     | 
| 
      
 129 
     | 
    
         
            +
                  let(:reply) { subject.replies.first }
         
     | 
| 
      
 130 
     | 
    
         
            +
             
     | 
| 
      
 131 
     | 
    
         
            +
                  it "behaves the same as a single post" do
         
     | 
| 
      
 132 
     | 
    
         
            +
                    reply.id.must_equal 2
         
     | 
| 
      
 133 
     | 
    
         
            +
                    reply.user_id.must_equal 2
         
     | 
| 
      
 134 
     | 
    
         
            +
                    reply.parent_id.must_equal 1
         
     | 
| 
      
 135 
     | 
    
         
            +
                    reply.user.last_name.must_equal "Smith"
         
     | 
| 
      
 136 
     | 
    
         
            +
                  end
         
     | 
| 
      
 137 
     | 
    
         
            +
                end
         
     | 
| 
      
 138 
     | 
    
         
            +
              end
         
     | 
| 
      
 139 
     | 
    
         
            +
            end
         
     | 
| 
      
 140 
     | 
    
         
            +
             
     | 
| 
      
 141 
     | 
    
         
            +
            describe 'requesting a collection with if-modified-since support' do
         
     | 
| 
      
 142 
     | 
    
         
            +
              before do
         
     | 
| 
      
 143 
     | 
    
         
            +
                FakeWeb.register_uri :get, uri('/users/1/posts.xml'), [
         
     | 
| 
      
 144 
     | 
    
         
            +
                  { :body => fixture('user_posts'), 'Last-Modified' => 'Mon, 02 Apr 2012 15:20:41 GMT' },
         
     | 
| 
      
 145 
     | 
    
         
            +
                  { :body => nil, :status => [304, 'Not Modified'] }
         
     | 
| 
      
 146 
     | 
    
         
            +
                ]
         
     | 
| 
      
 147 
     | 
    
         
            +
                @repository = PostRepository.new
         
     | 
| 
      
 148 
     | 
    
         
            +
              end
         
     | 
| 
      
 149 
     | 
    
         
            +
             
     | 
| 
      
 150 
     | 
    
         
            +
              it "has a cache key" do
         
     | 
| 
      
 151 
     | 
    
         
            +
                @posts = @repository.find_by_user_id(1)
         
     | 
| 
      
 152 
     | 
    
         
            +
                @posts.cache_key.
         
     | 
| 
      
 153 
     | 
    
         
            +
                    must_equal 'http://test.net/users/1/posts.xml:fragment_Mon, 02 Apr 2012 15:20:41 GMT'
         
     | 
| 
      
 154 
     | 
    
         
            +
              end
         
     | 
| 
      
 155 
     | 
    
         
            +
             
     | 
| 
      
 156 
     | 
    
         
            +
              it "has a cache key for cached response" do
         
     | 
| 
      
 157 
     | 
    
         
            +
                @repository.find_by_user_id(1).cache_key.
         
     | 
| 
      
 158 
     | 
    
         
            +
                    must_equal @repository.find_by_user_id(1).cache_key
         
     | 
| 
      
 159 
     | 
    
         
            +
              end
         
     | 
| 
      
 160 
     | 
    
         
            +
             
     | 
| 
      
 161 
     | 
    
         
            +
              it "caches when response header includes Last-Modified" do
         
     | 
| 
      
 162 
     | 
    
         
            +
                @repository.find_by_user_id(1).size.must_equal(2)
         
     | 
| 
      
 163 
     | 
    
         
            +
                @posts = @repository.find_by_user_id(1).size.must_equal(2)
         
     | 
| 
      
 164 
     | 
    
         
            +
              end
         
     | 
| 
      
 165 
     | 
    
         
            +
            end
         
     | 
| 
      
 166 
     | 
    
         
            +
             
     | 
| 
      
 167 
     | 
    
         
            +
            describe 'lazy loading' do
         
     | 
| 
      
 168 
     | 
    
         
            +
              before do
         
     | 
| 
      
 169 
     | 
    
         
            +
                @repository = PostRepository.new
         
     | 
| 
      
 170 
     | 
    
         
            +
                @repository.document_parser = -> body { raise('NotLazyEnough') }
         
     | 
| 
      
 171 
     | 
    
         
            +
              end
         
     | 
| 
      
 172 
     | 
    
         
            +
             
     | 
| 
      
 173 
     | 
    
         
            +
              it "should not parse the response if not necessary" do
         
     | 
| 
      
 174 
     | 
    
         
            +
                fake_http '/users/1/posts.xml', fixture('user_posts')
         
     | 
| 
      
 175 
     | 
    
         
            +
                @posts = PostRepository.new.find_by_user_id(1)
         
     | 
| 
      
 176 
     | 
    
         
            +
              end
         
     | 
| 
      
 177 
     | 
    
         
            +
             
     | 
| 
      
 178 
     | 
    
         
            +
              it "should work the same for domain layer type models" do
         
     | 
| 
      
 179 
     | 
    
         
            +
                fake_http '/users/1/posts.xml', fixture('user_posts')
         
     | 
| 
      
 180 
     | 
    
         
            +
                PostDomainRepository.new.find_by_user_id(1)
         
     | 
| 
      
 181 
     | 
    
         
            +
              end
         
     | 
| 
      
 182 
     | 
    
         
            +
            end
         
     | 
| 
      
 183 
     | 
    
         
            +
             
     | 
| 
      
 184 
     | 
    
         
            +
            describe "creating a new instance" do
         
     | 
| 
      
 185 
     | 
    
         
            +
              it "allows to create a new instance with an attribute hash" do
         
     | 
| 
      
 186 
     | 
    
         
            +
                p = Post.new :body => 'hi'
         
     | 
| 
      
 187 
     | 
    
         
            +
                p.body.must_equal 'hi'
         
     | 
| 
      
 188 
     | 
    
         
            +
              end
         
     | 
| 
      
 189 
     | 
    
         
            +
            end
         
     | 
| 
      
 190 
     | 
    
         
            +
             
     | 
| 
      
 191 
     | 
    
         
            +
            describe "being wrapped by a domain layer" do
         
     | 
| 
      
 192 
     | 
    
         
            +
              before do
         
     | 
| 
      
 193 
     | 
    
         
            +
                fake_http '/users/1/posts.xml', fixture('user_posts')
         
     | 
| 
      
 194 
     | 
    
         
            +
                @posts = PostDomainRepository.new.find_by_user_id(1)
         
     | 
| 
      
 195 
     | 
    
         
            +
                @post = @posts.first
         
     | 
| 
      
 196 
     | 
    
         
            +
              end
         
     | 
| 
      
 197 
     | 
    
         
            +
             
     | 
| 
      
 198 
     | 
    
         
            +
              it "handles a collection" do
         
     | 
| 
      
 199 
     | 
    
         
            +
                @post.published?.must_equal true
         
     | 
| 
      
 200 
     | 
    
         
            +
                @post.user.first_name.must_equal 'John'
         
     | 
| 
      
 201 
     | 
    
         
            +
              end
         
     | 
| 
      
 202 
     | 
    
         
            +
             
     | 
| 
      
 203 
     | 
    
         
            +
              it "allows filtering by letting the instantiator return nil" do
         
     | 
| 
      
 204 
     | 
    
         
            +
                @posts.size.must_equal 1
         
     | 
| 
      
 205 
     | 
    
         
            +
              end
         
     | 
| 
      
 206 
     | 
    
         
            +
            end
         
     | 
| 
      
 207 
     | 
    
         
            +
             
     | 
| 
      
 208 
     | 
    
         
            +
            describe "passing a block instead of a class to collection" do
         
     | 
| 
      
 209 
     | 
    
         
            +
              it "returns the parsed document" do
         
     | 
| 
      
 210 
     | 
    
         
            +
                fake_http '/users/1/posts.xml', fixture('user_posts')
         
     | 
| 
      
 211 
     | 
    
         
            +
                repository = BlogPostRepository.new
         
     | 
| 
      
 212 
     | 
    
         
            +
                repository.all.first.id.must_equal 1
         
     | 
| 
      
 213 
     | 
    
         
            +
              end
         
     | 
| 
      
 214 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,42 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            <?xml version="1.0"?>
         
     | 
| 
      
 2 
     | 
    
         
            +
            <userPosts type="array">
         
     | 
| 
      
 3 
     | 
    
         
            +
              <userPost>
         
     | 
| 
      
 4 
     | 
    
         
            +
            	<hidden>false</hidden>
         
     | 
| 
      
 5 
     | 
    
         
            +
                <objectId>1</objectId>
         
     | 
| 
      
 6 
     | 
    
         
            +
                <userObjectId>1</userObjectId>
         
     | 
| 
      
 7 
     | 
    
         
            +
                <user>
         
     | 
| 
      
 8 
     | 
    
         
            +
                  <firstName>John</firstName>
         
     | 
| 
      
 9 
     | 
    
         
            +
                  <lastName>Doe</lastName>
         
     | 
| 
      
 10 
     | 
    
         
            +
                </user>
         
     | 
| 
      
 11 
     | 
    
         
            +
                <body>How is everybody today?</body>
         
     | 
| 
      
 12 
     | 
    
         
            +
                <createdAt>2011-10-26T17:01:52+02:00</createdAt>
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                <replies type="array">
         
     | 
| 
      
 15 
     | 
    
         
            +
                  <userPost>
         
     | 
| 
      
 16 
     | 
    
         
            +
                    <objectId>2</objectId>
         
     | 
| 
      
 17 
     | 
    
         
            +
                    <parentObjectId>1</parentObjectId>
         
     | 
| 
      
 18 
     | 
    
         
            +
                    <userObjectId>2</userObjectId>
         
     | 
| 
      
 19 
     | 
    
         
            +
                    <user>
         
     | 
| 
      
 20 
     | 
    
         
            +
                      <firstName>Mike</firstName>
         
     | 
| 
      
 21 
     | 
    
         
            +
                      <lastName>Smith</lastName>
         
     | 
| 
      
 22 
     | 
    
         
            +
                    </user>
         
     | 
| 
      
 23 
     | 
    
         
            +
                    <body>I am fine, thanks for asking.</body>
         
     | 
| 
      
 24 
     | 
    
         
            +
                    <createdAt>2011-10-27T9:00:00+02:00</createdAt>
         
     | 
| 
      
 25 
     | 
    
         
            +
                  </userPost>
         
     | 
| 
      
 26 
     | 
    
         
            +
                  <userPost>
         
     | 
| 
      
 27 
     | 
    
         
            +
                    <objectId>3</objectId>
         
     | 
| 
      
 28 
     | 
    
         
            +
                    <parentObjectId>1</parentObjectId>
         
     | 
| 
      
 29 
     | 
    
         
            +
                    <userObjectId>1</userObjectId>
         
     | 
| 
      
 30 
     | 
    
         
            +
                    <user>
         
     | 
| 
      
 31 
     | 
    
         
            +
                      <firstName>John</firstName>
         
     | 
| 
      
 32 
     | 
    
         
            +
                      <lastName>Doe</lastName>
         
     | 
| 
      
 33 
     | 
    
         
            +
                    </user>
         
     | 
| 
      
 34 
     | 
    
         
            +
                    <body>You are more than welcome.</body>
         
     | 
| 
      
 35 
     | 
    
         
            +
                    <createdAt>2011-10-27T9:00:00+02:00</createdAt>
         
     | 
| 
      
 36 
     | 
    
         
            +
                  </userPost>
         
     | 
| 
      
 37 
     | 
    
         
            +
                </replies>
         
     | 
| 
      
 38 
     | 
    
         
            +
              </userPost>
         
     | 
| 
      
 39 
     | 
    
         
            +
              <userPost>
         
     | 
| 
      
 40 
     | 
    
         
            +
                <hidden>true</hidden>
         
     | 
| 
      
 41 
     | 
    
         
            +
              </userPost>
         
     | 
| 
      
 42 
     | 
    
         
            +
            </userPosts>
         
     | 
    
        data/test/test_helper.rb
    ADDED
    
    | 
         @@ -0,0 +1,11 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            def fake_http(path, body)
         
     | 
| 
      
 2 
     | 
    
         
            +
              FakeWeb.register_uri :get, uri(path), :body => body
         
     | 
| 
      
 3 
     | 
    
         
            +
            end
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            def uri(path)
         
     | 
| 
      
 6 
     | 
    
         
            +
              'http://user:pass@test.net' + path
         
     | 
| 
      
 7 
     | 
    
         
            +
            end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            def fixture(name)
         
     | 
| 
      
 10 
     | 
    
         
            +
              File.open File.join(File.dirname('__FILE__'), 'test', 'fixtures', "#{name}.xml")
         
     | 
| 
      
 11 
     | 
    
         
            +
            end
         
     | 
    
        metadata
    ADDED
    
    | 
         @@ -0,0 +1,128 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            --- !ruby/object:Gem::Specification
         
     | 
| 
      
 2 
     | 
    
         
            +
            name: ahora
         
     | 
| 
      
 3 
     | 
    
         
            +
            version: !ruby/object:Gem::Version
         
     | 
| 
      
 4 
     | 
    
         
            +
              version: 0.0.1
         
     | 
| 
      
 5 
     | 
    
         
            +
              prerelease: 
         
     | 
| 
      
 6 
     | 
    
         
            +
            platform: ruby
         
     | 
| 
      
 7 
     | 
    
         
            +
            authors:
         
     | 
| 
      
 8 
     | 
    
         
            +
            - Matthijs Langenberg
         
     | 
| 
      
 9 
     | 
    
         
            +
            autorequire: 
         
     | 
| 
      
 10 
     | 
    
         
            +
            bindir: bin
         
     | 
| 
      
 11 
     | 
    
         
            +
            cert_chain: []
         
     | 
| 
      
 12 
     | 
    
         
            +
            date: 2012-04-12 00:00:00.000000000 Z
         
     | 
| 
      
 13 
     | 
    
         
            +
            dependencies:
         
     | 
| 
      
 14 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 15 
     | 
    
         
            +
              name: nibbler
         
     | 
| 
      
 16 
     | 
    
         
            +
              requirement: &70160681904060 !ruby/object:Gem::Requirement
         
     | 
| 
      
 17 
     | 
    
         
            +
                none: false
         
     | 
| 
      
 18 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 19 
     | 
    
         
            +
                - - ! '>='
         
     | 
| 
      
 20 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 21 
     | 
    
         
            +
                    version: 1.3.0
         
     | 
| 
      
 22 
     | 
    
         
            +
              type: :runtime
         
     | 
| 
      
 23 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 24 
     | 
    
         
            +
              version_requirements: *70160681904060
         
     | 
| 
      
 25 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 26 
     | 
    
         
            +
              name: faraday
         
     | 
| 
      
 27 
     | 
    
         
            +
              requirement: &70160681901920 !ruby/object:Gem::Requirement
         
     | 
| 
      
 28 
     | 
    
         
            +
                none: false
         
     | 
| 
      
 29 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 30 
     | 
    
         
            +
                - - ! '>='
         
     | 
| 
      
 31 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 32 
     | 
    
         
            +
                    version: '0.7'
         
     | 
| 
      
 33 
     | 
    
         
            +
              type: :runtime
         
     | 
| 
      
 34 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 35 
     | 
    
         
            +
              version_requirements: *70160681901920
         
     | 
| 
      
 36 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 37 
     | 
    
         
            +
              name: nokogiri
         
     | 
| 
      
 38 
     | 
    
         
            +
              requirement: &70160681900040 !ruby/object:Gem::Requirement
         
     | 
| 
      
 39 
     | 
    
         
            +
                none: false
         
     | 
| 
      
 40 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 41 
     | 
    
         
            +
                - - ~>
         
     | 
| 
      
 42 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 43 
     | 
    
         
            +
                    version: '1.5'
         
     | 
| 
      
 44 
     | 
    
         
            +
              type: :runtime
         
     | 
| 
      
 45 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 46 
     | 
    
         
            +
              version_requirements: *70160681900040
         
     | 
| 
      
 47 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 48 
     | 
    
         
            +
              name: activesupport
         
     | 
| 
      
 49 
     | 
    
         
            +
              requirement: &70160681898560 !ruby/object:Gem::Requirement
         
     | 
| 
      
 50 
     | 
    
         
            +
                none: false
         
     | 
| 
      
 51 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 52 
     | 
    
         
            +
                - - ! '>='
         
     | 
| 
      
 53 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 54 
     | 
    
         
            +
                    version: '0'
         
     | 
| 
      
 55 
     | 
    
         
            +
              type: :runtime
         
     | 
| 
      
 56 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 57 
     | 
    
         
            +
              version_requirements: *70160681898560
         
     | 
| 
      
 58 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 59 
     | 
    
         
            +
              name: fakeweb
         
     | 
| 
      
 60 
     | 
    
         
            +
              requirement: &70160681897280 !ruby/object:Gem::Requirement
         
     | 
| 
      
 61 
     | 
    
         
            +
                none: false
         
     | 
| 
      
 62 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 63 
     | 
    
         
            +
                - - ! '>='
         
     | 
| 
      
 64 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 65 
     | 
    
         
            +
                    version: '0'
         
     | 
| 
      
 66 
     | 
    
         
            +
              type: :development
         
     | 
| 
      
 67 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 68 
     | 
    
         
            +
              version_requirements: *70160681897280
         
     | 
| 
      
 69 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 70 
     | 
    
         
            +
              name: minitest
         
     | 
| 
      
 71 
     | 
    
         
            +
              requirement: &70160681896360 !ruby/object:Gem::Requirement
         
     | 
| 
      
 72 
     | 
    
         
            +
                none: false
         
     | 
| 
      
 73 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 74 
     | 
    
         
            +
                - - ! '>='
         
     | 
| 
      
 75 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 76 
     | 
    
         
            +
                    version: '0'
         
     | 
| 
      
 77 
     | 
    
         
            +
              type: :development
         
     | 
| 
      
 78 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 79 
     | 
    
         
            +
              version_requirements: *70160681896360
         
     | 
| 
      
 80 
     | 
    
         
            +
            description: Consume Java-ish XML HTTP Resources easily
         
     | 
| 
      
 81 
     | 
    
         
            +
            email:
         
     | 
| 
      
 82 
     | 
    
         
            +
            - matthijs.langenberg@nedap.com
         
     | 
| 
      
 83 
     | 
    
         
            +
            executables: []
         
     | 
| 
      
 84 
     | 
    
         
            +
            extensions: []
         
     | 
| 
      
 85 
     | 
    
         
            +
            extra_rdoc_files: []
         
     | 
| 
      
 86 
     | 
    
         
            +
            files:
         
     | 
| 
      
 87 
     | 
    
         
            +
            - .gitignore
         
     | 
| 
      
 88 
     | 
    
         
            +
            - Gemfile
         
     | 
| 
      
 89 
     | 
    
         
            +
            - README.md
         
     | 
| 
      
 90 
     | 
    
         
            +
            - Rakefile
         
     | 
| 
      
 91 
     | 
    
         
            +
            - ahora.gemspec
         
     | 
| 
      
 92 
     | 
    
         
            +
            - lib/ahora.rb
         
     | 
| 
      
 93 
     | 
    
         
            +
            - lib/ahora/middleware.rb
         
     | 
| 
      
 94 
     | 
    
         
            +
            - lib/ahora/middleware/last_modified_caching.rb
         
     | 
| 
      
 95 
     | 
    
         
            +
            - lib/ahora/middleware/request_logger.rb
         
     | 
| 
      
 96 
     | 
    
         
            +
            - lib/ahora/representation.rb
         
     | 
| 
      
 97 
     | 
    
         
            +
            - lib/ahora/resource.rb
         
     | 
| 
      
 98 
     | 
    
         
            +
            - lib/ahora/version.rb
         
     | 
| 
      
 99 
     | 
    
         
            +
            - test/ahora/representation_test.rb
         
     | 
| 
      
 100 
     | 
    
         
            +
            - test/ahora_test.rb
         
     | 
| 
      
 101 
     | 
    
         
            +
            - test/fixtures/employee.xml
         
     | 
| 
      
 102 
     | 
    
         
            +
            - test/fixtures/user_posts.xml
         
     | 
| 
      
 103 
     | 
    
         
            +
            - test/test_helper.rb
         
     | 
| 
      
 104 
     | 
    
         
            +
            homepage: ''
         
     | 
| 
      
 105 
     | 
    
         
            +
            licenses: []
         
     | 
| 
      
 106 
     | 
    
         
            +
            post_install_message: 
         
     | 
| 
      
 107 
     | 
    
         
            +
            rdoc_options: []
         
     | 
| 
      
 108 
     | 
    
         
            +
            require_paths:
         
     | 
| 
      
 109 
     | 
    
         
            +
            - lib
         
     | 
| 
      
 110 
     | 
    
         
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         
     | 
| 
      
 111 
     | 
    
         
            +
              none: false
         
     | 
| 
      
 112 
     | 
    
         
            +
              requirements:
         
     | 
| 
      
 113 
     | 
    
         
            +
              - - ! '>='
         
     | 
| 
      
 114 
     | 
    
         
            +
                - !ruby/object:Gem::Version
         
     | 
| 
      
 115 
     | 
    
         
            +
                  version: '0'
         
     | 
| 
      
 116 
     | 
    
         
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         
     | 
| 
      
 117 
     | 
    
         
            +
              none: false
         
     | 
| 
      
 118 
     | 
    
         
            +
              requirements:
         
     | 
| 
      
 119 
     | 
    
         
            +
              - - ! '>='
         
     | 
| 
      
 120 
     | 
    
         
            +
                - !ruby/object:Gem::Version
         
     | 
| 
      
 121 
     | 
    
         
            +
                  version: '0'
         
     | 
| 
      
 122 
     | 
    
         
            +
            requirements: []
         
     | 
| 
      
 123 
     | 
    
         
            +
            rubyforge_project: 
         
     | 
| 
      
 124 
     | 
    
         
            +
            rubygems_version: 1.8.15
         
     | 
| 
      
 125 
     | 
    
         
            +
            signing_key: 
         
     | 
| 
      
 126 
     | 
    
         
            +
            specification_version: 3
         
     | 
| 
      
 127 
     | 
    
         
            +
            summary: Consume Java-ish XML HTTP Resources easily
         
     | 
| 
      
 128 
     | 
    
         
            +
            test_files: []
         
     |