kartograph 0.0.1 → 0.0.2

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: b3a8357794a9b9db82e82cfc2f40c34096392c54
4
- data.tar.gz: ea222705a629a01c14c74fdc71ff341c8fd38f96
3
+ metadata.gz: d0486e484ee7a91e233645567afcf28f2ff25d6b
4
+ data.tar.gz: 0b6232dbd15c7a27a90f89b00652abdc4912a789
5
5
  SHA512:
6
- metadata.gz: 9290c302a911fedd70604881bbd338c6f23e7ace3e3ca93d85fbb7cabbe83af1952636b8291c0a5013113b2878b8a5dba9d441eb01b6adc7990ea4bcd1da090b
7
- data.tar.gz: cf8c415714bc1dc47b585af1289d6b713640dff5d8eb0921ee5b2e6300e45d5f25c0885e327fed34dd9152c6ca0ceb53780c91f8188b1239b67a6ec7af711f07
6
+ metadata.gz: 389d7c025aad932d0f22e2d5b3fa1d03d907bd5af1cb06d916549af5caadfc10c47ee975ef3a0d5c4c9de6ea093bfc039bf2b9dccd8e34345ca5a9f5a6c0d994
7
+ data.tar.gz: aeff8daa983de66a69c76a7c73e995d91ce28b977c2749c7eba257ecda38294ba6d4653eb0f16c03f8bcac860003cd4b072dc44cecd1e25d1cb265d7a1f6092a
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/README.md CHANGED
@@ -18,7 +18,98 @@ Or install it yourself as:
18
18
 
19
19
  ## Usage
20
20
 
21
- TODO: Write usage instructions here
21
+ Kartograph makes it easy to generate and convert JSON. It's intention is to be used for API clients.
22
+
23
+ For example, if you have an object that you would like to convert to JSON for a create request to an API. You would have something similar to this:
24
+
25
+ ```ruby
26
+ class UserMapping
27
+ include Kartograph::DSL
28
+
29
+ kartograph do
30
+ mapping User # The object we're mapping
31
+
32
+ property :name, scopes: [:create, :update]
33
+ property :id, scopes: [:read]
34
+ end
35
+ end
36
+
37
+ user = User.new(name: 'Bobby Tables')
38
+ json_for_create = UserMapping.representation_for(:create, user)
39
+ ```
40
+
41
+ Some API's will give you the created resource back as JSON as well on a successful create. For that, you may do something like this:
42
+
43
+ ```ruby
44
+ response = HTTPClient.post("http://something.com/api/users", body: json_for_create)
45
+ created_user = UserMapping.extract_single(response.body, :read)
46
+ ```
47
+
48
+ Most API's will have a way of retrieving an entire resource collection. For this you can instruct Kartograph to convert a collection.
49
+
50
+ ```ruby
51
+ response = HTTPClient.get("http://something.com/api/users")
52
+ users = UserMapping.extract_collection(response.body, :read)
53
+ # => [ User, User, User ]
54
+ ```
55
+
56
+ ### Getting Harder
57
+
58
+ Sometimes resources will nest other properties under a key. Kartograph can handle this as well.
59
+
60
+ ```ruby
61
+ class UserMapping
62
+ include Kartograph::DSL
63
+
64
+ kartograph do
65
+ mapping User # The object we're mapping
66
+
67
+ property :name, scopes: [:read]
68
+
69
+ property :comments do
70
+ mapping Comment # The nested object we're mapping
71
+
72
+ property :text, scopes: [:read]
73
+ property :author, scopes: [:read]
74
+ end
75
+ end
76
+ end
77
+ ```
78
+
79
+ Just like the previous examples, when you serialize this. It will include the comment block for the scope defined.
80
+
81
+ ### Root Keys
82
+
83
+ Kartograph can also handle the event of root keys in response bodies. For example, if you receive a response with:
84
+
85
+ ```json
86
+ { "user": { "id": 123 } }
87
+ ```
88
+
89
+ You could define a mapping like this:
90
+
91
+
92
+ ```ruby
93
+ class UserMapping
94
+ include Kartograph::DSL
95
+
96
+ kartograph do
97
+ mapping User
98
+ root_key singular: 'user', plural: 'users' scopes: [:read]
99
+ property :id, scopes: [:read]
100
+ end
101
+ end
102
+ ```
103
+
104
+ This means that when you call the same thing:
105
+
106
+ ```ruby
107
+ response = HTTPClient.get("http://something.com/api/users")
108
+ users = UserMapping.extract_collection(response.body, :read)
109
+ ```
110
+
111
+ It will look for the root key before trying to deserialize the JSON response.
112
+ The advantage of this is it will only use the root key if there is a scope defined for it.
22
113
 
23
114
  ## Contributing
24
115
 
