yaks 0.8.3 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ module Yaks
2
+ class Mapper
3
+ class Form
4
+ class DynamicField
5
+ include Attributes.new(:block)
6
+
7
+ def self.create(_opts = nil, &block)
8
+ new(block: block)
9
+ end
10
+
11
+ def to_resource_fields(mapper)
12
+ Config.build_with_object(mapper.object, &block).to_resource_fields(mapper)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -5,13 +5,16 @@ module Yaks
5
5
  include Attributes.new(
6
6
  :name,
7
7
  label: nil,
8
- options: [].freeze
8
+ options: [].freeze,
9
+ if: nil
9
10
  ).add(HTML5Forms::FIELD_OPTIONS)
10
11
 
11
12
  Builder = Builder.new(self) do
12
- def_set :name
13
- def_set :label
13
+ def_set :name, :label
14
14
  def_add :option, create: Option, append_to: :options
15
+ HTML5Forms::FIELD_OPTIONS.each do |option, _|
16
+ def_set option
17
+ end
15
18
  end
16
19
 
17
20
  def self.create(*args)
@@ -24,18 +27,18 @@ module Yaks
24
27
 
25
28
  # Convert to a Resource::Form::Field, expanding any dynamic
26
29
  # values
27
- def to_resource(mapper)
28
- Resource::Form::Field.new(
29
- resource_attributes.each_with_object({}) do |attr, attrs|
30
- attrs[attr] = mapper.expand_value(public_send(attr))
31
- end.merge(options: resource_options(mapper))
32
- )
30
+ def to_resource_fields(mapper)
31
+ return [] if self.if && !mapper.expand_value(self.if)
32
+ [ Resource::Form::Field.new(
33
+ (resource_attributes - [:if]).each_with_object({}) do |attr, attrs|
34
+ attrs[attr] = mapper.expand_value(public_send(attr))
35
+ end.merge(options: resource_options(mapper))) ]
33
36
  end
34
37
 
35
38
  def resource_options(mapper)
36
39
  # make sure all empty options arrays are the same instance,
37
40
  # makes for prettier #pp
38
- options.empty? ? options : options.map {|opt| opt.to_resource_field_option(mapper) }
41
+ options.empty? ? options : options.map {|opt| opt.to_resource_field_option(mapper) }.compact
39
42
  end
40
43
 
41
44
  # All attributes that can be converted 1-to-1 to
@@ -4,13 +4,14 @@ module Yaks
4
4
  class Field
5
5
  # <option>, as used in a <select>
6
6
  class Option
7
- include Attributes.new(:value, :label, selected: false, disabled: false)
7
+ include Attributes.new(:value, :label, selected: false, disabled: false, if: nil)
8
8
 
9
9
  def self.create(value, opts = {})
10
10
  new(opts.merge(value: value))
11
11
  end
12
12
 
13
13
  def to_resource_field_option(mapper)
