yaks 0.10.0 → 0.11.0
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.
- checksums.yaml +4 -4
- data/README.md +88 -16
- data/Rakefile +1 -1
- data/ataru_setup.rb +6 -0
- data/lib/yaks/behaviour/optional_includes.rb +29 -0
- data/lib/yaks/format/json_api.rb +13 -13
- data/lib/yaks/mapper/link.rb +10 -5
- data/lib/yaks/reader/json_api.rb +25 -21
- data/lib/yaks/resource/link.rb +1 -1
- data/lib/yaks/version.rb +1 -1
- data/spec/json/confucius.json_api.json +20 -8
- data/spec/spec_helper.rb +11 -21
- data/spec/support/fixtures.rb +1 -1
- data/spec/unit/yaks/behaviour/optional_includes_spec.rb +63 -0
- data/spec/unit/yaks/default_policy/derive_mapper_from_collection_spec.rb +3 -3
- data/spec/unit/yaks/format/json_api_spec.rb +38 -12
- data/yaks.gemspec +4 -2
- metadata +6 -3
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 17023a2415d74fcf0ea0c840dfa7f1d2fcb8afb4
         | 
| 4 | 
            +
              data.tar.gz: 3fc6a562a7ba74d7064d2898f04ed32d1ba1b3b4
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 159c85fc53b2b7aa8e6fbc9a23fa3c1fa969f667e36e96c58e97ac4bc76b52bf7aaa1e95e309d319c121eeaafd30dc71a7de7fef3cca0173aae844593f3a5238
         | 
| 7 | 
            +
              data.tar.gz: c0ec3d24906a2b12accd6548e574679e239def3d9e71228e1c35812c302c8c92afc118d3af47927b82e5c329bfeee0190df7b8b8f54300c1f3bc7eb1ca6a21db
         | 
    
        data/README.md
    CHANGED
    
    | @@ -43,6 +43,7 @@ requested. These formats are presently supported: | |
