yaks 0.0.0 → 0.1.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.
Files changed (67) hide show
  1. checksums.yaml +13 -5
  2. data/.gitignore +1 -0
  3. data/.travis.yml +23 -0
  4. data/Gemfile +1 -1
  5. data/Gemfile.lock +32 -6
  6. data/README.md +182 -80
  7. data/examples/hal01.rb +126 -0
  8. data/examples/jsonapi01.rb +68 -0
  9. data/examples/jsonapi02.rb +62 -0
  10. data/examples/jsonapi03.rb +86 -0
  11. data/lib/yaks.rb +55 -25
  12. data/lib/yaks/collection_mapper.rb +33 -0
  13. data/lib/yaks/collection_resource.rb +65 -0
  14. data/lib/yaks/default_policy.rb +13 -0
  15. data/lib/yaks/hal_serializer.rb +59 -0
  16. data/lib/yaks/json_api_serializer.rb +59 -0
  17. data/lib/yaks/link_lookup.rb +23 -0
  18. data/lib/yaks/mapper.rb +59 -0
  19. data/lib/yaks/mapper/association.rb +43 -0
  20. data/lib/yaks/mapper/class_methods.rb +36 -0
  21. data/lib/yaks/mapper/config.rb +79 -0
  22. data/lib/yaks/mapper/has_many.rb +15 -0
  23. data/lib/yaks/mapper/has_one.rb +10 -0
  24. data/lib/yaks/mapper/link.rb +74 -0
  25. data/lib/yaks/mapper/lookup.rb +19 -0
  26. data/lib/yaks/mapper/map_links.rb +17 -0
  27. data/lib/yaks/null_resource.rb +28 -0
  28. data/lib/yaks/primitivize.rb +34 -15
  29. data/lib/yaks/profile_registry.rb +60 -0
  30. data/lib/yaks/rel_registry.rb +20 -0
  31. data/lib/yaks/resource.rb +28 -0
  32. data/lib/yaks/resource/link.rb +21 -0
  33. data/lib/yaks/serializer.rb +11 -53
  34. data/lib/yaks/shared_options.rb +15 -0
  35. data/lib/yaks/util.rb +76 -5
  36. data/lib/yaks/version.rb +1 -1
  37. data/spec/integration/map_to_resource_spec.rb +30 -0
  38. data/spec/json/hal_plant_collection.json +34 -0
  39. data/spec/spec_helper.rb +10 -1
  40. data/spec/support/friends_mapper.rb +29 -0
  41. data/spec/support/pet_mapper.rb +5 -0
  42. data/spec/support/pet_peeve_mapper.rb +3 -0
  43. data/spec/support/serializers.rb +11 -11
  44. data/spec/support/shared_contexts.rb +47 -0
  45. data/spec/support/shorthands.rb +22 -0
  46. data/spec/yaks/collection_resource_spec.rb +9 -0
  47. data/spec/yaks/hal_serializer_spec.rb +9 -0
  48. data/spec/yaks/mapper/association_spec.rb +21 -0
  49. data/spec/yaks/mapper/class_methods_spec.rb +28 -0
  50. data/spec/yaks/mapper/config_spec.rb +77 -0
  51. data/spec/yaks/mapper/has_one_spec.rb +16 -0
  52. data/spec/yaks/mapper/link_spec.rb +42 -0
  53. data/spec/yaks/mapper/map_links_spec.rb +46 -0
  54. data/spec/yaks/mapper_spec.rb +47 -0
  55. data/spec/yaks/resource_spec.rb +23 -0
  56. data/yaks.gemspec +6 -3
  57. metadata +115 -27
  58. data/lib/yaks/dumper.rb +0 -23
  59. data/lib/yaks/fold_ams_compat.rb +0 -33
  60. data/lib/yaks/fold_json_api.rb +0 -61
  61. data/lib/yaks/serializable_association.rb +0 -21
  62. data/lib/yaks/serializable_collection.rb +0 -10
  63. data/lib/yaks/serializable_object.rb +0 -18
  64. data/lib/yaks/serializer/class_methods.rb +0 -76
  65. data/spec/integration_spec.rb +0 -57
  66. data/spec/yaks/json_api_folder_spec.rb +0 -63
  67. data/spec/yaks/serializer_spec.rb +0 -12