14
+ return if self.if && !mapper.expand_value(self.if)
14
15
  Resource::Form::Field::Option.new(
15
16
  value: mapper.expand_value(value),
16
17
  label: mapper.expand_value(label),
@@ -3,39 +3,17 @@ module Yaks
3
3
  class Form
4
4
  class Fieldset
5
5
  extend Forwardable
6
- include Concord.new(:config)
6
+ include Attributes.new(:config)
7
7
 
8
- def_delegators :config, :fields, :dynamic_blocks
8
+ def_delegators :config, :fields
9
9
 
10
- ConfigBuilder = Builder.new(Config) do
11
- def_add :field, create: Field::Builder, append_to: :fields
12
- def_add :fieldset, create: Fieldset, append_to: :fields
13
- HTML5Forms::INPUT_TYPES.each do |type|
14
- def_add(type,
15
- create: Field::Builder,
16
- append_to: :fields,
17
- defaults: { type: type }
18
- )
19
- end
20
- def_forward :dynamic
10
+ def self.create(options = {}, &block)
11
+ new(config: Config.build(options, &block))
21
12
  end
22
13
 
23
- def self.create(_opts={}, &block)
24
- new(ConfigBuilder.build(Config.new, &block))
25
- end
26
-
27
- def to_resource(mapper)
28
- config = dynamic_blocks.inject(self.config) do |config, block|
29
- ConfigBuilder.build(config, mapper.object, &block)
30
- end
31
-
32
- resource_fields = resource_fields(config.fields, mapper)
33
-
34
- Resource::Form::Fieldset.new(fields: resource_fields)
35
- end
36
-
37
- def resource_fields(fields, mapper)
38
- fields.map { |field| field.to_resource(mapper) }
14
+ def to_resource_fields(mapper)
15
+ return [] if config.if && !mapper.expand_value(config.if)
16
+ [ Resource::Form::Fieldset.new(fields: config.to_resource_fields(mapper)) ]
39
17
  end
40
18
  end
41
19
  end
@@ -7,7 +7,6 @@ module Yaks
7
7
  links = convert_links(attributes.delete('_links') || {})
8
8
  embedded = convert_embedded(attributes.delete('_embedded') || {})
9
9
 
10
-
11
10
  Resource.new(
12
11
  type: attributes.delete('type') || type_from_links(links),
13
12
  attributes: Util.symbolize_keys(attributes),
@@ -0,0 +1,49 @@
1
+ module Yaks
2
+ module Reader
3
+ class JsonAPI
4
+ def call(parsed_json, env = {})
5
+ attributes = parsed_json['data'].first.dup
6
+ links = attributes.delete('links') || {}
7
+ linked = parsed_json['linked'].nil? ? {} : parsed_json['linked'].dup
8
+ embedded = convert_embedded(links, linked)
9
+ Resource.new(
10
+ type: Util.singularize(attributes.delete('type')[/\w+$/]),
11
+ attributes: Util.symbolize_keys(attributes),
12
+ subresources: embedded,
13
+ links: []
14
+ )
15
+ end
16
+
17
+ def convert_embedded(links, linked)
18
+ links.flat_map do |rel, link_data|
19
+ # If this is a compound link, the link_data will contain either
20
+ # * 'type' and 'id' for a one to one
21
+ # * 'type' and 'ids' for a homogeneous to-many relationship
22
+ # * 'data' being an array where each member has 'type' and 'id' for heterogeneous
23
+ if !link_data['type'].nil? && !link_data['id'].nil?
24
+ resource = linked.find{ |item| (item['id'] == link_data['id']) && (item['type'] == link_data['type']) }
25
+ call({'data' => [resource], 'linked' => linked}).with(rels: [rel])
26
+ elsif !link_data['type'].nil? && !link_data['ids'].nil?
27
+ resources = linked.select{ |item| (link_data['ids'].include? item['id']) && (item['type'] == link_data['type']) }
28
+ members = resources.map { |r|
29
+ call({'data' => [r], 'linked' => linked})
30
+ }
31
+ CollectionResource.new(
32
+ members: members,
33
+ type: link_data['type'],
34
+ rels: [rel]
35
+ )
36
+ elsif link_data['data'].present?
37
+ CollectionResource.new(
38
+ members: link_data['data'].map { |link|
39
+ resource = linked.find{ |item| (item['id'] == link['id']) && (item['type'] == link['type']) }
40
+ call({'data' => [resource], 'linked' => linked})
41
+ },
42
+ rels: [rel]
43
+ )
44
+ end
45
+ end.compact
46
+ end
47
+ end
48
+ end
49
+ end
@@ -1,3 +1,3 @@
1
1
  module Yaks
2
- VERSION = '0.8.3'
2
+ VERSION = '0.9.0'
3
3
  end
@@ -40,6 +40,7 @@ RSpec.describe Yaks::Format::JsonAPI do
40
40
  end
41
41
 
42
42
  include_examples 'JSON output format', config, :json_api, 'confucius'
43
+ include_examples 'JSON round trip', config, :json_api, 'confucius'
43
44
  end
44
45
 
45
46
  RSpec.describe Yaks::Format::CollectionJson do
@@ -1,51 +1,50 @@
1
1
  {
2
- "scholars": [
2
+ "data": [
3
3
  {
4
4
  "id": 9,
5
+ "type": "scholars",
5
6
  "href": "http://literature.example.com/authors/kongzi",
6
7
  "name": "孔子",
7
8
  "pinyin": "Kongzi",
8
9
  "latinized": "Confucius",
9
10
  "links": {
10
- "works": [11,12]
11
+ "works": {"type": "works", "ids": [11,12]}
11
12
  }
12
13
  }
13
14
  ],
14
- "linked": {
15
- "works": [
15
+ "linked": [
16
16
  {
17
17
  "id": 11,
18
+ "type": "works",
18
19
  "href": "http://literature.example.com/work/11",
19
20
  "chinese_name": "論語",
20
21
  "english_name": "Analects",
21
22
  "links": {
22
- "quotes": [17, 18],
23
- "era": 99
23
+ "quotes": {"type": "quotes", "ids": [17, 18]},
24
+ "era": {"type": "erae", "id": 99}
24
25
  }
25
26
  },
26
- {
27
- "id": 12,
28
- "href": "http://literature.example.com/work/12",
29
- "chinese_name": "易經",
30
- "english_name": "Commentaries to the Yi-jing",
31
- "links": {}
32
- }
33
- ],
34
- "quotes": [
35
27
  {
36
28
  "id": 17,
29
+ "type": "quotes",
37
30
  "chinese": "廄焚。子退朝,曰:“傷人乎?” 不問馬。"
38
31
  },
39
32
  {
40
33
  "id": 18,
34
+ "type": "quotes",
41
35
  "chinese": "子曰:“其恕乎!己所不欲、勿施於人。”"
42
- }
43
- ],
44
- "erae": [
36
+ },
45
37
  {
46
38
  "id": 99,
39
+ "type": "erae",
47
40
  "name": "Zhou Dynasty"
41
+ },
42
+ {
43
+ "id": 12,
44
+ "type": "works",
45
+ "href": "http://literature.example.com/work/12",
46
+ "chinese_name": "易經",
47
+ "english_name": "Commentaries to the Yi-jing"
48
48
  }
49
- ]
50
- }
49
+ ]
51
50
  }
@@ -38,7 +38,7 @@ RSpec.describe Yaks::Attributes do
38
38
  end
39
39
 
40
40
  context 'when extending' do
41
- subject { Class.new(super()) { include attributes.add(baz: 7) } }
41
+ subject { Class.new(super()) { include attributes.add(baz: 7, bar: 4) } }
42
42
 
43
43
  it 'should make the new attributes available' do
44
44
  expect(subject.new(foo: 3, baz: 6).baz).to equal 6
@@ -48,6 +48,14 @@ RSpec.describe Yaks::Attributes do
48
48
  expect(subject.new(foo: 3, baz: 6).foo).to equal 3
49
49
  end
50
50
 
51
+ it 'should take new default values' do
52
+ expect(subject.new(foo: 3, baz: 6).bar).to equal 4
53
+ end
54
+
55
+ it 'should make sure attribute names are uniq' do
56
+ expect(subject.attributes.names.length).to equal 3
57
+ end
58
+
51
59
  context 'without any defaults' do
52
60
  subject { Class.new(super()) { include attributes.add(:bax) } }
53
61
 
@@ -60,6 +68,34 @@ RSpec.describe Yaks::Attributes do
60
68
  end
61
69
  end
62
70
  end
71
+
72
+ context 'when removing an attribute with a default' do
73
+ subject { Class.new(super()) { include attributes.remove(:bar) } }
74
+
75
+ it 'should still recognize attributes that were kept' do
76
+ expect(subject.new(foo: 2).foo).to equal 2
77
+ end
78
+
79
+ it 'should no longer recognize the old attributes' do
80
+ expect { subject.new(foo: 3, bar: 3).bar }.to raise_error
81
+ end
82
+ end
83
+
84
+ context 'when removing an attribute without a default' do
85
+ subject { Class.new(super()) { include attributes.remove(:foo) } }
86
+
87
+ it 'should still recognize attributes that were kept' do
88
+ expect(subject.new(bar: 2).bar).to equal 2
89
+ end
90
+
91
+ it 'should no longer recognize the old attributes' do
92
+ expect { subject.new(foo: 3).foo }.to raise_error
93
+ end
94
+
95
+ it 'should keep the defaults' do
96
+ expect(subject.new.bar).to equal 3
97
+ end
98
+ end
63
99
  end
64
100
 
65
101
  RSpec.describe Yaks::Attributes::InstanceMethods do
@@ -13,13 +13,12 @@ RSpec.describe Yaks::Builder do
13
13
  def wrong_type(x, y)
14
14
  "foo #{x} #{y}"
15
15
  end
16
-
17
16
  end
18
17
 
19
18
  subject do
20
- Yaks::Builder.new(Buildable) do
19
+ Yaks::Builder.new(Buildable, [:finalize]) do
21
20
  def_set :foo, :bar
22
- def_forward :finalize, :wrong_type, :update
21
+ def_forward :wrong_type, :update
23
22
  end
24
23
  end
25
24
 
@@ -36,4 +35,36 @@ RSpec.describe Yaks::Builder do
36
35
  expect( subject.create(3, 4) { finalize } ).to eql Buildable.new(foo: 7, bar: 8)
37
36
  end
38
37
 
38
+ context 'with no methods to forward' do
39
+ subject do
40
+ Yaks::Builder.new(Buildable)
41
+ end
42
+
43
+ it 'should still work' do
44
+ expect(subject.create(3,4)).to eql Buildable.new(foo: 3, bar: 4)
45
+ end
46
+ end
47
+
48
+ describe '#build' do
49
+ it 'should pass on the initial state if no block is given' do
50
+ expect(subject.build(:foo)).to equal :foo
51
+ end
52
+
53
+ it 'should pass any extra args to the block' do
54
+ expect(subject.build(Buildable.new(foo: 1, bar: 2), 9) {|f| foo(f)}).to eql(
55
+ Buildable.new(foo: 9, bar: 2)
56
+ )
57
+ end
58
+ end
59
+
60
+ describe '#inspect' do
61
+ subject do
62
+ Yaks::Builder.new(Buildable, [:foo, :bar])
63
+ end
64
+
65
+ it 'should show the class and methods' do
66
+ expect(subject.inspect).to eql '#<Builder Buildable [:foo, :bar]>'
67
+ end
68
+ end
69
+
39
70
  end
@@ -3,12 +3,13 @@ RSpec.describe Yaks::CollectionMapper do
3
3
 
4
4
  subject(:mapper) { mapper_class.new(context) }
5
5
  let(:mapper_class) { described_class }
6
+ let(:mapper_stack) { [] }
6
7
 
7
8
  let(:context) {
8
9
  { item_mapper: item_mapper,
9
10
  policy: policy,
10
11
  env: {},
11
- mapper_stack: [] }
12
+ mapper_stack: mapper_stack }
12
13
  }
13
14
 
14
15
  let(:collection) { [] }
@@ -25,6 +26,24 @@ RSpec.describe Yaks::CollectionMapper do
25
26
  )
