kartograph 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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