yaks 0.8.3 → 0.9.0

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