26
27
  end
27
28
 
29
+ it 'should accept a second "env" argument' do
30
+ expect(mapper.call(collection, {})).to be_a Yaks::CollectionResource
31
+ end
32
+
33
+ context 'when at the top of the stack' do
34
+ it 'should have a "collection" rel derived from the type' do
35
+ expect(mapper.call(collection).rels).to eql ['rel:the_types']
36
+ end
37
+ end
38
+
39
+ context 'when not at the top of the stack' do
40
+ let(:mapper_stack) { [ :foo ]}
41
+
42
+ it 'should not have a rel' do
43
+ expect(mapper.call(collection).rels).to eql []
44
+ end
45
+ end
46
+
28
47
  context 'with members' do
29
48
  let(:collection) { [boingboing, wassup]}
30
49
  let(:item_mapper) { PetMapper }
@@ -52,6 +52,13 @@ RSpec.describe Yaks::CollectionResource do
52
52
  its(:rels) { should eq ['http://api.example.org/rels/orders'] }
53
53
 
54
54
  its(:subresources) { should eql [] }
55
+ end
56
+
57
+ describe '#seq' do
58
+ let(:init_opts) { { members: [1,2,3] } }
55
59
 
60
+ it 'iterates over the members' do
61
+ expect(subject.seq.map(&:next)).to eql [2,3,4]
62
+ end
56
63
  end