@@ -0,0 +1,28 @@
1
+ module Yaks
2
+ class NullResource
3
+ include Enumerable
4
+
5
+ def each
6
+ return to_enum unless block_given?
7
+ end
8
+
9
+ def attributes
10
+ Yaks::Hash()
11
+ end
12
+
13
+ def links
14
+ Yaks::List()
15
+ end
16
+
17
+ def subresources
18
+ Yaks::Hash()
19
+ end
20
+
21
+ def [](*)
22
+ end
23
+
24
+ def collection?
25
+ false
26
+ end
27
+ end
28
+ end
@@ -1,24 +1,43 @@
1
1
  module Yaks
2
2
  class Primitivize
3
+ include Concord.new(:object)
4
+
5
+ # TODO Global config, make this extensible in a per-instance way
6
+ MAPPINGS = {}
7
+
3
8
  def self.call(object)
4
- new.call(object)
9
+ new(object).call
10
+ end
11
+
12
+ def self.map(*types, &blk)
13
+ types.each do |type|
14
+ MAPPINGS[type] = blk
15
+ end
16
+ end
17
+
18
+ map String, TrueClass, FalseClass, NilClass, Numeric do
19
+ object
20
+ end
21
+
22
+ map Symbol do
23
+ object.to_s
24
+ end
25
+
26
+ map Hash, Hamster::Hash do
27
+ object.to_enum(:each).with_object({}) do |(key, value), output|
28
+ output[self.class.(key)] = self.class.(value)
29
+ end
30
+ end
31
+
32
+ map Enumerable, Hamster::Enumerable do
33
+ object.map(&self.class.method(:call)).to_a
5
34
  end
6
35
 
7
- def call(object)
8
- case object
9
- when String, TrueClass, FalseClass, NilClass, Numeric
10
- object
11
- when Symbol
12
- object.to_s
13
- when Hash, Hamster::Hash
14
- object.to_enum(:each).with_object({}) do |(key, value), output|
15
- output[self.(key)] = self.(value)
16
- end
17
- when Enumerable, Hamster::Enumerable
18
- object.map(&method(:call)).to_a
19
- else
20
- raise "don't know how to turn #{object.class} (#{object.inspect}) into a primitive"
36
+ def call
37
+ MAPPINGS.each do |pattern, block|
38
+ return instance_eval(&block) if pattern === object
21
39
  end
40
+ raise "don't know how to turn #{object.class} (#{object.inspect}) into a primitive"
22
41
  end
23
42
  end
24
43
  end
@@ -0,0 +1,60 @@
1
+ # -*- coding: utf-8 -*-
2
+ module Yaks
3
+ # RFC6906 The "profile" link relation http://tools.ietf.org/search/rfc6906
4
+ class ProfileRegistry
5
+ class << self
6
+ include Util
7
+
8
+ def create(&blk)
9
+ blk ||= ->{}
10
+ Class.new(self).tap(&σ(:instance_eval, &blk)).new
11
+ end
12
+
13
+ def profile(type, uri)
14
+ profiles {|reg| reg.put(type, uri)}
15
+ end
16
+
17
+ def profiles
18
+ @profiles ||= Yaks::Hash()
19
+ @profiles = yield(@profiles) if block_given?
20
+ @profiles
21
+ end
22
+
23
+ def inherited(child)
24
+ child.profiles { @profiles }
25
+ end
26
+ end
27
+
28
+ def find_by_type(type)
29
+ self.class.profiles[type]
30
+ end
31
+
32
+ def find_by_uri(by_uri)
33
+ self.class.profiles.detect {|type, uri| uri == by_uri}.first
34
+ end
35
+ end
36
+
37
+ class NullProfileRegistry
38
+ def find_by_type(type)
39
+ type.to_s
40
+ end
41
+
42
+ def find_by_uri(uri)
43
+ uri.to_sym
44
+ end
45
+ end
46
+
47
+ class TemplateProfileRegistry
48
+ def initialize(template)
49
+ @template = URITemplate.new(template)
50
+ end
51
+
52
+ def find_by_type(type)
53
+ @template.expand(type: type)
54
+ end
55
+
56
+ def find_by_uri(uri)
57
+ @template.extract(uri).fetch(:type) { uri }.to_sym
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,20 @@
1
+ module Yaks
2
+ class RelRegistry
3
+ end
4
+
5
+ class TemplateRelRegistry < RelRegistry
6
+ def initialize(template)
7
+ @template = URITemplate.new(template)
8
+ end
9
+
10
+ def lookup(source, destination)
11
+ @template.expand(:src => source, :dest => destination)
12
+ end
13
+ end
14
+
15
+ class NullRelRegistry < TemplateRelRegistry
16
+ def initialize
17
+ super('rel:src={src}&dest={dest}')
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,28 @@
1
+ module Yaks
2
+ class Resource
3
+ include Equalizer.new(:links, :attributes, :subresources)
4
+ include Enumerable, LinkLookup
5
+
6
+ attr_reader :attributes, :links, :subresources
7
+
8
+ def initialize(attributes, links, subresources)
9
+ @attributes = Yaks::Hash(attributes)
10
+ @links = Yaks::List(links)
11
+ @subresources = Yaks::Hash(subresources)
12
+ end
13
+
14
+ def [](attr)
15
+ attributes[attr]
16
+ end
17
+
18
+ def each
19
+ return to_enum unless block_given?
20
+ yield self
21
+ end
22
+
23
+ def collection?
24
+ false
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,21 @@
1
+ module Yaks
2
+ class Resource
3
+ class Link
4
+ include Equalizer.new(:rel, :uri, :options)
5
+
6
+ attr_reader :rel, :uri, :options
7
+
8
+ def initialize(rel, uri, options)
9
+ @rel, @uri, @options = rel, uri, options
10
+ end
11
+
12
+ def name
13
+ options[:name]
14
+ end
15
+
16
+ def templated?
17
+ options.fetch(:templated) { false }
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,64 +1,22 @@
1
1
  module Yaks
