yaks 0.4.0.rc1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 598793af50666377a34d0efb9e5264b2c7767f3b
4
- data.tar.gz: 87071e277c536147dcbe47a3201dbb59156191ef
3
+ metadata.gz: f3292feb7cef328921828cc97b68f397d23b7269
4
+ data.tar.gz: 0d074d9a0ea85e905d22572182904fe4f6fb3e36
5
5
  SHA512:
6
- metadata.gz: 45b38a236ceb4ceafcb68007663069a158b638116a46599071e054979d2747fd47550cfa3b0d04028ccefd8e47e0999e251686ab2bba2f3b08615a84e9b8bf16
7
- data.tar.gz: 0a0887de90d46d746a4dc1761a2f2d377dbb0bae99dd59c6e1101c1a493eceb147a4fae8344655f98fbebbd1a35ca00c7b0e7fc92b11f4db97df1205e71dcb1b
6
+ metadata.gz: a61962b2c69d90ba0b4ccbd2223bc14b27e0ee5ae773bcb12392c7d5bb9a9969497e5e86ae85761ebbab732230dd14f59dc3e8241b35282d9630c39b5c0f41fe
7
+ data.tar.gz: 12d9e712b28185dc1409518045da51d4eb4a9fd32c7be42a55747e7fd33b4698728a3c46bf170f54183576f7437eafa54db795fb13d4177594b9de62ed418165
data/CHANGELOG.md CHANGED
@@ -1,10 +1,18 @@
1
- # v0.4.0 (unreleased)
1
+ # v0.4.0
2
+
3
+ * Introduce after {} post-processing hook
4
+ * Streamline interfaces and variable names
5
+ * Improve deriving mappers automatically, even with Rails style autoloading
6
+ * Give CollectionResource a members_rel, for HAL-like formats with no top-level collection concept
7
+ * Switch back to using `src` and `dest` as the rel-template keys
8
+
9
+ # v0.4.0.rc1
2
10
 
3
11
  * Introduce Yaks.new as the main public interface
4
12
  * Fix JsonApiSerializer and make it compliant with current spec
5
13
  * Remove Hamster dependency, Yaks new uses plain old Ruby arrays and hashes
6
- * Remove RelRegistry and ProfileRegistry in favor of a simpler explicit syntax + policy based fallback
7
- * Add more policy derivation hooks, plus make DefaultPolicy template for rel urls configurable
14
+ * Remove `RelRegistry` and `ProfileRegistry` in favor of a simpler explicit syntax + policy based fallback
15
+ * Add more policy derivation hooks, plus make `DefaultPolicy` template for rel urls configurable
8
16
  * Optionally take a Rack env hash, pass it around so mappers can inspect it
9
17
  * Honor the HTTP Accept header if it is present in the rack env
10
18
  * Add map_to_primitive configuration option
data/README.md CHANGED
@@ -314,6 +314,30 @@ yaks = Yaks.new do
314
314
  end
315
315
  ```
316
316
 
317
+ ## Primitives
318
+
319
+ For JSON based formats, a final step in serializing is to turn the nested data into primitives that have a JSON equivalent. For example, JSON has no notion of symbols or 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`
320
+
321
+ ```ruby
322
+ Yaks.new do
323
+ map_to_primitive Date, Time, DateTime do
324
+ object.iso8601
325
+ end
326
+ end
327
+ ```
328
+
329
+ This can also be used to transform alternative data structures, like those from Hamster, into Ruby arrays and hashes. Use `call()` to recursively turn things into primitives.
330
+
331
+ ```ruby
332
+ Yaks.new do
333
+ map_to_primitive Hamster::Vector, Hamster::List do
334
+ object.map do |item|
335
+ call(item)
336
+ end
337
+ end
338
+ end
339
+ ```
340
+
317
341
  ## Usage
318
342
 