57
64
  end
@@ -0,0 +1,84 @@
1
+ class Kitten
2
+ include Yaks::Attributes.new(:furriness)
3
+
4
+ def self.create(opts, &block)
5
+ level = opts[:fur_level]
6
+ level = block.call(level) if block
7
+ new(furriness: level)
8
+ end
9
+ end
10
+
11
+ class Hanky
12
+ include Yaks::Attributes.new(:stickyness, :size, :color)
13
+
14
+ def self.create(sticky, opts = {})
15
+ new(stickyness: sticky, size: opts[:size], color: opts[:color])
16
+ end
17
+ end
18
+
19
+ RSpec.describe Yaks::Configurable do
20
+ let(:suffix) { SecureRandom.hex(16) }
21
+ subject do
22
+ eval %Q<
23
+ class TestConfigurable#{suffix}
24
+ class Config
25
+ include Yaks::Attributes.new(color: 'blue', taste: 'sour', contents: [])
26
+ end
27
+ extend Yaks::Configurable
28
+
29
+ def_add :kitten, create: Kitten, append_to: :contents, defaults: {fur_level: 7}
30
+ def_add :cat, create: Kitten, append_to: :contents
31
+ def_add :hanky, create: Hanky, append_to: :contents, defaults: {size: 12, color: :red}
32
+ end
33
+ >
34
+ Kernel.const_get("TestConfigurable#{suffix}")
35
+ end
36
+
37
+ describe '.extended' do
38
+ it 'should initialize an empty config object' do
39
+ expect(subject.config).to eql subject::Config.new
40
+ end
41
+ end
42
+
43
+ describe '#def_add' do
44
+ it 'should add' do
45
+ subject.kitten(fur_level: 9)
46
+ expect(subject.config.contents).to eql [Kitten.new(furriness: 9)]
47
+ end
48
+
49
+ it 'should use defaults if configured' do
50
+ subject.kitten
51
+ expect(subject.config.contents).to eql [Kitten.new(furriness: 7)]
52
+ end
53
+
54
+ it 'should work without defaults configured' do
55
+ subject.cat(fur_level: 3)
56
+ expect(subject.config.contents).to eql [Kitten.new(furriness: 3)]
57
+ end
58
+
59
+ it 'should pass on a block' do
60
+ subject.cat(fur_level: 3) {|l| l+3}
61
+ expect(subject.config.contents).to eql [Kitten.new(furriness: 6)]
62
+ end
63
+
64
+ it 'should work with a create with positional arguments - defaults' do
65
+ subject.hanky(3)
66
+ expect(subject.config.contents).to eql [Hanky.new(stickyness: 3, size: 12, color: :red)]
67
+ end
68
+
69
+ it 'should work with a create with positional arguments' do
70
+ subject.hanky(5, size: 15)
71
+ expect(subject.config.contents).to eql [Hanky.new(stickyness: 5, size: 15, color: :red)]
72
+ end
73
+ end
74
+
75
+
76
+ describe '#def_set' do
77
+ it 'should set' do
78
+ end
79
+ end
80
+ describe '#def_forward' do
81
+ it 'should forward' do
82
+ end
83
+ end
84
+ end