yaks 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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