| 43 43 | 
             
                - [Filtering](#user-content-filtering)
         | 
| 44 44 | 
             
              - [Links](#user-content-links)
         | 
| 45 45 | 
             
              - [Associations](#user-content-associations)
         | 
| 46 | 
            +
              - [Behaviours](#user-content-behaviours)
         | 
| 46 47 | 
             
            - [Calling Yaks](#user-content-calling-yaks)
         | 
| 47 48 | 
             
              - [Rack env](#user-content-rack-env)
         | 
| 48 49 | 
             
            - [Namespace](#user-content-namespace)
         | 
| @@ -68,6 +69,12 @@ requested. These formats are presently supported: | |
| 68 69 | 
             
            - [How to contribute](#user-content-how-to-contribute)
         | 
| 69 70 | 
             
            - [License](#user-content-license)
         | 
| 70 71 |  | 
| 72 | 
            +
            ## Packages
         | 
| 73 | 
            +
             | 
| 74 | 
            +
            - [yaks-sinatra](yaks-sinatra/README.md)
         | 
| 75 | 
            +
            - [yaks-html](yaks-html/README.md)
         | 
| 76 | 
            +
            - [yaks-transit](yaks-transit/README.md)
         | 
| 77 | 
            +
             | 
| 71 78 | 
             
            ## State of Development
         | 
| 72 79 |  | 
| 73 80 | 
             
            Recent focus has been on stabilizing the core classes, improving
         | 
| @@ -309,6 +316,59 @@ class ShowMapper < Yaks::Mapper | |
| 309 316 | 
             
            end
         | 
| 310 317 | 
             
            ```
         | 
| 311 318 |  | 
| 319 | 
            +
            ### Behaviours
         | 
| 320 | 
            +
             | 
| 321 | 
            +
            Yaks provides mixins to change how your mappers work. These need to be
         | 
| 322 | 
            +
            required separately, they are not loaded by default.
         | 
| 323 | 
            +
             | 
| 324 | 
            +
            #### OptionalIncludes
         | 
| 325 | 
            +
             | 
| 326 | 
            +
            You may choose to not render associations by default, but to only do
         | 
| 327 | 
            +
            so when the client explicitly asks for them. This can be done by
         | 
| 328 | 
            +
            including `Yaks::Behaviour::OptionalIncludes`.
         | 
| 329 | 
            +
             | 
| 330 | 
            +
            Which associations to load is specified with the the `include` query
         | 
| 331 | 
            +
            parameter. You can use dots to load nested associated.
         | 
| 332 | 
            +
             | 
| 333 | 
            +
            ```ruby
         | 
| 334 | 
            +
            require "yaks/behaviour/optional_includes"
         | 
| 335 | 
            +
             | 
| 336 | 
            +
            class PostMapper < Yaks::Mapper
         | 
| 337 | 
            +
              include Yaks::Behaviour::OptionalIncludes
         | 
| 338 | 
            +
             | 
| 339 | 
            +
              has_one :author
         | 
| 340 | 
            +
              has_many :comments
         | 
| 341 | 
            +
            end
         | 
| 342 | 
            +
             | 
| 343 | 
            +
            class AuthorMapper < Yaks::Mapper
         | 
| 344 | 
            +
              include Yaks::Behaviour::OptionalIncludes
         | 
| 345 | 
            +
             | 
| 346 | 
            +
              has_one :profile
         | 
| 347 | 
            +
            end
         | 
| 348 | 
            +
            ```
         | 
| 349 | 
            +
             | 
| 350 | 
            +
            ```
         | 
| 351 | 
            +
            GET /post/42?include=comments,author.profile
         | 
| 352 | 
            +
            ```
         | 
| 353 | 
            +
             | 
| 354 | 
            +
            Note that this will only work when Yaks has access to the Rack
         | 
| 355 | 
            +
            environment. When using an existing integration like `yaks-sinatra`
         | 
| 356 | 
            +
            this will be handled for you.
         | 
| 357 | 
            +
             | 
| 358 | 
            +
            To force an association to always be included, override its `if`
         | 
| 359 | 
            +
            condition to always return true.
         | 
| 360 | 
            +
             | 
| 361 | 
            +
            ```ruby
         | 
| 362 | 
            +
            require "yaks/behaviour/optional_includes"
         | 
| 363 | 
            +
             | 
| 364 | 
            +
            class PostMapper < Yaks::Mapper
         | 
| 365 | 
            +
              include Yaks::Behaviour::OptionalIncludes
         | 
| 366 | 
            +
             | 
| 367 | 
            +
              has_one :author
         | 
| 368 | 
            +
              has_many :comments, if: ->{ true }
         | 
| 369 | 
            +
            end
         | 
| 370 | 
            +
            ```
         | 
| 371 | 
            +
             | 
| 312 372 | 
             
            ## Calling Yaks
         | 
| 313 373 |  | 
| 314 374 | 
             
            Once you have a Yaks instance, you can call it with `call`
         | 
| @@ -513,19 +573,6 @@ at your API entry point should do the trick. | |
| 513 573 |  | 
| 514 574 | 
             
            ### JSON-API
         | 
| 515 575 |  | 
| 516 | 
            -
            The JSON-API spec has evolved since the Yaks formatter was
         | 
| 517 | 
            -
            implemented. It is also not the most suitable format for Yaks
         | 
| 518 | 
            -
            feature-set due to its strong convention-driven nature and weak
         | 
| 519 | 
            -
            support for hypermedia.
         | 
| 520 | 
            -
             | 
| 521 | 
            -
            At this time, The JSON-API specification has not reached a 1.0 release.
         | 
| 522 | 
            -
            Some changes to the Yaks JSON-API formatter may still be required
         | 
| 523 | 
            -
            before it is completely compatible with the latest version of the
         | 
| 524 | 
            -
            specification.
         | 
| 525 | 
            -
             | 
| 526 | 
            -
            If you would like to see better JSON-API support, get in touch. We
         | 
| 527 | 
            -
            might be able to work something out.
         | 
| 528 | 
            -
             | 
| 529 576 | 
             
            ```ruby
         | 
| 530 577 | 
             
            Yaks.new do
         | 
| 531 578 | 
             
              default_format :json_api
         | 
| @@ -552,6 +599,8 @@ yaks = Yaks.new do | |
| 552 599 | 
             
            end
         | 
| 553 600 | 
             
            ```
         | 
| 554 601 |  | 
| 602 | 
            +
            For optional includes, see [`Yaks::Behaviour::OptionalIncludes`](#user-content-behaviours).
         | 
| 603 | 
            +
             | 
| 555 604 | 
             
            ### Collection+JSON
         | 
| 556 605 |  | 
| 557 606 | 
             
            Collection+JSON has support for write templates. To use them, the `:template`
         | 
| @@ -802,15 +851,38 @@ end | |
| 802 851 |  | 
| 803 852 | 
             
            For JSON based formats, the "syntax tree" is merely a structure of Ruby primitives that have a JSON equivalent. If your mappers return non-primitive attribute values, you can define how they should be converted. For example, JSON has no notion of dates. If your mappers return these types as attributes, then Yaks needs to know how to turn these into primitives. To add extra types, use `map_to_primitive`
         | 
| 804 853 |  | 
| 854 | 
            +
            Here's an example with a custom `Currency` class, which can be represented as an integer.
         | 
| 855 | 
            +
             | 
| 805 856 | 
             
            ```ruby
         | 
| 806 857 | 
             
            Yaks.new do
         | 
| 807 | 
            -
              map_to_primitive  | 
| 808 | 
            -
                 | 
| 858 | 
            +
              map_to_primitive Currency do |currency|
         | 
| 859 | 
            +
                currency.to_i
         | 
| 809 860 | 
             
              end
         | 
| 810 861 | 
             
            end
         | 
| 811 862 | 
             
            ```
         | 
| 812 863 |  | 
| 813 | 
            -
             | 
| 864 | 
            +
            One notable use case is representing dates and times. The JSON
         | 
| 865 | 
            +
            specification does not define any syntax for these, so the only
         | 
| 866 | 
            +
            solution is to represent them either as numbers or strings. If you're
         | 
| 867 | 
            +
            not sure what to do with these then the ISO8601 standard is a safe
         | 
| 868 | 
            +
            bet. It defines a way to represent times and dates as strings, and is
         | 
| 869 | 
            +
            also adopted by the W3C in [RFC3339](http://tools.ietf.org/html/rfc3339).
         | 
| 870 | 
            +
             | 
| 871 | 
            +
            An alternative representation that is sometimes used is "unix time",
         | 
| 872 | 
            +
            defined as the numbers of seconds passed since 1 January 1970.
         | 
| 873 | 
            +
             | 
| 874 | 
            +
            Here's an example for a Rails app, so including ActiveSupport's `TimeWithZone`.
         | 
| 875 | 
            +
             | 
| 876 | 
            +
            ```ruby
         | 
| 877 | 
            +
            Yaks.new do
         | 
| 878 | 
            +
              map_to_primitive Date, Time, DateTime, ActiveSupport::TimeWithZone, &:iso8601
         | 
| 879 | 
            +
            end
         | 
| 880 | 
            +
            ```
         | 
| 881 | 
            +
             | 
| 882 | 
            +
            `map_to_primitive` can also be used to transform alternative data
         | 
| 883 | 
            +
            structures, like those from [Hamster](https://github.com/hamstergem/hamster),
         | 
| 884 | 
            +
            into Ruby arrays and hashes. Use `call()` to recursively turn things into
         | 
| 885 | 
            +
            primitives.
         | 
| 814 886 |  | 
| 815 887 | 
             
            ```ruby
         | 
| 816 888 | 
             
            Yaks.new do
         | 
    
        data/Rakefile
    CHANGED
    
    
    
        data/ataru_setup.rb
    CHANGED
    
    | @@ -33,6 +33,12 @@ class HomeMapper < Yaks::Mapper; end | |
| 33 33 |  | 
| 34 34 | 
             
            class SpecialMapper < Yaks::Mapper; end
         | 
| 35 35 |  | 
| 36 | 
            +
            module ActiveSupport
         | 
| 37 | 
            +
              class TimeWithZone < Time ; end
         | 
| 38 | 
            +
            end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
            class Currency ; end
         | 
| 41 | 
            +
             | 
| 36 42 | 
             
            module Setup
         | 
| 37 43 | 
             
              def setup
         | 
| 38 44 | 
             
                # Do some nice setup that is run before every snippet
         | 
| @@ -0,0 +1,29 @@ | |
| 1 | 
            +
            require "rack/utils"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Yaks
         | 
| 4 | 
            +
              module Behaviour
         | 
| 5 | 
            +
                module OptionalIncludes
         | 
| 6 | 
            +
                  RACK_KEY = "yaks.optional_includes".freeze
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def associations
         | 
| 9 | 
            +
                    super.select do |association|
         | 
| 10 | 
            +
                      association.if != Undefined || include_association?(association)
         | 
| 11 | 
            +
                    end
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  private
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  def include_association?(association)
         | 
| 17 | 
            +
                    includes = env.fetch(RACK_KEY) do
         | 
| 18 | 
            +
                      query_string = env.fetch("QUERY_STRING", nil)
         | 
| 19 | 
            +
                      query = Rack::Utils.parse_query(query_string)
         | 
| 20 | 
            +
                      env[RACK_KEY] = query.fetch("include", "").split(",").map { |r| r.split(".") }
         | 
| 21 | 
            +
                    end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                    includes.any? do |relationship|
         | 
| 24 | 
            +
                      relationship[mapper_stack.size].eql?(association.name.to_s)
         | 
| 25 | 
            +
                    end
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
            end
         | 
    
        data/lib/yaks/format/json_api.rb
    CHANGED
    
    | @@ -42,31 +42,31 @@ module Yaks | |
| 42 42 | 
             
                    attributes = resource.attributes.reject { |k| k.equal?(:id) }
         | 
| 43 43 | 
             
                    result[:attributes] = attributes if attributes.any?
         | 
| 44 44 |  | 
| 45 | 
            -
                     | 
| 46 | 
            -
                    result[: | 
| 47 | 
            -
                     | 
| 48 | 
            -
                    result | 
| 45 | 
            +
                    relationships = serialize_relationships(resource.subresources)
         | 
| 46 | 
            +
                    result[:relationships] = relationships unless relationships.empty?
         | 
| 47 | 
            +
                    links = serialize_links(resource.links)
         | 
| 48 | 
            +
                    result[:links] = links unless links.empty?
         | 
| 49 49 |  | 
| 50 50 | 
             
                    result
         | 
| 51 51 | 
             
                  end
         | 
| 52 52 |  | 
| 53 | 
            -
                  # @param [ | 
| 53 | 
            +
                  # @param [Array] subresources
         | 
| 54 54 | 
             
                  # @return [Hash]
         | 
| 55 | 
            -
                  def  | 
| 55 | 
            +
                  def serialize_relationships(subresources)
         | 
| 56 56 | 
             
                    subresources.each_with_object({}) do |resource, hsh|
         | 
| 57 | 
            -
                       | 
| 58 | 
            -
                      hsh[resource.rels.first.sub(/^rel:/, '')] = serialize_subresource_link(resource)
         | 
| 57 | 
            +
                      hsh[resource.rels.first.sub(/^rel:/, '').to_sym] = serialize_relationship(resource)
         | 
| 59 58 | 
             
                    end
         | 
| 60 59 | 
             
                  end
         | 
| 61 60 |  | 
| 62 61 | 
             
                  # @param [Yaks::Resource] resource
         | 
| 63 | 
            -
                  # @return [Array,  | 
| 64 | 
            -
                  def  | 
| 62 | 
            +
                  # @return [Array, Hash]
         | 
| 63 | 
            +
                  def serialize_relationship(resource)
         | 
| 65 64 | 
             
                    if resource.collection?
         | 
| 66 | 
            -
                       | 
| 67 | 
            -
                     | 
| 68 | 
            -
                       | 
| 65 | 
            +
                      data = resource.map { |r| {type: pluralize(r.type), id: r[:id].to_s} }
         | 
| 66 | 
            +
                    elsif !resource.null_resource?
         | 
| 67 | 
            +
                      data = {type: pluralize(resource.type), id: resource[:id].to_s}
         | 
| 69 68 | 
             
                    end
         | 
| 69 | 
            +
                    {data: data}
         | 
| 70 70 | 
             
                  end
         | 
| 71 71 |  | 
| 72 72 | 
             
                  # @param [Hash] subresources
         | 
    
        data/lib/yaks/mapper/link.rb
    CHANGED
    
    | @@ -25,7 +25,7 @@ module Yaks | |
| 25 25 | 
             
                #   it will receive the mapper instance as argument. Otherwise it is evaluated in the mapper context
         | 
| 26 26 | 
             
                class Link
         | 
| 27 27 | 
             
                  extend Forwardable, Util
         | 
| 28 | 
            -
                  include Attribs.new(:rel, :template, options: {}), Util
         | 
| 28 | 
            +
                  include Attribs.new(:rel, :template, options: {}.freeze), Util
         | 
| 29 29 |  | 
| 30 30 | 
             
                  def self.create(*args)
         | 
| 31 31 | 
             
                    args, options = extract_options(args)
         | 
| @@ -62,11 +62,16 @@ module Yaks | |
| 62 62 | 
             
                    uri = mapper.expand_uri(template, options.fetch(:expand, true))
         | 
| 63 63 | 
             
                    return if uri.nil?
         | 
| 64 64 |  | 
| 65 | 
            -
                     | 
| 65 | 
            +
                    attrs = {
         | 
| 66 66 | 
             
                      rel: rel,
         | 
| 67 | 
            -
                      uri: uri | 
| 68 | 
            -
             | 
| 69 | 
            -
             | 
| 67 | 
            +
                      uri: uri
         | 
| 68 | 
            +
                    }
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                    resource_link_options(mapper).tap do |opts|
         | 
| 71 | 
            +
                      attrs[:options] = opts unless opts.empty?
         | 
| 72 | 
            +
                    end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                    Resource::Link.new(attrs)
         | 
| 70 75 | 
             
                  end
         | 
| 71 76 |  | 
| 72 77 | 
             
                  private
         | 
    
        data/lib/yaks/reader/json_api.rb
    CHANGED
    
    | @@ -12,12 +12,12 @@ module Yaks | |
| 12 12 | 
             
                    else
         | 
| 13 13 | 
             
                      attributes = parsed_json['data'].dup
         | 
| 14 14 | 
             
                      links = attributes.delete('links') || {}
         | 
| 15 | 
            +
                      relationships = attributes.delete('relationships') || {}
         | 
| 15 16 | 
             
                      type  = attributes.delete('type')
         | 
| 16 17 | 
             
                      attributes.merge!(attributes.delete('attributes') || {})
         | 
| 17 18 |  | 
| 18 | 
            -
                       | 
| 19 | 
            -
                       | 
| 20 | 
            -
                      links      = convert_links(Hash[resource_links])
         | 
| 19 | 
            +
                      embedded   = convert_embedded(Hash[relationships], included)
         | 
| 20 | 
            +
                      links      = convert_links(Hash[links])
         | 
| 21 21 |  | 
| 22 22 | 
             
                      Resource.new(
         | 
| 23 23 | 
             
                        type: Util.singularize(type),
         | 
| @@ -28,29 +28,33 @@ module Yaks | |
| 28 28 | 
             
                    end
         | 
| 29 29 | 
             
                  end
         | 
| 30 30 |  | 
| 31 | 
            -
                  def convert_embedded( | 
| 32 | 
            -
                     | 
| 33 | 
            -
                      # A Link doesn't have to contain a ` | 
| 31 | 
            +
                  def convert_embedded(relationships, included)
         | 
| 32 | 
            +
                    relationships.flat_map do |rel, relationship|
         | 
| 33 | 
            +
                      # A Link doesn't have to contain a `data` member.
         | 
| 34 34 | 
             
                      # It can contain URLs instead, or as well, but we are only worried about *embedded* links here.
         | 
| 35 | 
            -
                       | 
| 36 | 
            -
                      # Resource  | 
| 35 | 
            +
                      data = relationship['data']
         | 
| 36 | 
            +
                      # Resource data MUST be represented as one of the following:
         | 
| 37 37 | 
             
                      #
         | 
| 38 38 | 
             
                      # * `null` for empty to-one relationships.
         | 
| 39 | 
            -
                      # * a " | 
| 39 | 
            +
                      # * a "resource identifier object" for non-empty to-one relationships.
         | 
| 40 40 | 
             
                      # * an empty array ([]) for empty to-many relationships.
         | 
| 41 | 
            -
                      # * an array of  | 
| 42 | 
            -
                      if  | 
| 43 | 
            -
                         | 
| 44 | 
            -
                      elsif  | 
| 45 | 
            -
                         | 
| 46 | 
            -
                           | 
| 47 | 
            -
             | 
| 48 | 
            -
             | 
| 49 | 
            -
             | 
| 50 | 
            -
             | 
| 51 | 
            -
             | 
| 41 | 
            +
                      # * an array of resource identifier objects for non-empty to-many relationships.
         | 
| 42 | 
            +
                      if data.nil?
         | 
| 43 | 
            +
                        NullResource.new(rels: [rel])
         | 
| 44 | 
            +
                      elsif data.is_a? Array
         | 
| 45 | 
            +
                        if data.empty?
         | 
| 46 | 
            +
                          NullResource.new(collection: true, rels: [rel])
         | 
| 47 | 
            +
                        else
         | 
| 48 | 
            +
                          CollectionResource.new(
         | 
| 49 | 
            +
                            members: data.map { |link|
         | 
| 50 | 
            +
                              data = included.find{ |item| (item['id'] == link['id']) && (item['type'] == link['type']) }
         | 
| 51 | 
            +
                              call('data'  => data, 'included' => included)
         | 
| 52 | 
            +
                            },
         | 
| 53 | 
            +
                            rels: [rel]
         | 
| 54 | 
            +
                          )
         | 
| 55 | 
            +
                        end
         | 
| 52 56 | 
             
                      else
         | 
| 53 | 
            -
                        data = included.find{ |item| (item['id'] ==  | 
| 57 | 
            +
                        data = included.find{ |item| (item['id'] == data['id']) && (item['type'] == data['type']) }
         | 
| 54 58 | 
             
                        call('data'  => data, 'included' => included).with(rels: [rel])
         | 
| 55 59 | 
             
                      end
         | 
| 56 60 | 
             
                    end.compact
         | 
    
        data/lib/yaks/resource/link.rb
    CHANGED
    
    
    
        data/lib/yaks/version.rb
    CHANGED
    
    
| @@ -7,12 +7,16 @@ | |
| 7 7 | 
             
                  "pinyin": "Kongzi",
         | 
| 8 8 | 
             
                  "latinized": "Confucius"
         | 
| 9 9 | 
             
                },
         | 
| 10 | 
            -
                " | 
| 11 | 
            -
                  " | 
| 10 | 
            +
                "relationships": {
         | 
| 11 | 
            +
                  "works": {
         | 
| 12 | 
            +
                    "data": [{"type": "works", "id": "11"}, {"type": "works", "id": "12"}]
         | 
| 13 | 
            +
                  }
         | 
| 14 | 
            +
                },
         | 
| 15 | 
            +
            		"links": {
         | 
| 16 | 
            +
              	  "profile": "http://literature.example.com/profiles/scholar",
         | 
| 12 17 | 
             
                  "self": "http://literature.example.com/authors/kongzi",
         | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
                }
         | 
| 18 | 
            +
            			"http://literature.example.com/rels/quotes": "http://literature.example.com/quotes/?author=kongzi&q={query}"
         | 
| 19 | 
            +
            	  }
         | 
| 16 20 | 
             
              },
         | 
| 17 21 | 
             
              "included": [
         | 
| 18 22 | 
             
                  {
         | 
| @@ -22,11 +26,15 @@ | |
| 22 26 | 
             
                      "chinese_name": "論語",
         | 
| 23 27 | 
             
                      "english_name": "Analects"
         | 
| 24 28 | 
             
                    },
         | 
| 29 | 
            +
                    "relationships": {
         | 
| 30 | 
            +
                      "quotes": {
         | 
| 31 | 
            +
                        "data": [{"type": "quotes", "id": "17"}, {"type": "quotes", "id": "18"}]
         | 
| 32 | 
            +
                      },
         | 
| 33 | 
            +
                      "era": {"data": {"type": "erae", "id": "99"}}
         | 
| 34 | 
            +
                    },
         | 
| 25 35 | 
             
                    "links": {
         | 
| 26 36 | 
             
                      "profile": "http://literature.example.com/profiles/work",
         | 
| 27 | 
            -
                      "self": "http://literature.example.com/work/11" | 
| 28 | 
            -
                      "quotes": {"linkage": [{"type": "quotes", "id": "17"}, {"type": "quotes", "id": "18"}]},
         | 
| 29 | 
            -
                      "era": {"linkage": {"type": "erae", "id": "99"}}
         | 
| 37 | 
            +
                      "self": "http://literature.example.com/work/11"
         | 
| 30 38 | 
             
                    }
         | 
| 31 39 | 
             
                  },
         | 
| 32 40 | 
             
                  {
         | 
| @@ -57,6 +65,10 @@ | |
| 57 65 | 
             
                      "chinese_name": "易經",
         | 
| 58 66 | 
             
                      "english_name": "Commentaries to the Yi-jing"
         | 
| 59 67 | 
             
                    },
         | 
| 68 | 
            +
                    "relationships": {
         | 
| 69 | 
            +
                      "quotes": { "data": [] },
         | 
| 70 | 
            +
                      "era": { "data": null }
         | 
| 71 | 
            +
                    },
         | 
| 60 72 | 
             
                    "links": {
         | 
| 61 73 | 
             
                      "profile": "http://literature.example.com/profiles/work",
         | 
| 62 74 | 
             
                      "self": "http://literature.example.com/work/12"
         | 
    
        data/spec/spec_helper.rb
    CHANGED
    
    | @@ -1,13 +1,20 @@ | |
| 1 | 
            -
            require 'rspec/its'
         | 
| 2 | 
            -
            require 'bogus/rspec'
         | 
| 3 | 
            -
            require 'timeout'
         | 
| 4 | 
            -
             | 
| 5 1 | 
             
            require 'yaks'
         | 
| 6 2 | 
             
            require 'yaks-html'
         | 
| 7 3 | 
             
            require 'virtus'
         | 
| 8 4 |  | 
| 5 | 
            +
            require_relative '../../shared/rspec_config'
         | 
| 6 | 
            +
             | 
| 9 7 | 
             
            require 'fixture_helpers'
         | 
| 10 8 |  | 
| 9 | 
            +
            RSpec.configure do |rspec|
         | 
| 10 | 
            +
              rspec.include FixtureHelpers
         | 
| 11 | 
            +
            end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            Bogus.configure do |bogus|
         | 
| 14 | 
            +
              bogus.search_modules << Yaks
         | 
| 15 | 
            +
              bogus.search_modules << Yaks::Mapper
         | 
| 16 | 
            +
            end
         | 
| 17 | 
            +
             | 
| 11 18 | 
             
            require_relative 'support/models'
         | 
| 12 19 | 
             
            require_relative 'support/pet_mapper'
         | 
| 13 20 | 
             
            require_relative 'support/pet_peeve_mapper'
         | 
| @@ -17,20 +24,3 @@ require_relative 'support/shared_contexts' | |
| 17 24 | 
             
            require_relative 'support/youtypeit_models_mappers'
         | 
| 18 25 | 
             
            require_relative 'support/deep_eql'
         | 
| 19 26 | 
             
            require_relative 'support/classes_for_policy_testing'
         | 
| 20 | 
            -
             | 
| 21 | 
            -
            RSpec.configure do |rspec|
         | 
| 22 | 
            -
              rspec.include FixtureHelpers
         | 
| 23 | 
            -
              rspec.backtrace_exclusion_patterns = [] if ENV['FULLSTACK']
         | 
| 24 | 
            -
              rspec.disable_monkey_patching!
         | 
| 25 | 
            -
              rspec.raise_errors_for_deprecations!
         | 
| 26 | 
            -
              if defined?(Mutant)
         | 
| 27 | 
            -
                rspec.around(:each) do |example|
         | 
| 28 | 
            -
                  Timeout.timeout(1, &example)
         | 
| 29 | 
            -
                end
         | 
| 30 | 
            -
              end
         | 
| 31 | 
            -
            end
         | 
| 32 | 
            -
             | 
| 33 | 
            -
            Bogus.configure do |bogus|
         | 
| 34 | 
            -
              bogus.search_modules << Yaks
         | 
| 35 | 
            -
              bogus.search_modules << Yaks::Mapper
         | 
| 36 | 
            -
            end
         | 
    
        data/spec/support/fixtures.rb
    CHANGED
    
    | @@ -1,4 +1,4 @@ | |
| 1 | 
            -
            shared_context 'fixtures' do
         | 
| 1 | 
            +
            RSpec.shared_context 'fixtures' do
         | 
| 2 2 | 
             
              let(:john)       { Friend.new(id: 1, name: 'john', pets: [boingboing, wassup], pet_peeve: regexps) }
         | 
| 3 3 | 
             
              let(:boingboing) { Pet.new(id: 2, name: 'boingboing', species: 'dog')                              }
         | 
| 4 4 | 
             
              let(:wassup)     { Pet.new(id: 3, name: 'wassup', species: 'cat')                                  }
         | 
| @@ -0,0 +1,63 @@ | |
| 1 | 
            +
            require "yaks/behaviour/optional_includes"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            RSpec.describe Yaks::Behaviour::OptionalIncludes do
         | 
| 4 | 
            +
              include_context 'yaks context'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              subject(:mapper)   { mapper_class.new(yaks_context) }
         | 
| 7 | 
            +
              let(:resource)     { mapper.call(instance) }
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              let(:mapper_class) do
         | 
| 10 | 
            +
                Class.new(Yaks::Mapper).tap do |mapper_class|
         | 
| 11 | 
            +
                  mapper_class.send :include, Yaks::Behaviour::OptionalIncludes
         | 
| 12 | 
            +
                  mapper_class.type "user"
         | 
| 13 | 
            +
                  mapper_class.has_many :posts, mapper: post_mapper_class
         | 
| 14 | 
            +
                  mapper_class.has_one :account, mapper: account_mapper_class
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
              let(:post_mapper_class) do
         | 
| 18 | 
            +
                Class.new(Yaks::Mapper).tap do |mapper_class|
         | 
| 19 | 
            +
                  mapper_class.type "post"
         | 
| 20 | 
            +
                  mapper_class.has_many :comments, mapper: comment_mapper_class
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
              let(:account_mapper_class) { Class.new(Yaks::Mapper) { type "account" } }
         | 
| 24 | 
            +
              let(:comment_mapper_class) { Class.new(Yaks::Mapper) { type "comment" } }
         | 
| 25 | 
            +
             | 
| 26 | 
            +
              let(:instance) { fake(posts: [fake(comments: [fake])], account: fake) }
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              it "includes the associations" do
         | 
| 29 | 
            +
                rack_env["QUERY_STRING"] = "include=posts.comments,account"
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                expect(resource.type).to eq "user"
         | 
| 32 | 
            +
                expect(resource.subresources[0].type).to eq "post"
         | 
| 33 | 
            +
                expect(resource.subresources[0].members[0].type).to eq "post"
         | 
| 34 | 
            +
                expect(resource.subresources[0].members[0].subresources[0].type).to eq "comment"
         | 
| 35 | 
            +
                expect(resource.subresources[0].members[0].subresources[0].members[0].type).to eq "comment"
         | 
| 36 | 
            +
                expect(resource.subresources[1].type).to eq "account"
         | 
| 37 | 
            +
              end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
              it "excludes associations not specified in the QUERY_STRING" do
         | 
| 40 | 
            +
                rack_env["QUERY_STRING"] = "include=posts"
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                expect(resource.subresources.count).to eq 1
         | 
| 43 | 
            +
              end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
              it "doesn't include the associations when QUERY_STRING is empty" do
         | 
| 46 | 
            +
                expect(resource.type).to eq "user"
         | 
| 47 | 
            +
                expect(resource.subresources).to be_empty
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
              it "allows :if to override the query parameter checking" do
         | 
| 51 | 
            +
                mapper_class.has_one :account, mapper: account_mapper_class, if: true
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                expect(resource.subresources.count).to eq 1
         | 
| 54 | 
            +
              end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
              it "caches parsing of the query parameter" do
         | 
| 57 | 
            +
                rack_env["QUERY_STRING"] = "include=posts"
         | 
| 58 | 
            +
                expect(mapper.call(instance).subresources.count).to eq 1
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                rack_env["QUERY_STRING"] = nil
         | 
| 61 | 
            +
                expect(mapper.call(instance).subresources.count).to eq 1
         | 
| 62 | 
            +
              end
         | 
| 63 | 
            +
            end
         | 
| @@ -30,7 +30,7 @@ RSpec.describe Yaks::DefaultPolicy, '#derive_mapper_from_collection' do | |
| 30 30 | 
             
                  it 'should propagate the error' do
         | 
| 31 31 | 
             
                    expect {
         | 
| 32 32 | 
             
                      policy.derive_mapper_from_object([])
         | 
| 33 | 
            -
                    }.to raise_error
         | 
| 33 | 
            +
                    }.to raise_error(RuntimeError)
         | 
| 34 34 | 
             
                  end
         | 
| 35 35 | 
             
                end
         | 
| 36 36 |  | 
| @@ -38,9 +38,9 @@ RSpec.describe Yaks::DefaultPolicy, '#derive_mapper_from_collection' do | |
| 38 38 | 
             
                  let(:options) { {namespace: DislikesOtherMappers} }
         | 
| 39 39 |  | 
| 40 40 | 
             
                  it 'should propagate the error' do
         | 
| 41 | 
            -
                    expect  | 
| 41 | 
            +
                    expect {
         | 
| 42 42 | 
             
                      policy.derive_mapper_from_object([Namespace::Nested::Rye.new])
         | 
| 43 | 
            -
                     | 
| 43 | 
            +
                    }.to raise_error(RuntimeError)
         | 
| 44 44 | 
             
                  end
         | 
| 45 45 | 
             
                end
         | 
| 46 46 | 
             
              end
         | 
| @@ -47,10 +47,12 @@ RSpec.describe Yaks::Format::JsonAPI do | |
| 47 47 | 
             
                  expect(format.call(resource)).to eql(
         | 
| 48 48 | 
             
                    data: {
         | 
| 49 49 | 
             
                      type: 'wizards',
         | 
| 50 | 
            +
                      relationships: {
         | 
| 51 | 
            +
                        favourite_spell: {data: {type: "spells", id: "1"}}
         | 
| 52 | 
            +
                      },
         | 
| 50 53 | 
             
                      links: {
         | 
| 51 54 | 
             
                        self: "/the/self/link",
         | 
| 52 | 
            -
                        profile: "/the/profile/link" | 
| 53 | 
            -
                        'favourite_spell' => {linkage: {type: "spells", id: "1"}},
         | 
| 55 | 
            +
                        profile: "/the/profile/link"
         | 
| 54 56 | 
             
                      }
         | 
| 55 57 | 
             
                    },
         | 
| 56 58 | 
             
                    included: [{type: 'spells', id: "1"}]
         | 
| @@ -72,7 +74,7 @@ RSpec.describe Yaks::Format::JsonAPI do | |
| 72 74 | 
             
                  expect(format.call(resource)).to eql(
         | 
| 73 75 | 
             
                    data: {
         | 
| 74 76 | 
             
                      type: 'wizards',
         | 
| 75 | 
            -
                       | 
| 77 | 
            +
                      relationships: {favourite_spell: {data: {type: 'spells', id: "777"}}}
         | 
| 76 78 | 
             
                    },
         | 
| 77 79 | 
             
                    included: [{type: 'spells', id: "777", attributes: {name: 'Lucky Sevens'}}]
         | 
| 78 80 | 
             
                  )
         | 
| @@ -103,10 +105,10 @@ RSpec.describe Yaks::Format::JsonAPI do | |
| 103 105 | 
             
                it 'should include the each subresource only once' do
         | 
| 104 106 | 
             
                  expect(format.call(resource)).to eql(
         | 
| 105 107 | 
             
                    data: [
         | 
| 106 | 
            -
                      {type: 'wizards', id: '7',  | 
| 107 | 
            -
                      {type: 'wizards', id: '3',  | 
| 108 | 
            -
                      {type: 'wizards', id: '2',  | 
| 109 | 
            -
                      {type: 'wizards', id: '9',  | 
| 108 | 
            +
                      {type: 'wizards', id: '7', relationships: {favourite_spell: {data: {type: 'spells', id: '1'}}}},
         | 
| 109 | 
            +
                      {type: 'wizards', id: '3', relationships: {favourite_spell: {data: {type: 'spells', id: '1'}}}},
         | 
| 110 | 
            +
                      {type: 'wizards', id: '2', relationships: {favourite_spell: {data: {type: 'spells', id: '12'}}}},
         | 
| 111 | 
            +
                      {type: 'wizards', id: '9', relationships: {wand:            {data: {type: 'wands',  id: '1'}}}},
         | 
| 110 112 | 
             
                    ],
         | 
| 111 113 | 
             
                    included: [
         | 
| 112 114 | 
             
                      {type: 'spells', id: '1'},
         | 
| @@ -121,14 +123,38 @@ RSpec.describe Yaks::Format::JsonAPI do | |
| 121 123 | 
             
                let(:resource) {
         | 
| 122 124 | 
             
                  Yaks::Resource.new(
         | 
| 123 125 | 
             
                    type: 'wizard',
         | 
| 124 | 
            -
                    subresources: [ | 
| 126 | 
            +
                    subresources: [subresource]
         | 
| 125 127 | 
             
                  )
         | 
| 126 128 | 
             
                }
         | 
| 127 129 |  | 
| 128 | 
            -
                 | 
| 129 | 
            -
                   | 
| 130 | 
            -
             | 
| 131 | 
            -
                   | 
| 130 | 
            +
                context "non-collection subresouce" do
         | 
| 131 | 
            +
                  let(:subresource) { Yaks::NullResource.new.add_rel("rel:wand") }
         | 
| 132 | 
            +
             | 
| 133 | 
            +
                  it 'should include a nil linkage object' do
         | 
| 134 | 
            +
                    expect(format.call(resource)).to eql(
         | 
| 135 | 
            +
                      data: {
         | 
| 136 | 
            +
                        type: 'wizards',
         | 
| 137 | 
            +
                        relationships: {
         | 
| 138 | 
            +
                          wand: {data: nil}
         | 
| 139 | 
            +
                        }
         | 
| 140 | 
            +
                      }
         | 
| 141 | 
            +
                    )
         | 
| 142 | 
            +
                  end
         | 
| 143 | 
            +
                end
         | 
| 144 | 
            +
             | 
| 145 | 
            +
                context "collection subresouce" do
         | 
| 146 | 
            +
                  let(:subresource) { Yaks::NullResource.new(collection: true).add_rel("rel:wands") }
         | 
| 147 | 
            +
             | 
| 148 | 
            +
                  it 'should include a nil linkage object' do
         | 
| 149 | 
            +
                    expect(format.call(resource)).to eql(
         | 
| 150 | 
            +
                      data: {
         | 
| 151 | 
            +
                        type: 'wizards',
         | 
| 152 | 
            +
                        relationships: {
         | 
| 153 | 
            +
                          wands: {data: []}
         | 
| 154 | 
            +
                        }
         | 
| 155 | 
            +
                      }
         | 
| 156 | 
            +
                    )
         | 
| 157 | 
            +
                  end
         | 
| 132 158 | 
             
                end
         | 
| 133 159 | 
             
              end
         | 
| 134 160 |  | 
    
        data/yaks.gemspec
    CHANGED
    
    | @@ -38,8 +38,10 @@ Gem::Specification.new do |gem| | |
| 38 38 | 
             
              gem.add_development_dependency 'benchmark-ips'
         | 
| 39 39 | 
             
              gem.add_development_dependency 'bogus'
         | 
| 40 40 | 
             
              gem.add_development_dependency 'hamster'
         | 
| 41 | 
            -
               | 
| 42 | 
            -
             | 
| 41 | 
            +
              if RUBY_VERSION >= "2.1.0"
         | 
| 42 | 
            +
                gem.add_development_dependency 'mutant'
         | 
| 43 | 
            +
                gem.add_development_dependency 'mutant-rspec'
         | 
| 44 | 
            +
              end
         | 
| 43 45 | 
             
              gem.add_development_dependency 'rake'
         | 
| 44 46 | 
             
              gem.add_development_dependency 'rubocop'
         | 
| 45 47 | 
             
              gem.add_development_dependency 'rspec', '~> 3.0'
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: yaks
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 0.11.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Arne Brasseur
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2015- | 
| 11 | 
            +
            date: 2015-07-09 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: abstract_type
         | 
| @@ -318,6 +318,7 @@ files: | |
| 318 318 | 
             
            - ataru_setup.rb
         | 
| 319 319 | 
             
            - find_missing_tests.rb
         | 
| 320 320 | 
             
            - lib/yaks.rb
         | 
| 321 | 
            +
            - lib/yaks/behaviour/optional_includes.rb
         | 
| 321 322 | 
             
            - lib/yaks/breaking_changes.rb
         | 
| 322 323 | 
             
            - lib/yaks/builder.rb
         | 
| 323 324 | 
             
            - lib/yaks/changelog.rb
         | 
| @@ -395,6 +396,7 @@ files: | |
| 395 396 | 
             
            - spec/support/pet_peeve_mapper.rb
         | 
| 396 397 | 
             
            - spec/support/shared_contexts.rb
         | 
| 397 398 | 
             
            - spec/support/youtypeit_models_mappers.rb
         | 
| 399 | 
            +
            - spec/unit/yaks/behaviour/optional_includes_spec.rb
         | 
| 398 400 | 
             
            - spec/unit/yaks/builder_spec.rb
         | 
| 399 401 | 
             
            - spec/unit/yaks/collection_mapper_spec.rb
         | 
| 400 402 | 
             
            - spec/unit/yaks/collection_resource_spec.rb
         | 
| @@ -463,7 +465,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 463 465 | 
             
                  version: '0'
         | 
| 464 466 | 
             
            requirements: []
         | 
| 465 467 | 
             
            rubyforge_project: 
         | 
| 466 | 
            -
            rubygems_version: 2. | 
| 468 | 
            +
            rubygems_version: 2.2.3
         | 
| 467 469 | 
             
            signing_key: 
         | 
| 468 470 | 
             
            specification_version: 4
         | 
| 469 471 | 
             
            summary: Serialize to hypermedia. HAL, JSON-API, etc.
         | 
| @@ -497,6 +499,7 @@ test_files: | |
| 497 499 | 
             
            - spec/support/pet_peeve_mapper.rb
         | 
| 498 500 | 
             
            - spec/support/shared_contexts.rb
         | 
| 499 501 | 
             
            - spec/support/youtypeit_models_mappers.rb
         | 
| 502 | 
            +
            - spec/unit/yaks/behaviour/optional_includes_spec.rb
         | 
| 500 503 | 
             
            - spec/unit/yaks/builder_spec.rb
         | 
| 501 504 | 
             
            - spec/unit/yaks/collection_mapper_spec.rb
         | 
| 502 505 | 
             
            - spec/unit/yaks/collection_resource_spec.rb
         |