2
2
  class Serializer
3
- include Yaks
4
- extend ClassMethods
3
+ extend Forwardable
4
+ include Util, SharedOptions
5
5
 
6
- attr_accessor :object
7
- attr_reader :serializer_lookup, :root_key
6
+ attr_reader :resource, :options
7
+ def_delegators :resource, :links, :attributes, :subresources
8
8
 
9
- def initialize(options = {})
10
- @serializer_lookup = options.fetch(:serializer_lookup) { Yaks.default_serializer_lookup }
11
- @root_key = options.fetch(:root_key) { self.class._root_key }
12
- end
13
-
14
- def identity_key
15
- self.class._identity_key
16
- end
17
-
18
- def attributes
19
- self.class._attributes
20
- end
21
-
22
- def associations
23
- self.class._associations
24
- end
25
-
26
- def filter(attributes)
27
- attributes
28
- end
9
+ protected :resource, :links, :attributes, :subresources, :options
29
10
 
30
- def serializable_collection(enumerable)
31
- SerializableCollection.new(root_key, identity_key, enumerable.map(&method(:serializable_object)))
11
+ def initialize(resource, options = {})
12
+ @resource = resource
13
+ @options = YAKS_DEFAULT_OPTIONS.merge(options)
32
14
  end
33
15
 
34
- def serializable_object(object)
35
- self.object = object
36
- SerializableObject.new(
37
- serializable_attributes(object),
38
- serializable_associations(object)
39
- )
16
+ def profile_name
17
+ (profile = resource.profile) &&
18
+ profile_registry.find_by_uri(profile)
40
19
  end
41
20
 
42
- def serializable_attributes(object)
43
- Hash(filter(attributes).map {|attr| [attr, send(attr)] })
44
- end
45
-
46
- def serializable_associations(object)
47
- Hamster.enumerate(filter(associations.map(&:last)).each).map do |name|
48
- type = associations.detect {|type, n| name == n }.first
49
- if type == :has_one
50
- obj = send(name)
51
- serializer = serializer_lookup.(obj).new
52
- objects = List(serializer.serializable_object(obj))
53
- else
54
- serializer = nil
55
- objects = Hamster.enumerate(send(name).each).map do |obj|
56
- serializer ||= serializer_lookup.(obj).new
57
- serializer.serializable_object(obj)
58
- end
59
- end
60
- SerializableAssociation.new( SerializableCollection.new(name, :id, objects), type == :has_one )
61
- end
62
- end
63
21
  end
64
22
  end
@@ -0,0 +1,15 @@
1
+ module Yaks
2
+ module SharedOptions
3
+ def policy
4
+ options[:policy]
5
+ end
6
+
7
+ def profile_registry
8
+ options[:profile_registry]
9
+ end
10
+
11
+ def rel_registry
12
+ options[:rel_registry]
13
+ end
14
+ end
15
+ end
data/lib/yaks/util.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # -*- coding: utf-8 -*-
1
2
  module Yaks
2
3
  module Util
3
4
  extend self
@@ -7,14 +8,84 @@ module Yaks
7
8
 
8
9
  def underscore(str)
9
10
  str.gsub(/::/, '/')