@@ -0,0 +1,54 @@
1
+ require 'kartograph'
2
+
3
+ json = '{
4
+ "domains": [
5
+ {
6
+ "name": "example.com",
7
+ "ttl": 1800,
8
+ "zone_file": "Example zone file text..."
9
+ }
10
+ ],
11
+ "meta": {
12
+ "total": 1
13
+ }
14
+ }'
15
+
16
+ class Domain
17
+ attr_accessor :name, :ttl, :zone_file
18
+ end
19
+
20
+ class MetaInformation
21
+ attr_accessor :total
22
+ end
23
+
24
+ class DomainMapper
25
+ include Kartograph::DSL
26
+
27
+ kartograph do
28
+ mapping Domain
29
+ root_key singular: 'domain', plural: 'domains', scopes: [:read]
30
+
31
+ property :name, scopes: [:read]
32
+ property :ttl, scopes: [:read]
33
+ property :zone_file, scopes: [:read]
34
+ end
35
+ end
36
+
37
+ class MetaInformationMapper
38
+ include Kartograph::DSL
39
+
40
+ kartograph do
41
+ mapping MetaInformation
42
+
43
+ root_key singular: 'meta', scopes: [:read]
44
+ property :total, scopes: [:read]
45
+ end
46
+ end
47
+
48
+ domains = DomainMapper.extract_collection(json, :read)
49
+ meta = MetaInformationMapper.extract_single(json, :read)
50
+
51
+ puts "Total Domains: #{domains.size}"
52
+ puts domains.map(&:name)
53
+ puts
54
+ puts "Total pages: #{meta.total}"
@@ -0,0 +1,24 @@
1
+ class Domain
2
+ attr_accessor :name, :ttl, :zone_file
3
+ end
4
+
5
+ class DomainMapper
6
+ include Kartograph::DSL
7
+
8
+ kartograph do
9
+ mapping Domain
10
+ root_key singular: 'domain', plural: 'domains', scopes: [:read]
11
+
12
+ property :name, scopes: [:read, :create]
13
+ property :ttl, scopes: [:read, :create]
14
+ property :zone_file, scopes: [:read]
15
+ end
16
+ end
17
+
18
+ domain = Domain.new
19
+ domain.name = 'example.com'
20
+ domain.ttl = 3600
21
+ domain.zone_file = "this wont be represented for create"
22
+
23
+ puts DomainMapper.representation_for(:create, domain)
24
+ #=> {"name":"example.com","ttl":3600}
@@ -21,4 +21,5 @@ Gem::Specification.new do |spec|
21
21
  spec.add_development_dependency "bundler", "~> 1.6"
22
22
  spec.add_development_dependency "rake"
23
23
  spec.add_development_dependency "rspec", "~> 3.0.0"
24
+ spec.add_development_dependency "pry"
24
25
  end
@@ -1,5 +1,13 @@
1
1
  require "kartograph/version"
2
+ require 'json'
2
3
 
3
4
  module Kartograph
4
- # Your code goes here...
5
+ autoload :DSL, 'kartograph/dsl'
6
+ autoload :Map, 'kartograph/map'
7
+ autoload :Property, 'kartograph/property'
8
+ autoload :PropertyCollection, 'kartograph/property_collection'
9
+ autoload :RootKey, 'kartograph/root_key'
10
+
11
+ autoload :Artist, 'kartograph/artist'
12
+ autoload :Sculptor, 'kartograph/sculptor'
5
13
  end