319
343
  Yaks is used in production by [Ticketsolve](http://www.ticketsolve.com/). You can find an example API endpoint [here](http://leicestersquaretheatre.ticketsolve.com/api).
data/bench/bench.rb ADDED
@@ -0,0 +1,17 @@
1
+
2
+ require 'benchmark/ips'
3
+ require 'yaks'
4
+
5
+ require_relative '../spec/acceptance/models'
6
+ require_relative '../spec/fixture_helpers'
7
+
8
+ Benchmark.ips do |x|
9
+ $yaks = Yaks.new
10
+
11
+ input = FixtureHelpers.load_yaml_fixture 'confucius'
12
+
13
+ x.report "Simple HAL mapping" do
14
+ $yaks.serialize(input)
15
+ end
16
+
17
+ end
@@ -5,34 +5,45 @@ module Yaks
5
5
  attr_reader :collection
6
6
  alias collection object
7
7
 
8
- def initialize(collection, context)
9
- super(collection, context)
8
+ def initialize(context)
9
+ super(context)
10
10
  end
11
11
 
12
- def resource_mapper
13
- context[:resource_mapper]
12
+ def member_mapper
13
+ context.fetch(:member_mapper) do
14
+ if collection.first
15
+ mapper_for_model(collection.first)
16
+ end
17
+ end
14
18
  end
15
19
 
16
- def to_resource
20
+ def call(collection)
21
+ @object = collection
22
+
17
23
  CollectionResource.new(
18
24
  type: collection_type,
25
+ members_rel: members_rel,
19
26
  links: map_links,
20
27
  attributes: map_attributes,
21
28
  members: collection.map do |obj|
22
- mapper_for_model(obj).new(obj, context).to_resource
29
+ mapper_for_model(obj).new(context).call(obj)
23
30
  end
24
31
  )
25
32
  end
26
33
 
27
34
  private
28
35
 
36
+ def members_rel
37
+ policy.expand_rel( 'collection', pluralize( collection_type ) )
38
+ end
39
+
29
40
  def collection_type
30
- return unless context.key?(:resource_mapper)
31
- resource_mapper.config.type || policy.derive_type_from_mapper_class(resource_mapper)
41
+ return unless member_mapper
42
+ member_mapper.config.type || policy.derive_type_from_mapper_class(member_mapper)
32
43
  end
33
44
 
34
45
  def mapper_for_model(model)
35
- context.fetch(:resource_mapper) do
46
+ context.fetch(:member_mapper) do
36
47
  policy.derive_mapper_from_object(model)
37
48
  end
38
49
  end
@@ -15,18 +15,19 @@ module Yaks
15
15
  # In the second case a collection has a single "subresource", being its
16
16
  # members.
17
17
  class CollectionResource < Resource
18
- include Equalizer.new(:type, :links, :attributes, :members)
18
+ include Equalizer.new(:type, :links, :attributes, :members, :members_rel)
19
19
  include Enumerable
20
20
 
21
21
  extend Forwardable
22
22
 
23
- attr_reader :type, :links, :members
23
+ attr_reader :type, :links, :members, :members_rel
24
24
 
25
25
  def_delegators :members, :each
26
26
 
27
27
  def initialize(options)
28
28
  super
29
- @members = options.fetch(:members, [])
29
+ @members = options.fetch(:members, [])
30
+ @members_rel = options.fetch(:members_rel, 'members')
30
31
  end
31
32
 
32
33
  # Make a CollectionResource quack like a resource.
@@ -44,8 +45,7 @@ module Yaks
44
45
  # :(
45
46
  def subresources
46
47
  if members.any?
47
- profile_link = links.select{|link| link.rel.equal? :profile}.first.uri
48
- { profile_link => self }
48
+ { members_rel => self }
49
49
  else
50
50
  {}
51
51
  end
data/lib/yaks/config.rb CHANGED
@@ -33,11 +33,16 @@ module Yaks
33
33
  def mapper_namespace(namespace)
34
34
  config.policy_options[:namespace] = namespace
35
35
  end
36
+ alias namespace mapper_namespace
36
37
 
37
38
  def map_to_primitive(*args, &blk)
38
39
  config.primitivize.map(*args, &blk)
39
40
  end
40
41
 
42
+ def after(&block)
43
+ config.steps << block
44
+ end
45
+
41
46
  DefaultPolicy.public_instance_methods(false).each do |method|
42
47
  define_method method do |&blk|
43
48
  @policies << proc {
@@ -47,13 +52,14 @@ module Yaks
47
52
  end
48
53
  end
49
54
 
50
- attr_accessor :format_options, :default_format, :policy_class, :policy_options, :primitivize
55
+ attr_accessor :format_options, :default_format, :policy_class, :policy_options, :primitivize, :steps
51
56
 
52
57
  def initialize(&blk)
53
58
  @format_options = Hash.new({})
54
59
  @default_format = :hal
55
60
  @policy_options = {}
56
- @primitivize = Primitivize.create
61
+ @primitivize = Primitivize.create
62
+ @steps = [ @primitivize ]
57
63
  DSL.new(self, &blk)
58
64
  end
59
65
 
@@ -64,7 +70,7 @@ module Yaks
64
70
  def serializer_class(opts, env)
65
71
  if env.key? 'HTTP_ACCEPT'
66
72
  accept = Rack::Accept::Charset.new(env['HTTP_ACCEPT'])
67
- mime_type = accept.best_of(Yaks::Serializer.mime_types)
73
+ mime_type = accept.best_of(Yaks::Serializer.mime_types.values)
68
74
  return Yaks::Serializer.by_mime_type(mime_type) if mime_type
69
75
  end
70
76
  Yaks::Serializer.by_name(opts.fetch(:format) { @default_format })
@@ -82,15 +88,16 @@ module Yaks
82
88
  # Yaks::Resource => serialized structure
83
89
  # serialized structure => serialized flat
84
90
 
85
- def serialize(object, opts = {}, env = {})
91
+ def call(object, opts = {}, env = {})
86
92
  context = {
87
93
  policy: policy,
88
94
  env: env
89
95
  }
90
96
  mapper = opts.fetch(:mapper) { policy.derive_mapper_from_object(object) }
91
- resource = mapper.new(object, context).to_resource
97
+ resource = mapper.new(context).call(object)
92
98
  serialized = serializer_class(opts, env).new(resource, format_options[format_name(opts)]).call
93
- primitivize.call(serialized)
99
+ steps.inject(serialized) {|memo, step| step.call(memo) }
94
100
  end
101
+ alias serialize call
95
102
  end
96
103
  end
@@ -3,7 +3,7 @@ module Yaks
3
3
  include Util
4
4
 
5
5
  DEFAULTS = {
6
- rel_template: "rel:src={mapper_name}&dest={association_name}",
6
+ rel_template: "rel:src={src}&dest={dest}",
7
7
  namespace: Kernel
8
8
  }
9
9
 
@@ -15,9 +15,18 @@ module Yaks
15
15
 
16
16
  def derive_mapper_from_object(model)
17
17
  if model.respond_to? :to_ary
18
- if @options[:namespace].const_defined?(:CollectionMapper)
19
- @options[:namespace].const_get(:CollectionMapper)
18
+ if m = model.first
19
+ name = m.class.name.split('::').last + 'CollectionMapper'
20
+ begin
21
+ return @options[:namespace].const_get(name)
22
+ rescue NameError
23
+ end
20
24
  end
25
+ begin
26
+ return @options[:namespace].const_get(:CollectionMapper)
27
+ rescue NameError
28
+ end
29
+ Yaks::CollectionMapper
21
30
  else
22
31
  name = model.class.name.split('::').last
23
32
  @options[:namespace].const_get(name + 'Mapper')
@@ -25,19 +34,22 @@ module Yaks
25
34
  end
26
35
 
27
36
  def derive_type_from_mapper_class(mapper_class)
28
- underscore(mapper_class.to_s.sub(/Mapper$/, ''))
37
+ underscore(mapper_class.name.split('::').last.sub(/Mapper$/, ''))
29
38
  end
30
39
 
31
40
  def derive_mapper_from_association(association)
32
- Object.const_get("#{camelize(singularize(association.name.to_s))}Mapper")
41
+ @options[:namespace].const_get("#{camelize(singularize(association.name.to_s))}Mapper")
33
42
  end
34
43
 
35
44
  def derive_rel_from_association(mapper, association)
45
+ expand_rel( derive_type_from_mapper_class(mapper.class), association.name )
46
+ end
47
+
48
+ def expand_rel(src_name, dest_name)
36
49
  URITemplate.new(@options[:rel_template]).expand(
37
- mapper_name: derive_type_from_mapper_class(mapper.class),
38
- association_name: association.name
50
+ src: src_name,
51
+ dest: dest_name
39
52
  )
40
53
  end
41
-
42
54
  end
43
55
  end
@@ -2,14 +2,15 @@ module Yaks
2
2
  class Mapper
3
3
  class HasMany < Association
4
4
  def map_resource(collection, context)
5
- resource_mapper = association_mapper(context.fetch(:policy))
6
- context = context.merge(resource_mapper: resource_mapper)
7
- collection_mapper.new(collection, context).to_resource
5
+ policy = context.fetch(:policy)
6
+ member_mapper = association_mapper(policy)
7
+ context = context.merge(member_mapper: member_mapper)
8
+ collection_mapper(collection, policy).new(context).call(collection)
8
9
  end
9
10
 
10
- def collection_mapper
11
+ def collection_mapper(collection, policy)
11
12
  return @collection_mapper unless @collection_mapper.equal? Undefined
12
- CollectionMapper
13
+ policy.derive_mapper_from_object(collection)
13
14
  end
14
15
  end
15
16
  end
@@ -1,10 +1,10 @@
1
1
  module Yaks
2
2
  class Mapper
3
3
  class HasOne < Association
4
- def map_resource(instance, context)
4
+ def map_resource(object, context)
5
5
  association_mapper(context.fetch(:policy))
6
- .new(instance, context)
7
- .to_resource
6
+ .new(context)
7
+ .call(object)
8
8
  end
9
9
  end
10
10
  end
data/lib/yaks/mapper.rb CHANGED
@@ -10,8 +10,7 @@ module Yaks
10
10
 
11
11
  attr_reader :object, :context
12
12
 
13
- def initialize(object, context)
14
- @object = object
13
+ def initialize(context)
15
14
  @context = context
16
15
  end
17
16
 
@@ -23,7 +22,9 @@ module Yaks
23
22
  context.fetch(:env)
24
23
  end
25
24
 
26
- def call
25
+ def call(object)
26
+ @object = object
27
+
27
28
  return NullResource.new if object.nil?
28
29
 
29
30
  Resource.new(
@@ -33,7 +34,6 @@ module Yaks
33
34
  subresources: map_subresources
34
35
  )
35
36
  end
36
- alias to_resource call
37
37
 
38
38
  def map_attributes
39
39
  filter(attributes).each_with_object({}) do |attr, memo|
@@ -24,7 +24,7 @@ module Yaks
24
24
  @serializers[name] = klass
25
25
 
26
26
  @mime_types ||= {}
27
- @mime_types[mime_type] = klass
27
+ @mime_types[mime_type] = [name, klass]
28
28
  end
29
29
 
30
30
  def by_name(name)
@@ -32,11 +32,11 @@ module Yaks
32
32
  end
33
33
 
34
34
  def by_mime_type(mime_type)
35
- @mime_types.fetch(mime_type)
35
+ @mime_types.fetch(mime_type)[1]
36
36
  end
37
37
 
38
38
  def mime_types
39
- @mime_types.keys
39
+ @mime_types.inject({}) {|memo, (mime_type, (name, _))| memo[name] = mime_type ; memo }
40
40
  end
41
41
  end
42
42
  end
data/lib/yaks/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Yaks
2
- VERSION = '0.4.0.rc1'
2
+ VERSION = '0.4.0'
3
3
  end
data/lib/yaks.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'forwardable'
4
4
  require 'set'
5
+ require 'pathname'
5
6
 
6
7
  require 'concord'
7
8
  require 'inflection'
@@ -16,6 +17,7 @@ require 'yaks/default_policy'
16
17
 
17
18
  module Yaks
18
19
  Undefined = Object.new
20
+ Root = Pathname(__FILE__).join('../..')
19
21
 
20
22
  YAKS_DEFAULT_OPTIONS = {
21
23
  singular_links: [:self, :profile]
@@ -1,6 +1,4 @@
1
1
  require 'spec_helper'
2
- require 'yaml'
3
- require 'json'
4
2
 
5
3
  require_relative './models'
6
4
 
@@ -0,0 +1,14 @@
1
+ require 'yaml'
2
+ require 'json'
3
+
4
+ module FixtureHelpers
5
+ module_function
6
+
7
+ def load_json_fixture(name)
8
+ JSON.parse(Yaks::Root.join('spec/json', name + '.json').read)
9
+ end
10
+
11
+ def load_yaml_fixture(name)
12
+ YAML.load(Yaks::Root.join('spec/yaml', name + '.yaml').read)
13
+ end
14
+ end
@@ -3,8 +3,8 @@ require 'spec_helper'
3
3
  RSpec.describe 'Mapping domain models to Resource objects' do
4
4
  include_context 'fixtures'
5
5
 
6
- subject { mapper.to_resource }
7
- let(:mapper) { FriendMapper.new(john, policy: Yaks::DefaultPolicy.new, env: {}) }
6
+ subject { mapper.call(john) }
7
+ let(:mapper) { FriendMapper.new(policy: Yaks::DefaultPolicy.new, env: {}) }
8
8
 
9
9
  it { should be_a Yaks::Resource }
10
10
  its(:type) { should eql 'friend' }
@@ -19,7 +19,8 @@ RSpec.describe 'Mapping domain models to Resource objects' do
19
19
  members: [
20
20
  Yaks::Resource.new(type: 'pet', attributes: {:id => 2, :species => "dog", :name => "boingboing"}),
21
21
  Yaks::Resource.new(type: 'pet', attributes: {:id => 3, :species => "cat", :name => "wassup"})
22
- ]
22
+ ],
23
+ members_rel: 'rel:src=collection&dest=pets'
23
24
  )
24
25
  )