10
- .gsub(/(?<!^)([A-Z])(?=[a-z$])|(?<=[a-z])([A-Z])/, '_\1\2')
11
- .tr("-", "_")
12
- .downcase!
11
+ .gsub(/(?<!^|\/)([A-Z])(?=[a-z$])|(?<=[a-z])([A-Z])/, '_\1\2')
12
+ .tr("-", "_")
13
+ .downcase
13
14
  end
14
15
 
15
16
  def camelize(str)
16
- str.gsub(/\/(.?)/) { "::#{ $1.upcase }" }
17
- .gsub!(/(?:^|_)(.)/) { $1.upcase }
17
+ str.gsub(/\/(.?)/) { "::#{ $1.upcase }" }
18
+ .gsub!(/(?:^|_)(.)/) { $1.upcase }
19
+ end
20
+
21
+ def List(*args)
22
+ Hamster.list(*args)
23
+ end
24
+
25
+ def Hash(*args)
26
+ Hamster.hash(*args)
27
+ end
28
+
29
+ def Set(*args)
30
+ Hamster.set(*args)
31
+ end
32
+
33
+ # Turn what is maybe a Proc into its result (or itself)
34
+ #
35
+ # When input can be either a value or a proc that returns a value,
36
+ # this conversion function can be used to resolve the thing to a
37
+ # value.
38
+ #
39
+ # The proc can be evaluated (instance_evaled) in a certain context,
40
+ # or evaluated as a closure.
41
+ #
42
+ # @param [Object|Proc] maybe_proc
43
+ # A proc or a plain value
44
+ # @param [Object] context
45
+ # (optional) A context used to instance_eval the proc
46
+ def Resolve(maybe_proc, context = nil)
47
+ if maybe_proc.respond_to?(:to_proc)
48
+ if context
49
+ if maybe_proc.to_proc.arity > 0
50
+ context.instance_eval(&maybe_proc)
51
+ else
52
+ # In case it's a lambda with zero arity instance_eval fails
53
+ context.instance_exec(&maybe_proc)
54
+ end
55
+ else
56
+ maybe_proc.()
57
+ end
58
+ else
59
+ maybe_proc
60
+ end
61
+ end
62
+
63
+ def curry_method(name)
64
+ method(name).to_proc.curry
65
+ end
66
+ alias μ curry_method
67
+
68
+ def identity_function
69
+ ->(x) {x}
70
+ end
71
+ alias ι identity_function
72
+
73
+ def juxt(*procs)
74
+ ->(*args) { procs.map &σ(:call, *args) }
75
+ end
76
+
77
+ def curry_symbol(symbol, *args, &blk)
78
+ ->(obj) { obj.method(symbol).to_proc.curry.(*args, &blk) }
79
+ end
80
+ alias σ curry_symbol
81
+ alias send_with_args curry_symbol
82
+
83
+ def extract_options(args)
84
+ if args.last.is_a? Hash
85
+ [args.take(args.count-1), args.last]
86
+ else
87
+ [args, {}]
88
+ end
18
89
  end
19
90
 
20
91
  end
data/lib/yaks/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Yaks
2
- VERSION = '0.0.0'
2
+ VERSION = '0.1.0'
3
3
  end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Mapping domain models to Resource objects' do
4
+ include_context 'fixtures'
5
+ include_context 'shorthands'
6
+
7
+ subject { mapper.to_resource }
8
+ let(:mapper) { FriendMapper.new(john) }
9
+
10
+
11
+ it { should be_a Yaks::Resource }
12
+ its(:attributes) { should eq Yaks::Hash(id: 1, name: 'john') }
13
+ its(:links) { should eq Yaks::List(
14
+ resource_link[:profile, 'friend'],
15
+ resource_link[:copyright, '/api/copyright/2024']
16
+ )}
17
+
18
+ its(:subresources) {
19
+ should eq Yaks::Hash(
20
+ "rel:src=friend&dest=pet_peeve" => resource[{id: 4, type: 'parsing with regexps'}, [resource_link[:profile, 'pet_peeve']]],
21
+ "rel:src=friend&dest=pets" => Yaks::CollectionResource.new(
22
+ [resource_link[:profile, 'pet']],
23
+ [
24
+ resource[{:id => 2, :species => "dog", :name => "boingboing"}, [resource_link[:profile, 'pet']]],
25
+ resource[{:id => 3, :species => "cat", :name => "wassup"}, [resource_link[:profile, 'pet']]]
26
+ ]
27
+ )
28
+ )
29
+ }
30
+ end