@@ -0,0 +1,22 @@
1
+ module Kartograph
2
+ class Artist
3
+ attr_reader :object, :map
4
+
5
+ def initialize(object, map)
6
+ @object = object
7
+ @map = map
8
+ end
9
+
10
+ def properties
11
+ map.properties
12
+ end
13
+
14
+ def draw(scope = nil)
15
+ scoped_properties = scope ? properties.filter_by_scope(scope) : properties
16
+ scoped_properties.each_with_object({}) do |property, mapped|
17
+ raise ArgumentError, "#{object} does not respond to #{property.name}, so we can't map it" unless object.respond_to?(property.name)
18
+ mapped[property.name] = property.value_for(object, scope)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,58 @@
1
+ module Kartograph
2
+ module DSL
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ def kartograph(&block)
9
+ @kartograph_map ||= Map.new
10
+
11
+ block.arity > 0 ? block.call(@kartograph_map) : @kartograph_map.instance_eval(&block)
12
+ end
13
+
14
+ def representation_for(scope, object, dumper = JSON)
15
+ drawed = Artist.new(object, @kartograph_map).draw(scope)
16
+
17
+ retrieve_root_key(scope, :singular) do |root_key|
18
+ # Reassign drawed if a root key exists
19
+ drawed = { root_key => drawed }
20
+ end
21
+
22
+ dumper.dump(drawed)
23
+ end
24
+
25
+ def extract_single(content, scope, loader = JSON)
26
+ loaded = loader.load(content)
27
+
28
+ retrieve_root_key(scope, :singular) do |root_key|
29
+ # Reassign loaded if a root key exists
30
+ loaded = loaded[root_key]
31
+ end
32
+
33
+ Sculptor.new(loaded, @kartograph_map).sculpt(scope)
34
+ end
35
+
36
+ def extract_collection(content, scope, loader = JSON)
37
+ loaded = loader.load(content)
38
+
39
+ retrieve_root_key(scope, :plural) do |root_key|
40
+ # Reassign loaded if a root key exists
41
+ loaded = loaded[root_key]
42
+ end
43
+
44
+ loaded.map do |object|
45
+ Sculptor.new(object, @kartograph_map).sculpt(scope)
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def retrieve_root_key(scope, type, &block)
52
+ if root_key = @kartograph_map.root_key_for(scope, type)
53
+ yield root_key
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,32 @@
1
+ module Kartograph
2
+ class Map
3
+ def property(*args, &block)
4
+ properties << Property.new(*args, &block)
5
+ end
6
+
7
+ def properties
8
+ @properties ||= PropertyCollection.new
9
+ end
10
+
11
+ def root_keys
12
+ @root_keys ||= []
13
+ end
14
+
15
+ def mapping(klass = nil)
16
+ @mapping = klass if klass
17
+ @mapping
18
+ end
19
+
20
+ def root_key(options)
21
+ root_keys << RootKey.new(options)
22
+ end
23
+
24
+ def root_key_for(scope, type)
25
+ return unless %i(singular plural).include?(type)
26
+
27
+ if (root_key = root_keys.select {|rk| rk.scopes.include?(scope) }[0])
28
+ root_key.send(type)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,43 @@
1
+ module Kartograph
2
+ class Property
3
+ attr_reader :name, :options, :map
4
+
5
+ def initialize(name, options = {}, &block)
6
+ @name = name
7
+ @options = options
8
+
9
+ if block_given?
10
+ @map ||= Map.new
11
+ block.arity > 0 ? block.call(map) : map.instance_eval(&block)
12
+ end
13
+ end
14
+
15
+ def value_for(object, scope = nil)
16
+ value = object.send(name)
17
+ map ? artist_value(value, scope) : value
18
+ end
19
+
20
+ def value_from(object, scope = nil)
21
+ value = object.has_key?(name) ? object[name] : object[name.to_s]
22
+ map ? sculpt_value(value, scope) : value
23
+ end
24
+
25
+ def scopes
26
+ options[:scopes] || []
27
+ end
28
+
29
+ def plural?
30
+ !!options[:plural]
31
+ end
32
+
33
+ private
34
+
35
+ def sculpt_value(value, scope)
36
+ plural? ? value.map {|v| Sculptor.new(v, map).sculpt(scope) } : Sculptor.new(value, map).sculpt(scope)
37
+ end
38
+
39
+ def artist_value(value, scope)
40
+ plural? ? value.map {|v| Artist.new(v, map).draw(scope) } : Artist.new(value, map).draw(scope)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,20 @@
1
+ require 'forwardable'
2
+
3
+ module Kartograph
4
+ class PropertyCollection
5
+ # Make this collection quack like an array
6
+ # http://words.steveklabnik.com/beware-subclassing-ruby-core-classes
7
+ extend Forwardable
8
+ def_delegators :@collection, *(Array.instance_methods - Object.instance_methods)
9
+
10
+ def initialize(*)
11
+ @collection = []
12
+ end
13
+
14
+ def filter_by_scope(scope)
15
+ select do |property|
16
+ property.scopes.include?(scope)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,19 @@
1
+ module Kartograph
2
+ class RootKey
3
+ attr_reader :options
4
+
5
+ def initialize(options = {})
6
+ @options = options
7
+ end
8
+
9
+ def scopes
10
+ options[:scopes] || []
11
+ end
12
+
13
+ %i(singular plural).each do |method|
14
+ define_method(method) do
15
+ options[method]
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,26 @@
1
+ module Kartograph
2
+ class Sculptor
3
+ attr_reader :object, :map
4
+
5
+ def initialize(object, map)
6
+ @object = object
7
+ @map = map
8
+ end
9
+
10
+ def properties
11
+ map.properties
12
+ end
13
+
14
+ def sculpt(scope = nil)
15
+ # Initializing the object we're coercing so we can set attributes on it
16
+ coerced = map.mapping.new
17
+ scoped_properties = scope ? properties.filter_by_scope(scope) : properties
18
+
19
+ scoped_properties.each_with_object(coerced) do |property, mutable|
20
+ setter_method = "#{property.name}="
21
+ value = property.value_from(object, scope)
22
+ mutable.send(setter_method, value)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,3 +1,3 @@
1
1
  module Kartograph
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -0,0 +1,70 @@
1
+ require 'spec_helper'
2
+
3
+ describe Kartograph::Artist do
4
+ let(:map) { Kartograph::Map.new }
5
+ let(:properties) { map.properties }
6
+
7
+ describe '#initialize' do
8
+ it 'initializes with an object and a map' do
9
+ object = double('object', name: 'hello')
10
+ properties << Kartograph::Property.new(:name)
11
+
12
+ artist = Kartograph::Artist.new(object, map)
13
+
14
+ expect(artist.object).to be(object)
15
+ expect(artist.map).to be(map)
16
+ end
17
+ end
18
+
19
+ describe '#draw' do
20
+ it 'returns a hash of mapped properties' do
21
+ object = double('object', hello: 'world')
22
+ properties << Kartograph::Property.new(:hello)
23
+
24
+ artist = Kartograph::Artist.new(object, map)
25
+ masterpiece = artist.draw
26
+
27
+ expect(masterpiece).to include(hello: 'world')
28
+ end
29
+
30
+ it 'raises for a property that the object does not have' do
31
+ object = double('object')
32
+ properties << Kartograph::Property.new(:bunk)
33
+ artist = Kartograph::Artist.new(object, map)
34
+
35
+ expect { artist.draw }.to raise_error(ArgumentError).with_message("#{object} does not respond to bunk, so we can't map it")
36
+ end
37
+
38
+ context 'for filtered drawing' do
39
+ it 'only returns the scoped properties' do
40
+ object = double('object', hello: 'world', foo: 'bar')
41
+ properties << Kartograph::Property.new(:hello, scopes: [:create, :read])
42
+ properties << Kartograph::Property.new(:foo, scopes: [:create])
43
+
44
+ artist = Kartograph::Artist.new(object, map)
45
+ masterpiece = artist.draw(:read)
46
+
47
+ expect(masterpiece).to eq(hello: 'world')
48
+ end
49
+
50
+ context 'on nested properties' do
51
+ it 'only returns the nested properties within the same scope' do
52
+ child = double('child', hello: 'world', foo: 'bunk')
53
+ object = double('object', child: child)
54
+
55
+ root_property = Kartograph::Property.new(:child, scopes: [:create, :read]) do
56
+ property :hello, scopes: [:create]
57
+ property :foo, scopes: [:read]
58
+ end
59
+
60
+ properties << root_property
61
+
62
+ artist = Kartograph::Artist.new(object, map)
63
+ masterpiece = artist.draw(:read)
64
+
65
+ expect(masterpiece).to eq(child: { foo: child.foo })
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,125 @@
1
+ require 'spec_helper'
2
+
3
+ describe Kartograph::DSL do
4
+ describe 'Inclusion' do
5
+ it 'gives you a class method for .kartograph' do
6
+ klass = Class.new
7
+ expect { klass.send(:include, described_class) }.to change { klass.respond_to?(:kartograph) }.to(true)
8
+ end
9
+ end
10
+
11
+ describe '.kartograph' do
12
+ subject(:mapping) { Class.new { include Kartograph::DSL } }
13
+
14
+ it 'yields a Kartograph::Map instance' do
15
+ expect {|b| mapping.kartograph(&b) }.to yield_with_args(instance_of(Kartograph::Map))
16
+ end
17
+ end
18
+
19
+ describe '.representation_for' do
20
+ include_context 'DSL Objects'
21
+
22
+ it 'returns the JSON representation for an object' do
23
+ json = mapped.representation_for(:create, object)
24
+ expect(json).to eq(
25
+ { name: object.name }.to_json
26
+ )
27
+ end
28
+
29
+ context 'with a root key for the scope' do
30
+ it 'returns the json with the root key' do
31
+ mapped.kartograph do
32
+ root_key singular: 'user', scopes: [:create]
33
+ end
34
+ json = mapped.representation_for(:create, object)
35
+
36
+ expect(json).to eq(
37
+ {
38
+ user: {
39
+ name: object.name
40
+ }
41
+ }.to_json
42
+ )
43
+ end
44
+ end
45
+ end
46
+
47
+ describe '.extract_single' do
48
+ include_context 'DSL Objects'
49
+ let(:json) do
50
+ { id: 1337, name: 'Paul the octopus' }
51
+ end
52
+
53
+ it 'returns a populated object from a JSON representation' do
54
+ extracted = mapped.extract_single(json.to_json, :read)
55
+
56
+ expect(extracted.id).to eq(1337)
57
+ expect(extracted.name).to eq('Paul the octopus')
58
+ end
59
+
60
+ context 'with a root key in the JSON' do
61
+ let(:json) { { test: super() } }
62
+
63
+ before do
64
+ mapped.kartograph do
65
+ root_key singular: 'test', scopes: [:read]
66
+ end
67
+ end
68
+
69
+ it 'traverses into the key and pulls the object from there' do
70
+ extracted = mapped.extract_single(json.to_json, :read)
71
+
72
+ expect(extracted.id).to eq(1337)
73
+ expect(extracted.name).to eq('Paul the octopus')
74
+ end
75
+ end
76
+ end
77
+
78
+ describe '.extract_collection' do
79
+ include_context 'DSL Objects'
80
+ let(:json) do
81
+ [
82
+ { id: 1337, name: 'Paul the octopus' },
83
+ { id: 1338, name: 'Hank the octopus' }
84
+ ]
85
+ end
86
+
87
+ it 'returns a collection of objects from the json' do
88
+ extracted = mapped.extract_collection(json.to_json, :read)
89
+
90
+ expect(extracted.size).to be(2)
91
+ expect(extracted).to all(be_kind_of(DummyUser))
92
+
93
+ expect(extracted[0].id).to eq(json[0][:id])
94
+ expect(extracted[0].name).to eq(json[0][:name])
95
+
96
+ expect(extracted[1].id).to eq(json[1][:id])
97
+ expect(extracted[1].name).to eq(json[1][:name])
98
+ end
99
+
100
+ context 'for a nested key' do
101
+ let(:json) { { users: super() } }
102
+
103
+ before do
104
+ mapped.kartograph do
105
+ root_key plural: 'users', scopes: [:read]
106
+ end
107
+ end
108
+
109
+ it 'returns a collection of objects from the json' do
110
+ extracted = mapped.extract_collection(json.to_json, :read)
111
+
112
+ expect(extracted.size).to be(2)
113
+ expect(extracted).to all(be_kind_of(DummyUser))
114
+
115
+ scoped = json[:users]
116
+
117
+ expect(extracted[0].id).to eq(scoped[0][:id])
118
+ expect(extracted[0].name).to eq(scoped[0][:name])
119
+
120
+ expect(extracted[1].id).to eq(scoped[1][:id])
121
+ expect(extracted[1].name).to eq(scoped[1][:name])
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ describe Kartograph::Map do
4
+ subject(:map) { Kartograph::Map.new }
5
+
6
+ describe '#property' do
7
+ it 'adds a property to the map' do
8
+ map.property :attribute_name, scopes: [:read]
9
+ expect(map.properties.size).to be(1)
10
+ expect(map.properties.first).to be_kind_of(Kartograph::Property)
11
+ end
12
+ end
13
+
14
+ describe '#properties' do
15
+ it 'returns a PropertyCollection object' do
16
+ properties = map.properties
17
+ expect(properties).to be_kind_of(Kartograph::PropertyCollection)
18
+ end
19
+ end
20
+
21
+ describe '#mapping' do
22
+ it 'sets the class we\'re mapping' do
23
+ map.mapping Class
24
+ expect(map.mapping).to be(Class)
25
+ end
26
+ end
27
+
28
+ describe '#root_key' do
29
+ it 'sets the root keys' do
30
+ map.root_key singlular: 'test', scopes: [:read]
31
+ map.root_key singlular: 'test', scopes: [:create]
32
+
33
+ expect(map.root_keys.size).to be(2)
34
+ expect(map.root_keys).to all(be_kind_of(Kartograph::RootKey))
35
+ end
36
+ end
37
+
38
+ describe '#root_key_for' do
39
+ it 'returns the first root key for the scope and type' do
40
+ map.root_key singular: 'test', scopes: [:read]
41
+ key = map.root_key_for(:read, :singular)
42
+
43
+ expect(key).to eq('test')
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ describe Kartograph::PropertyCollection do
4
+ describe '#filter_by_scope' do
5
+ it 'only returns properties with a certain scope attached' do
6
+ collection = Kartograph::PropertyCollection.new
7
+ collection << Kartograph::Property.new(:hello, scopes: [:read, :create])
8
+ collection << Kartograph::Property.new(:id, scopes: [:read])
9
+
10
+ filtered = collection.filter_by_scope(:create)
11
+ expect(filtered.size).to be(1)
12
+ expect(filtered.first.name).to eq(:hello)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,155 @@
1
+ require 'spec_helper'
2
+
3
+ describe Kartograph::Property do
4
+ describe '#initialize' do
5
+ it 'initializes with an attribute name and options' do
6
+ name = :hey
7
+ options = { lol: 'whut' }
8
+
9
+ property = Kartograph::Property.new(name, options)
10
+ expect(property.name).to eq(name)
11
+ expect(property.options).to eq(options)
12
+ end
13
+
14
+ context 'with a block' do
15
+ it 'yields a map instance for the property' do
16
+ expect {|b| Kartograph::Property.new(:hello, &b) }.to yield_with_args(instance_of(Kartograph::Map))
17
+ end
18
+ end
19
+ end
20
+
21
+ describe '#scopes' do
22
+ it 'returns the scopes that the property is for' do
23
+ property = Kartograph::Property.new(:name, scopes: [:read, :create])
24
+ expect(property.scopes).to include(:read, :create)
25
+ end
26
+
27
+ it 'returns an empty array when no scopes are provided' do
28
+ property = Kartograph::Property.new(:name)
29
+ expect(property.scopes).to eq( [] )
30
+ end
31
+ end
32
+
33
+ describe '#plural?' do
34
+ it 'returns true when set to plural' do
35
+ property = Kartograph::Property.new(:name, scopes: [:read], plural: true)
36
+ expect(property).to be_plural
37
+ end
38
+
39
+ it 'returns false when not set' do
40
+ property = Kartograph::Property.new(:name, scopes: [:read])
41
+ expect(property).to_not be_plural
42
+ end
43
+ end
44
+
45
+ describe '#value_for' do
46
+ it 'returns the value when passed an object' do
47
+ property = Kartograph::Property.new(:sammy)
48
+ object = double('object', sammy: 'cephalopod')
49
+
50
+ expect(property.value_for(object)).to eq('cephalopod')
51
+ end
52
+
53
+ context 'for a nested property set' do
54
+ it 'returns nested properties' do
55
+ top_level = Kartograph::Property.new(:sammy) do
56
+ property :cephalopod
57
+ end
58
+
59
+ child = double('child', cephalopod: 'I will ink you')
60
+ root = double('root', sammy: child)
61
+
62
+ expect(top_level.value_for(root)).to eq(cephalopod: child.cephalopod)
63
+ end
64
+
65
+ context 'when it is plural' do
66
+ it 'returns a pluralized representation' do
67
+ top_level = Kartograph::Property.new(:sammy, plural: true) do
68
+ property :cephalopod
69
+ end
70
+
71
+ child1 = double('child', cephalopod: 'I will ink you')
72
+ child2 = double('child', cephalopod: 'I wont because im cool')
73
+
74
+ root = double('root', sammy: [child1, child2])
75
+
76
+ expect(top_level.value_for(root)).to eq([
77
+ { cephalopod: child1.cephalopod },
78
+ { cephalopod: child2.cephalopod }
79
+ ])
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ describe '#value_from' do
86
+ let(:hash) { { hello: 'world' } }
87
+
88
+ it 'retrieves the value from a hash for the property' do
89
+ property = Kartograph::Property.new(:hello)
90
+ expect(property.value_from(hash)).to eq('world')
91
+ end
92
+
93
+ context 'string and symbol agnostic' do
94
+ let(:hash) { { 'hello' => 'world' } }
95
+
96
+ it 'retrieves the value from a hash for the property' do
97
+ property = Kartograph::Property.new(:hello)
98
+ expect(property.value_from(hash)).to eq('world')
99
+ end
100
+ end
101
+
102
+ context 'for a nested property set' do
103
+ it 'returns an object with the properties set on it' do
104
+ dummy_class = Struct.new(:id, :name)
105
+
106
+ nested_property = Kartograph::Property.new(:hello) do
107
+ mapping dummy_class
108
+ property :id
109
+ property :name
110
+ end
111
+
112
+ hash = { hello: {
113
+ 'id' => 555,
114
+ 'name' => 'Buckstar'
115
+ }}
116
+
117
+ value = nested_property.value_from(hash)
118
+ expect(value).to be_kind_of(dummy_class)
119
+ expect(value.id).to eq(hash[:hello]['id'])
120
+ expect(value.name).to eq(hash[:hello]['name'])
121
+ end
122
+
123
+ it 'returns a collection of objects when set to plural' do
124
+ dummy_class = Struct.new(:id, :name)
125
+
126
+ nested_property = Kartograph::Property.new(:hello, plural: true) do
127
+ mapping dummy_class
128
+
129
+ property :id
130
+ property :name
131
+ end
132
+
133
+ hash = {
134
+ hello: [{
135
+ 'id' => 555,
136
+ 'name' => 'Buckstar'
137
+ }, {
138
+ 'id' => 556,
139
+ 'name' => 'Starbuck'
140
+ }]
141
+ }
142
+
143
+ value = nested_property.value_from(hash)
144
+ expect(value).to be_kind_of(Array)
145
+ expect(value.size).to be(2)
146
+
147
+ expect(value[0].id).to eq(hash[:hello][0]['id'])
148
+ expect(value[0].name).to eq(hash[:hello][0]['name'])
149
+
150
+ expect(value[1].id).to eq(hash[:hello][1]['id'])
151
+ expect(value[1].name).to eq(hash[:hello][1]['name'])
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ describe Kartograph::RootKey do
4
+ describe '#initialize' do
5
+ it 'initializes with options' do
6
+ options = { singular: 'hello', plural: 'hellos', scopes: [:read] }
7
+
8
+ instance = Kartograph::RootKey.new(options)
9
+ expect(instance.options).to eq(options)
10
+ end
11
+ end
12
+
13
+ describe '#scopes' do
14
+ it 'reads the scopes' do
15
+ instance = Kartograph::RootKey.new(scopes: [:read])
16
+ expect(instance.scopes).to eq([:read])
17
+ end
18
+ end
19
+
20
+ describe '#singular' do
21
+ it 'reads the singular key' do
22
+ instance = Kartograph::RootKey.new(singular: 'user')
23
+ expect(instance.singular).to eq('user')
24
+ end
25
+ end
26
+
27
+ describe '#plural' do
28
+ it 'reads the plural key' do
29
+ instance = Kartograph::RootKey.new(plural: 'user')
30
+ expect(instance.plural).to eq('user')
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,76 @@
1
+ require 'spec_helper'
2
+
3
+ describe Kartograph::Sculptor do
4
+ describe '#initialize' do
5
+ it 'initializes with a hash and map' do
6
+ hash = {}
7
+ map = double('im a map')
8
+
9
+ sculptor = Kartograph::Sculptor.new(hash, map)
10
+
11
+ expect(sculptor.object).to be(hash)
12
+ expect(sculptor.map).to be(map)
13
+ end
14
+ end
15
+
16
+ describe '#sculpt' do
17
+ let(:map) { Kartograph::Map.new }
18
+ let(:object) { { 'id' => 343, 'name' => 'Guilty Spark' } }
19
+
20
+ context 'without a scope' do
21
+ before do
22
+ map.mapping DummyUser
23
+ map.property :id, scopes: [:read]
24
+ map.property :name, scopes: [:read, :create]
25
+ end
26
+
27
+ it 'returns a coerced user' do
28
+ sculptor = Kartograph::Sculptor.new(object, map)
29
+ sculpted = sculptor.sculpt
30
+
31
+ expect(sculpted).to be_kind_of(DummyUser)
32
+ expect(sculpted.id).to eq(object['id'])
33
+ expect(sculpted.name).to eq(object['name'])
34
+ end
35
+ end
36
+
37
+ context 'with a scope' do
38
+ before do
39
+ map.mapping DummyUser
40
+ map.property :id, scopes: [:read]
41
+ map.property :name, scopes: [:create]
42
+ end
43
+
44
+ it 'returns a coerced user' do
45
+ sculptor = Kartograph::Sculptor.new(object, map)
46
+ sculpted = sculptor.sculpt(:read)
47
+
48
+ expect(sculpted).to be_kind_of(DummyUser)
49
+ expect(sculpted.id).to eq(object['id'])
50
+ expect(sculpted.name).to be_nil
51
+ end
52
+
53
+ context 'for nested properties' do
54
+ let(:object) { super().merge('comment' => { 'id' => 123, 'text' => 'hello' }) }
55
+
56
+ before do
57
+ map.property :comment, scopes: [:read] do
58
+ mapping DummyComment
59
+
60
+ property :id, scopes: [:read]
61
+ property :text, scopes: [:create]
62
+ end
63
+ end
64
+
65
+ it 'returns the nested objects with scoped properties set' do
66
+ sculptor = Kartograph::Sculptor.new(object, map)
67
+ sculpted = sculptor.sculpt(:read)
68
+
69
+ expect(sculpted.comment).to be_kind_of(DummyComment)
70
+ expect(sculpted.comment.id).to eq(123)
71
+ expect(sculpted.comment.text).to be_nil
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,66 @@
1
+ require 'kartograph'
2
+
3
+ Dir['./spec/support/**/*.rb'].each {|f| load f }
4
+
5
+ RSpec.configure do |config|
6
+ # The settings below are suggested to provide a good initial experience
7
+ # with RSpec, but feel free to customize to your heart's content.
8
+ =begin
9
+ # These two settings work together to allow you to limit a spec run
10
+ # to individual examples or groups you care about by tagging them with
11
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
12
+ # get run.
13
+ config.filter_run :focus
14
+ config.run_all_when_everything_filtered = true
15
+
16
+ # Many RSpec users commonly either run the entire suite or an individual
17
+ # file, and it's useful to allow more verbose output when running an
18
+ # individual spec file.
19
+ if config.files_to_run.one?
20
+ # Use the documentation formatter for detailed output,
21
+ # unless a formatter has already been configured
22
+ # (e.g. via a command-line flag).
23
+ config.default_formatter = 'doc'
24
+ end
25
+
26
+ # Print the 10 slowest examples and example groups at the
27
+ # end of the spec run, to help surface which specs are running
28
+ # particularly slow.
29
+ config.profile_examples = 10
30
+
31
+ # Run specs in random order to surface order dependencies. If you find an
32
+ # order dependency and want to debug it, you can fix the order by providing
33
+ # the seed, which is printed after each run.
34
+ # --seed 1234
35
+ config.order = :random
36
+
37
+ # Seed global randomization in this process using the `--seed` CLI option.
38
+ # Setting this allows you to use `--seed` to deterministically reproduce
39
+ # test failures related to randomization by passing the same `--seed` value
40
+ # as the one that triggered the failure.
41
+ Kernel.srand config.seed
42
+
43
+ # rspec-expectations config goes here. You can use an alternate
44
+ # assertion/expectation library such as wrong or the stdlib/minitest
45
+ # assertions if you prefer.
46
+ config.expect_with :rspec do |expectations|
47
+ # Enable only the newer, non-monkey-patching expect syntax.
48
+ # For more details, see:
49
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
50
+ expectations.syntax = :expect
51
+ end
52
+
53
+ # rspec-mocks config goes here. You can use an alternate test double
54
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
55
+ config.mock_with :rspec do |mocks|
56
+ # Enable only the newer, non-monkey-patching expect syntax.
57
+ # For more details, see:
58
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
59
+ mocks.syntax = :expect
60
+
61
+ # Prevents you from mocking or stubbing a method that does not exist on
62
+ # a real object. This is generally recommended.
63
+ mocks.verify_partial_doubles = true
64
+ end
65
+ =end
66
+ end
@@ -0,0 +1,22 @@
1
+ shared_context "DSL Objects" do
2
+ let(:object) { double('object', id: 1066, name: 'Bruce (the dude from Finding Nemo)') }
3
+ let(:mapped) do
4
+ Class.new do
5
+ include Kartograph::DSL
6
+
7
+ kartograph do
8
+ mapping DummyUser
9
+
10
+ property :id, scopes: [:read]
11
+ property :name, scopes: [:read, :create]
12
+
13
+ property :comments, plural: true do
14
+ mapping DummyComment
15
+
16
+ property :id, scopes: [:read]
17
+ property :text, scopes: [:read, :create]
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,2 @@
1
+ class DummyComment < Struct.new(:id, :text)
2
+ end
@@ -0,0 +1,2 @@
1
+ class DummyUser < Struct.new(:id, :name, :comment)
2
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kartograph
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Ross
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-07-29 00:00:00.000000000 Z
11
+ date: 2014-07-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - ~>
53
53
  - !ruby/object:Gem::Version