25
26
  }
@@ -4,7 +4,7 @@
4
4
  "self" : { "href" : "http://api.example.com/plants" }
5
5
  },
6
6
  "_embedded" : {
7
- "http://api.example.com/doc/plant_collection" : [
7
+ "http://api.example.com/rels/plants" : [
8
8
  {
9
9
  "_links" : {
10
10
  "profile" : { "href" : "http://api.example.com/doc/plant" },
data/spec/spec_helper.rb CHANGED
@@ -1,13 +1,9 @@
1
- require 'pathname'
2
1
  require 'rspec/its'
3
2
 
4
- ROOT = Pathname(__FILE__).join('../..')
5
-
6
- $LOAD_PATH.unshift(ROOT.join('lib'))
7
-
8
3
  require 'yaks'
9
4
  require 'virtus'
10
- require 'json'
5
+
6
+ require 'fixture_helpers'
11
7
 
12
8
  require_relative 'support/models'
13
9
  require_relative 'support/pet_mapper'
@@ -17,15 +13,9 @@ require_relative 'support/fixtures'
17
13
  require_relative 'support/shared_contexts'
18
14
  require_relative 'support/youtypeit_models_mappers'
19
15
 
20
- def load_json_fixture(name)
21
- JSON.parse(ROOT.join('spec/json', name + '.json').read)
22
- end
23
-
24
- def load_yaml_fixture(name)
25
- YAML.load(ROOT.join('spec/yaml', name + '.yaml').read)
26
- end
27
16
 
28
17
  RSpec.configure do |rspec|
18
+ rspec.include FixtureHelpers
29
19
  rspec.backtrace_exclusion_patterns = [] if ENV['FULLSTACK']
30
20
  #rspec.disable_monkey_patching!
31
21
  rspec.raise_errors_for_deprecations!
@@ -1,5 +1,11 @@
1
1
  shared_context 'collection resource' do
2
- let(:resource) { Yaks::CollectionResource.new(links: links, members: members) }
2
+ let(:resource) do
3
+ Yaks::CollectionResource.new(
4
+ links: links,
5
+ members: members,
6
+ members_rel: 'http://api.example.com/rels/plants'
7
+ )
8
+ end
3
9
  let(:links) { [] }
4
10
  let(:members) { [] }
5
11
  end
@@ -3,48 +3,52 @@ require 'spec_helper'
3
3
  RSpec.describe Yaks::CollectionMapper do
4
4
  include_context 'fixtures'
5
5
 
6
- subject(:mapper) { described_class.new(collection, context) }
6
+ subject(:mapper) { described_class.new(context) }
7
7
  let(:context) {
8
- { resource_mapper: resource_mapper,
8
+ { member_mapper: member_mapper,
9
9
  policy: policy,
10
10
  env: {}
11
11
  }
12
12
  }
13
13
  let(:collection) { [] }
14
- let(:resource_mapper) { Class.new(Yaks::Mapper) { type 'the_type' } }
14
+ let(:member_mapper) { Class.new(Yaks::Mapper) { type 'the_type' } }
15
15
  let(:policy) { Yaks::DefaultPolicy.new }
16
16
 
17
- its(:to_resource) {
18
- should eql Yaks::CollectionResource.new(
19
- type: 'the_type',
20
- links: [],
21
- attributes: {},
22
- members: []
17
+ it 'should map the collection' do
18
+ expect(mapper.call(collection)).to eql Yaks::CollectionResource.new(
19
+ type: 'the_type',
20
+ links: [],
21
+ attributes: {},
22
+ members: [],
23
+ members_rel: 'rel:src=collection&dest=the_types'
23
24
  )
24
- }
25
+ end
25
26
 
26
27
  context 'with members' do
27
28
  let(:collection) { [boingboing, wassup]}
28
- let(:resource_mapper) { PetMapper }
29
+ let(:member_mapper) { PetMapper }
29
30
 
30
- its(:to_resource) {
31
- should eql Yaks::CollectionResource.new(
31
+ it 'should map the members' do
32
+ mapper.call(collection)
33
+
34
+ expect(mapper.call(collection)).to eql Yaks::CollectionResource.new(
32
35
  type: 'pet',
33
36
  links: [],
34
37
  attributes: {},
35
38
  members: [
36
39
  Yaks::Resource.new(type: 'pet', attributes: {:id => 2, :species => "dog", :name => "boingboing"}),
37
40
  Yaks::Resource.new(type: 'pet', attributes: {:id => 3, :species => "cat", :name => "wassup"})
38
- ]
41
+ ],
42
+ members_rel: 'rel:src=collection&dest=pets'
39
43
  )
40
- }
44
+ end
41
45
  end
42
46
 
43
47
  context 'with collection attributes' do
44
48
  subject(:mapper) {
45
49
  Class.new(Yaks::CollectionMapper) do
46
50
  attributes :foo, :bar
47
- end.new(collection, context)
51
+ end.new(context)
48
52
  }
49
53
 
50
54
  let(:collection) {
@@ -54,31 +58,33 @@ RSpec.describe Yaks::CollectionMapper do
54
58
  end.new([])
55
59
  }
56
60
 
57
- its(:to_resource) {
58
- should eql Yaks::CollectionResource.new(
61
+ it 'should map the attributes' do
62
+ expect(mapper.call(collection)).to eql Yaks::CollectionResource.new(
59
63
  type: 'the_type',
60
64
  links: [],
61
65
  attributes: { foo: 123, bar: 'pi la~~~' },
62
- members: []
66
+ members: [],
67
+ members_rel: 'rel:src=collection&dest=the_types'
63
68
  )
64
- }
69
+ end
65
70
  end
66
71
 
67
72
  context 'with collection links' do
68
73
  subject(:mapper) {
69
74
  Class.new(Yaks::CollectionMapper) do
70
75
  link :self, 'http://api.example.com/orders'
71
- end.new(collection, context)
76
+ end.new(context)
72
77
  }
73
78
 
74
- its(:to_resource) {
75
- should eql Yaks::CollectionResource.new(
79
+ it 'should map the links' do
80
+ expect(mapper.call(collection)).to eql Yaks::CollectionResource.new(
76
81
  type: 'the_type',
77
82
  links: [ Yaks::Resource::Link.new(:self, 'http://api.example.com/orders', {}) ],
78
83
  attributes: { },
79
- members: []
84
+ members: [],
85
+ members_rel: 'rel:src=collection&dest=the_types'
80
86
  )
81
- }
87
+ end
82
88
  end
83
89
 
84
90
  end
@@ -50,7 +50,7 @@ RSpec.describe Yaks::CollectionResource do
50
50
  }
51
51
 
52
52
  its(:subresources) { should eql(
53
- 'http://rels/collection' => Yaks::CollectionResource.new(
53
+ 'members' => Yaks::CollectionResource.new(
54
54
  type: 'order',
55
55
  attributes: { total: 10.00 },
56
56
  links: [
@@ -63,7 +63,7 @@ RSpec.describe Yaks::CollectionResource do
63
63
  links: [Yaks::Resource::Link.new(:self, 'http://order/10', {})],
64
64
  attributes: { customer: 'John Doe', price: 10.00 }
65
65
  )
66
- ]
66
+ ],
67
67
  )
68
68
  )
69
69
  }
@@ -56,12 +56,12 @@ RSpec.describe Yaks::Config do
56
56
 
57
57
  describe '#serialize' do
58
58
  configure do
59
- rel_template 'http://api.mysuperfriends.com/{association_name}'
59
+ rel_template 'http://api.mysuperfriends.com/{dest}'
60
60
  format_options :hal, plural_links: [:copyright]
61
61
  end
62
62
 
63
63
  specify do
64
- expect(config.serialize(john)).to eql(load_json_fixture 'john.hal')
64
+ expect(config.call(john)).to eql(load_json_fixture 'john.hal')
65
65
  end
66
66
  end
67
67
 
@@ -3,9 +3,10 @@ require 'spec_helper'
3
3
  RSpec.describe Yaks::Mapper::HasMany do
4
4
  let(:closet_mapper) do
5
5
  Class.new(Yaks::Mapper) do
6
+ type 'closet'
6
7
  has_many :shoes,
7
8
  rel: 'http://foo/shoes',
8
- mapper: Class.new(Yaks::Mapper) { type 'shoes' ; attributes :size, :color }
9
+ mapper: Class.new(Yaks::Mapper) { type 'shoe' ; attributes :size, :color }
9
10
  end
10
11
  end
11
12
 
@@ -19,13 +20,14 @@ RSpec.describe Yaks::Mapper::HasMany do
19
20
  }
20
21
 
21
22
  it 'should map the subresources' do
22
- expect(closet_mapper.new(closet, policy: Yaks::DefaultPolicy.new, env: {}).map_subresources).to eql(
23
+ expect(closet_mapper.new(policy: Yaks::DefaultPolicy.new, env: {}).call(closet).subresources).to eql(
23
24
  "http://foo/shoes" => Yaks::CollectionResource.new(
24
- type: 'shoes',
25
+ type: 'shoe',
25
26
  members: [
26
- Yaks::Resource.new(type: 'shoes', attributes: {:size => 9, :color => :blue}),
27
- Yaks::Resource.new(type: 'shoes', attributes: {:size => 11.5, :color => :red})
28
- ]
27
+ Yaks::Resource.new(type: 'shoe', attributes: {:size => 9, :color => :blue}),
28
+ Yaks::Resource.new(type: 'shoe', attributes: {:size => 11.5, :color => :red})
29
+ ],
30
+ members_rel: 'rel:src=collection&dest=shoes'
29
31
  )
30
32
  )
31
33
  end
@@ -35,12 +37,17 @@ RSpec.describe Yaks::Mapper::HasMany do
35
37
  subject(:has_many) { described_class.new(:name, :mapper, :rel, collection_mapper) }
36
38
 
37
39
  context 'when the collection mapper is undefined' do
38
- its(:collection_mapper) { should equal Yaks::CollectionMapper }
40
+ it 'should derive one from collection and policy' do
41
+ expect(has_many.collection_mapper([], Yaks::DefaultPolicy.new)).to equal Yaks::CollectionMapper
42
+ end
39
43
  end
40
44
 
41
45
  context 'when the collection mapper is specified' do
42
46
  let(:collection_mapper) { :foo }
43
- its(:collection_mapper) { should equal :foo }
47
+
48
+ it 'should use the given collection mapper' do
49
+ expect(has_many.collection_mapper([], Yaks::DefaultPolicy.new)).to equal :foo
50
+ end
44
51
  end
45
52
  end
46
53
  end
@@ -11,6 +11,9 @@ RSpec.describe Yaks::Mapper::Link do
11
11
  its(:uri_template) { should eq URITemplate.new(template) }
12
12
  its(:expand?) { should be true }
13
13
 
14
+ let(:policy) { Yaks::DefaultPolicy.new }
15
+ let(:context) { { policy: policy, env: {} } }
16
+
14
17
  describe '#rel?' do
15
18
  it 'should return true if the relation matches' do
16
19
  expect(link.rel?(:next)).to be true
@@ -81,7 +84,9 @@ RSpec.describe Yaks::Mapper::Link do
81
84
  let(:object) { Struct.new(:x,:y).new(3,4) }
82
85
 
83
86
  let(:mapper) do
84
- Yaks::Mapper.new(object, nil)
87
+ Yaks::Mapper.new(context).tap do |mapper|
88
+ mapper.call(object)
89
+ end
85
90
  end
86
91
 
87
92
  context 'with attributes' do
@@ -1,7 +1,8 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  RSpec.describe Yaks::Mapper do
4
- subject(:mapper) { mapper_class.new(instance, context) }
4
+ subject(:mapper) { mapper_class.new(context) }
5
+ let(:resource) { mapper.call(instance) }
5
6
 
6
7
  let(:mapper_class) { Class.new(Yaks::Mapper) { type 'foo' } }
7
8
  let(:instance) { double(foo: 'hello', bar: 'world') }
@@ -19,7 +20,7 @@ RSpec.describe Yaks::Mapper do
19
20
  end
20
21
 
21
22
  it 'should load them from the model' do
22
- expect(mapper.map_attributes).to eq(foo: 'hello', bar: 'world')
23
+ expect(resource.attributes).to eq(foo: 'hello', bar: 'world')
23
24
  end
24
25
 
25
26
  context 'with attribute filtering' do
@@ -32,7 +33,7 @@ RSpec.describe Yaks::Mapper do
32
33
  end
33
34
 
34
35
  it 'should only map the non-filtered attributes' do
35
- expect(mapper.map_attributes).to eq(:bar => 'world')
36
+ expect(resource.attributes).to eq(:bar => 'world')
36
37
  end
37
38
  end
38
39
  end
@@ -43,13 +44,13 @@ RSpec.describe Yaks::Mapper do
43
44
  end
44
45
 
45
46
  it 'should map the link' do
46
- expect(mapper.map_links).to eq [
47
+ expect(resource.links).to eq [
47
48
  Yaks::Resource::Link.new(:profile, 'http://foo/bar', {})
48
49
  ]
49
50
  end
50
51
 
51
52
  it 'should use the link in the resource' do
52
- expect(mapper.to_resource.links).to include Yaks::Resource::Link.new(:profile, 'http://foo/bar', {})
53
+ expect(resource.links).to include Yaks::Resource::Link.new(:profile, 'http://foo/bar', {})
53
54
  end
54
55
 
55
56
  context 'with the same link rel defined multiple times' do
@@ -62,7 +63,7 @@ RSpec.describe Yaks::Mapper do
62
63
  end
63
64
 
64
65
  it 'should map all the links' do
65
- expect(mapper.map_links).to eq [
66
+ expect(resource.links).to eq [
66
67
  Yaks::Resource::Link.new(:profile, 'http://foo/bar', {}),
67
68
  Yaks::Resource::Link.new(:self, 'http://foo/bam', {}),
68
69
  Yaks::Resource::Link.new(:self, 'http://foo/baz', {}),
@@ -91,12 +92,12 @@ RSpec.describe Yaks::Mapper do
91
92
 
92
93
 
93
94
  it 'should have the subresource in the resource' do
94
- expect(mapper.to_resource.subresources).to eq("http://foo.bar/rels/widgets" => Yaks::Resource.new(type: 'widget', attributes: {:type => "super_widget"}))
95
+ expect(resource.subresources).to eq("http://foo.bar/rels/widgets" => Yaks::Resource.new(type: 'widget', attributes: {:type => "super_widget"}))
95
96
  end
96
97
 
97
98
  context 'with explicit mapper and rel' do
98
99
  it 'should delegate to the given mapper' do
99
- expect(mapper.map_subresources).to eq(
100
+ expect(resource.subresources).to eq(
100
101
  "http://foo.bar/rels/widgets" => Yaks::Resource.new(type: 'widget', attributes: {:type => "super_widget"})
101
102
  )
102
103
  end
@@ -112,7 +113,7 @@ RSpec.describe Yaks::Mapper do
112
113
  expect(assoc).to be_a Yaks::Mapper::HasOne
113
114
  widget_mapper
114
115
  }
115
- expect(mapper.map_subresources).to eq(
116
+ expect(resource.subresources).to eq(
116
117
  "http://foo.bar/rels/widgets" => Yaks::Resource.new(type: 'widget', attributes: {:type => "super_widget"})
117
118
  )
118
119
  end
@@ -129,7 +130,7 @@ RSpec.describe Yaks::Mapper do
129
130
  expect(assoc).to be_a Yaks::Mapper::HasOne
130
131
  'http://rel/rel'
131
132
  }
132
- expect(mapper.map_subresources).to eq(
133
+ expect(resource.subresources).to eq(
133
134
  "http://rel/rel" => Yaks::Resource.new(type: 'widget', attributes: {:type => "super_widget"})
134
135
  )
135
136
  end
@@ -143,7 +144,7 @@ RSpec.describe Yaks::Mapper do
143
144
  end
144
145
 
145
146
  it 'should not map the resource' do
146
- expect(mapper.map_subresources).to eq({})
147
+ expect(resource.subresources).to eq({})
147
148
  end
148
149
  end
149
150
  end
@@ -162,16 +163,14 @@ RSpec.describe Yaks::Mapper do
162
163
  end
163
164
 
164
165
  it 'should get the attribute from the mapper' do
165
- expect(mapper.map_attributes).to eq(fooattr: 'hello my friend', bar: 'world')
166
+ expect(resource.attributes).to eq(fooattr: 'hello my friend', bar: 'world')
166
167
  end
167
168
  end
168
169
  end
169
170
 
170
- describe '#to_resource' do
171
- let(:instance) { nil }
172
-
171
+ describe '#call' do
173
172
  it 'should return a NullResource when the subject is nil' do
174
- expect(mapper.to_resource).to be_a Yaks::NullResource
173
+ expect(mapper.call(nil)).to be_a Yaks::NullResource
175
174
  end
176
175
  end
177
176
  end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Yaks::Serializer do
3
+ RSpec.describe Yaks::Serializer do
4
4
  describe '.by_name' do
5
5
  specify { expect(Yaks::Serializer.by_name(:hal)).to eql Yaks::Serializer::Hal }
6
6
  specify { expect(Yaks::Serializer.by_name(:json_api)).to eql Yaks::Serializer::JsonApi }
data/yaks.gemspec CHANGED
@@ -23,8 +23,10 @@ Gem::Specification.new do |gem|
23
23
  gem.add_runtime_dependency 'rack-accept' , '~> 0.4.5'
24
24
 
25
25
  gem.add_development_dependency 'virtus'
26
- gem.add_development_dependency 'rspec', '~> 3.0'
26
+ gem.add_development_dependency 'rspec', '~> 2.99'
27
27
  gem.add_development_dependency 'rake'
28
28
  gem.add_development_dependency 'mutant-rspec'
29
+ gem.add_development_dependency 'mutant', '0.5.12'
29
30
  gem.add_development_dependency 'rspec-its'
31
+ gem.add_development_dependency 'benchmark-ips'
30
32
  end
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.0.rc1
4
+ version: 0.4.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: 2014-06-11 00:00:00.000000000 Z
11
+ date: 2014-06-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: inflection
@@ -86,14 +86,14 @@ dependencies:
86
86
  requirements:
87
87
  - - ~>
88
88
  - !ruby/object:Gem::Version
89
- version: '3.0'
89
+ version: '2.99'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - ~>
95
95
  - !ruby/object:Gem::Version
96
- version: '3.0'
96
+ version: '2.99'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: rake
99
99
  requirement: !ruby/object:Gem::Requirement
@@ -122,6 +122,20 @@ dependencies:
122
122
  - - '>='
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: mutant
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - '='
130
+ - !ruby/object:Gem::Version
131
+ version: 0.5.12
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - '='
137
+ - !ruby/object:Gem::Version
138
+ version: 0.5.12
125
139
  - !ruby/object:Gem::Dependency
126
140
  name: rspec-its
127
141
  requirement: !ruby/object:Gem::Requirement
@@ -136,6 +150,20 @@ dependencies:
136
150
  - - '>='
137
151
  - !ruby/object:Gem::Version
138
152
  version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: benchmark-ips
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - '>='
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - '>='
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
139
167
  description: Serialize to hypermedia. HAL, JSON-API, etc.
140
168
  email:
141
169
  - arne@arnebrasseur.net
@@ -151,6 +179,7 @@ files:
151
179
  - LICENSE
152
180
  - README.md
153
181
  - Rakefile
182
+ - bench/bench.rb
154
183
  - examples/hal01.rb
155
184
  - examples/jsonapi01.rb
156
185
  - examples/jsonapi02.rb
@@ -182,6 +211,7 @@ files:
182
211
  - shaved_yak.gif
183
212
  - spec/acceptance/acceptance_spec.rb
184
213
  - spec/acceptance/models.rb
214
+ - spec/fixture_helpers.rb
185
215
  - spec/integration/map_to_resource_spec.rb
186
216
  - spec/json/confucius.hal.json
187
217
  - spec/json/confucius.json_api.json
@@ -230,9 +260,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
230
260
  version: '0'
231
261
  required_rubygems_version: !ruby/object:Gem::Requirement
232
262
  requirements:
233
- - - '>'
263
+ - - '>='
234
264
  - !ruby/object:Gem::Version
235
- version: 1.3.1
265
+ version: '0'
236
266
  requirements: []
237
267
  rubyforge_project:
238
268
  rubygems_version: 2.2.2
@@ -242,6 +272,7 @@ summary: Serialize to hypermedia. HAL, JSON-API, etc.
242
272
  test_files:
243
273
  - spec/acceptance/acceptance_spec.rb
244
274
  - spec/acceptance/models.rb
275
+ - spec/fixture_helpers.rb
245
276
  - spec/integration/map_to_resource_spec.rb
246
277
  - spec/json/confucius.hal.json
247
278
  - spec/json/confucius.json_api.json