54
54
  version: 3.0.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
55
69
  description: Short Description
56
70
  email:
57
71
  - rross@digitalocean.com
@@ -60,13 +74,34 @@ extensions: []
60
74
  extra_rdoc_files: []
61
75
  files:
62
76
  - .gitignore
77
+ - .rspec
63
78
  - Gemfile
64
79
  - LICENSE.txt
65
80
  - README.md
66
81
  - Rakefile
82
+ - examples/domains.rb
83
+ - examples/representation_for.rb
67
84
  - kartograph.gemspec
68
85
  - lib/kartograph.rb
86
+ - lib/kartograph/artist.rb
87
+ - lib/kartograph/dsl.rb
88
+ - lib/kartograph/map.rb
89
+ - lib/kartograph/property.rb
90
+ - lib/kartograph/property_collection.rb
91
+ - lib/kartograph/root_key.rb
92
+ - lib/kartograph/sculptor.rb
69
93
  - lib/kartograph/version.rb
94
+ - spec/lib/kartograph/artist_spec.rb
95
+ - spec/lib/kartograph/dsl_spec.rb
96
+ - spec/lib/kartograph/map_spec.rb
97
+ - spec/lib/kartograph/property_collection_spec.rb
98
+ - spec/lib/kartograph/property_spec.rb
99
+ - spec/lib/kartograph/root_key_spec.rb
100
+ - spec/lib/kartograph/sculptor_spec.rb
101
+ - spec/spec_helper.rb
102
+ - spec/support/dsl_contexts.rb
103
+ - spec/support/dummy_comment.rb
104
+ - spec/support/dummy_user.rb
70
105
  homepage: ''
71
106
  licenses:
72
107
  - MIT
@@ -91,4 +126,15 @@ rubygems_version: 2.2.2
91
126
  signing_key:
92
127
  specification_version: 4
93
128
  summary: Short Summary
94
- test_files: []
129
+ test_files:
130
+ - spec/lib/kartograph/artist_spec.rb
131
+ - spec/lib/kartograph/dsl_spec.rb
132
+ - spec/lib/kartograph/map_spec.rb
133
+ - spec/lib/kartograph/property_collection_spec.rb
134
+ - spec/lib/kartograph/property_spec.rb
135
+ - spec/lib/kartograph/root_key_spec.rb
136
+ - spec/lib/kartograph/sculptor_spec.rb
137
+ - spec/spec_helper.rb
138
+ - spec/support/dsl_contexts.rb
139
+ - spec/support/dummy_comment.rb
140
+ - spec/support/dummy_user.rb