contentful_model 0.2.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +5 -5
  2. data/lib/contentful_model.rb +17 -13
  3. data/lib/contentful_model/asset.rb +30 -0
  4. data/lib/contentful_model/asset_dimension_query.rb +70 -0
  5. data/lib/contentful_model/asset_dimensions.rb +54 -0
  6. data/lib/contentful_model/associations/associations.rb +10 -4
  7. data/lib/contentful_model/associations/belongs_to.rb +9 -4
  8. data/lib/contentful_model/associations/belongs_to_many.rb +22 -45
  9. data/lib/contentful_model/associations/has_many.rb +15 -13
  10. data/lib/contentful_model/associations/has_many_nested.rb +39 -37
  11. data/lib/contentful_model/associations/has_one.rb +22 -14
  12. data/lib/contentful_model/base.rb +115 -74
  13. data/lib/contentful_model/client.rb +14 -3
  14. data/lib/contentful_model/errors.rb +0 -1
  15. data/lib/contentful_model/manageable.rb +49 -21
  16. data/lib/contentful_model/management.rb +1 -0
  17. data/lib/contentful_model/migrations/content_type.rb +24 -24
  18. data/lib/contentful_model/migrations/content_type_factory.rb +9 -6
  19. data/lib/contentful_model/migrations/migration.rb +9 -4
  20. data/lib/contentful_model/queries.rb +60 -9
  21. data/lib/contentful_model/query.rb +148 -5
  22. data/lib/contentful_model/validations/lambda_validation.rb +17 -0
  23. data/lib/contentful_model/validations/validates_presence_of.rb +4 -1
  24. data/lib/contentful_model/validations/validations.rb +56 -8
  25. data/lib/contentful_model/version.rb +1 -1
  26. data/spec/asset_spec.rb +141 -0
  27. data/spec/associations/belongs_to_many_spec.rb +38 -0
  28. data/spec/associations/belongs_to_spec.rb +22 -0
  29. data/spec/associations/has_many_nested_spec.rb +321 -0
  30. data/spec/associations/has_many_spec.rb +33 -0
  31. data/spec/associations/has_one_spec.rb +32 -0
  32. data/spec/base_spec.rb +199 -0
  33. data/spec/chainable_queries_spec.rb +199 -0
  34. data/spec/client_spec.rb +11 -0
  35. data/spec/contentful_model_spec.rb +25 -0
  36. data/spec/fixtures/vcr_cassettes/asset/all.yml +161 -0
  37. data/spec/fixtures/vcr_cassettes/asset/find.yml +118 -0
  38. data/spec/fixtures/vcr_cassettes/association/belongs_to_many.yml +439 -0
  39. data/spec/fixtures/vcr_cassettes/association/has_many.yml +161 -0
  40. data/spec/fixtures/vcr_cassettes/association/has_one.yml +161 -0
  41. data/spec/fixtures/vcr_cassettes/association/nested_with_root_root.yml +240 -0
  42. data/spec/fixtures/vcr_cassettes/association/nested_without_root_child_higher_include.yml +123 -0
  43. data/spec/fixtures/vcr_cassettes/association/nested_without_root_child_parent.yml +609 -0
  44. data/spec/fixtures/vcr_cassettes/association/nested_without_root_childless.yml +688 -0
  45. data/spec/fixtures/vcr_cassettes/association/nested_without_root_middle.yml +319 -0
  46. data/spec/fixtures/vcr_cassettes/association/nested_without_root_middle_higher_include.yml +161 -0
  47. data/spec/fixtures/vcr_cassettes/association/nested_without_root_middle_parent.yml +489 -0
  48. data/spec/fixtures/vcr_cassettes/association/nested_without_root_parentless.yml +331 -0
  49. data/spec/fixtures/vcr_cassettes/association/nested_without_root_parentless_higher_include.yml +82 -0
  50. data/spec/fixtures/vcr_cassettes/associations/nested_without_root_middle.yml +161 -0
  51. data/spec/fixtures/vcr_cassettes/associations/nested_without_root_middle_higher_include.yml +82 -0
  52. data/spec/fixtures/vcr_cassettes/base/content_type.yml +185 -0
  53. data/spec/fixtures/vcr_cassettes/base/return_nil_for_empty.yml +202 -0
  54. data/spec/fixtures/vcr_cassettes/base/return_nil_for_empty_with_value.yml +124 -0
  55. data/spec/fixtures/vcr_cassettes/client.yml +98 -0
  56. data/spec/fixtures/vcr_cassettes/dog.yml +161 -0
  57. data/spec/fixtures/vcr_cassettes/human.yml +199 -0
  58. data/spec/fixtures/vcr_cassettes/management/client.yml +1362 -0
  59. data/spec/fixtures/vcr_cassettes/management/nyancat.yml +1449 -0
  60. data/spec/fixtures/vcr_cassettes/management/nyancat_2.yml +1449 -0
  61. data/spec/fixtures/vcr_cassettes/management/nyancat_publish.yml +481 -0
  62. data/spec/fixtures/vcr_cassettes/management/nyancat_refetch_and_fail.yml +558 -0
  63. data/spec/fixtures/vcr_cassettes/management/nyancat_refetch_and_save.yml +620 -0
  64. data/spec/fixtures/vcr_cassettes/management/nyancat_save.yml +479 -0
  65. data/spec/fixtures/vcr_cassettes/management/nyancat_save_2.yml +479 -0
  66. data/spec/fixtures/vcr_cassettes/nyancat.yml +196 -0
  67. data/spec/fixtures/vcr_cassettes/playground/nyancat.yml +177 -0
  68. data/spec/fixtures/vcr_cassettes/query/each_entry.yml +319 -0
  69. data/spec/fixtures/vcr_cassettes/query/each_page.yml +319 -0
  70. data/spec/fixtures/vcr_cassettes/query/empty.yml +180 -0
  71. data/spec/fixtures/vcr_cassettes/query/load.yml +197 -0
  72. data/spec/fixtures/vcr_cassettes/query/manual_pagination.yml +161 -0
  73. data/spec/fixtures/vcr_cassettes/query/nyancat_invalid_elements.yml +247 -0
  74. data/spec/manageable_spec.rb +186 -0
  75. data/spec/management_spec.rb +17 -0
  76. data/spec/migrations/content_type_factory_spec.rb +41 -0
  77. data/spec/migrations/content_type_spec.rb +176 -0
  78. data/spec/migrations/migration_spec.rb +75 -0
  79. data/spec/queries_spec.rb +85 -0
  80. data/spec/query_spec.rb +126 -0
  81. data/spec/spec_helper.rb +58 -0
  82. data/spec/validations/validations_spec.rb +182 -0
  83. metadata +213 -19
  84. data/lib/contentful_model/chainable_queries.rb +0 -104
@@ -0,0 +1,17 @@
1
+ module Contentful
2
+ module Validations
3
+ # Class to define lambda validations
4
+ class LambdaValidation
5
+ def initialize(name, fn)
6
+ @name = name
7
+ fail "#{name}: Validator function or Proc is required" unless fn.is_a?(Proc)
8
+ @validator = fn
9
+ end
10
+
11
+ def validate(entry)
12
+ return ["#{@name}: validation not met"] unless @validator[entry]
13
+ []
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,10 +1,12 @@
1
1
  module Contentful
2
2
  module Validations
3
+ # Module to check presence of a field
3
4
  module PresenceOf
4
5
  def self.included(base)
5
6
  base.extend(ClassMethods)
6
7
  end
7
8
 
9
+ # Class method
8
10
  module ClassMethods
9
11
  def validates_presence_of(*args)
10
12
  @validations ||= []
@@ -13,6 +15,7 @@ module Contentful
13
15
  end
14
16
  end
15
17
 
18
+ # Actual validation
16
19
  class PresenceValidation
17
20
  attr_reader :fields
18
21
 
@@ -24,7 +27,7 @@ module Contentful
24
27
  errors = []
25
28
 
26
29
  fields.each do |field|
27
- errors << "#{field} is required" unless entry.respond_to?(field)
30
+ errors << "#{field} is required" unless entry.respond_to?(field) && entry.public_send(field).present?
28
31
  end
29
32
 
30
33
  errors
@@ -1,6 +1,10 @@
1
+ require_relative 'validates_presence_of'
2
+ require_relative 'lambda_validation'
3
+
1
4
  # A module to validate entries. A little like ActiveRecord. We don't think this should be necessary, really,
2
5
  # because Contentful should be doing the validating, but they expose invalid entries through the preview API.
3
6
  module ContentfulModel
7
+ # Defines general validation methods
4
8
  module Validations
5
9
  def self.included(base)
6
10
  base.extend ClassMethods
@@ -8,23 +12,28 @@ module ContentfulModel
8
12
  attr_reader :errors
9
13
  end
10
14
 
11
- def valid?
12
- validate
15
+ def valid?(save = false)
16
+ validate(save)
13
17
  end
14
18
 
15
- def invalid?
16
- !valid?
19
+ def invalid?(save = false)
20
+ !valid?(save)
17
21
  end
18
22
 
19
- def validate
23
+ def validate(save = false)
20
24
  @errors = []
21
- unless self.respond_to?(:fields)
25
+ unless respond_to?(:fields)
22
26
  @errors.push("Entity doesn't respond to the fields() method")
23
27
  return false
24
28
  end
25
29
 
26
- validations = self.class.send(:validations)
27
- unless validations.nil?
30
+ validation_kinds = [:validations]
31
+ validation_kinds << :save_validations if save
32
+
33
+ validation_kinds.each do |validation_kind|
34
+ validations = self.class.send(validation_kind)
35
+ next if validations.nil?
36
+
28
37
  validations.each do |validation|
29
38
  @errors += validation.validate(self)
30
39
  end
@@ -33,10 +42,49 @@ module ContentfulModel
33
42
  @errors.empty?
34
43
  end
35
44
 
45
+ # Class methods for Validations
36
46
  module ClassMethods
37
47
  def validations
38
48
  @validations
39
49
  end
50
+
51
+ def save_validations
52
+ @save_validations
53
+ end
54
+
55
+ def validate_with(validation, on_load: false)
56
+ if validation.is_a?(Class)
57
+ validation = validation.new
58
+ elsif validation.respond_to?(:validate)
59
+ validation = validation
60
+ else
61
+ fail '::validate_with requires a Class or object that responds to #validate(entry)'
62
+ end
63
+
64
+ if on_load
65
+ @validations ||= []
66
+ @validations << validation
67
+ else
68
+ @save_validations ||= []
69
+ @save_validations << validation
70
+ end
71
+ end
72
+
73
+ def validate(name, fn = nil, on_load: false, &block)
74
+ vs = []
75
+ fail '::validate requires either a function or a block sent as a validation' if fn.nil? && block.nil?
76
+
77
+ vs << ::Contentful::Validations::LambdaValidation.new(name, fn) unless fn.nil?
78
+ vs << ::Contentful::Validations::LambdaValidation.new(name, block) unless block.nil?
79
+
80
+ if on_load
81
+ @validations ||= []
82
+ @validations += vs
83
+ else
84
+ @save_validations ||= []
85
+ @save_validations += vs
86
+ end
87
+ end
40
88
  end
41
89
  end
42
90
  end
@@ -1,3 +1,3 @@
1
1
  module ContentfulModel
2
- VERSION = "0.2.0"
2
+ VERSION = '1.0.0'.freeze
3
3
  end
@@ -0,0 +1,141 @@
1
+ require 'spec_helper'
2
+
3
+ describe ContentfulModel::Asset do
4
+ before :each do
5
+ ContentfulModel.configure do |config|
6
+ config.space = 'cfexampleapi'
7
+ config.access_token = 'b4c0n73n7fu1'
8
+ end
9
+ end
10
+
11
+ describe 'class methods' do
12
+ it '.all' do
13
+ vcr('asset/all') {
14
+ assets = described_class.all
15
+ expect(assets).to be_a ::Contentful::Array
16
+ expect(assets.first).to be_a described_class
17
+ expect(assets.first.id).to eq 'nyancat'
18
+ }
19
+ end
20
+
21
+ it '.find' do
22
+ vcr('asset/find') {
23
+ asset = described_class.find('nyancat')
24
+ expect(asset).to be_a described_class
25
+ expect(asset.id).to eq 'nyancat'
26
+ }
27
+ end
28
+ end
29
+
30
+ describe 'queries' do
31
+ it '#resize' do
32
+ vcr('asset/find') {
33
+ asset = described_class.find('nyancat')
34
+ url = asset.resize(10, 20).load
35
+
36
+ expect(url).to include('w=10')
37
+ expect(url).to include('h=20')
38
+ }
39
+ end
40
+
41
+ it '#width' do
42
+ vcr('asset/find') {
43
+ asset = described_class.find('nyancat')
44
+ url = asset.width(20).load
45
+
46
+ expect(url).to include('w=20')
47
+ }
48
+ end
49
+
50
+ it '#height' do
51
+ vcr('asset/find') {
52
+ asset = described_class.find('nyancat')
53
+ url = asset.height(10).load
54
+
55
+ expect(url).to include('h=10')
56
+ }
57
+ end
58
+
59
+ it '#format' do
60
+ vcr('asset/find') {
61
+ asset = described_class.find('nyancat')
62
+ url = asset.format('jpg').load
63
+
64
+ expect(url).to include('fm=jpg')
65
+ }
66
+ end
67
+
68
+ it '#jpeg_quality' do
69
+ vcr('asset/find') {
70
+ asset = described_class.find('nyancat')
71
+ url = asset.jpeg_quality(80).load
72
+
73
+ expect(url).to include('fm=jpg')
74
+ expect(url).to include('q=80')
75
+ }
76
+ end
77
+
78
+ it '#png_8bit' do
79
+ vcr('asset/find') {
80
+ asset = described_class.find('nyancat')
81
+ url = asset.png_8bit.load
82
+
83
+ expect(url).to include('fm=png')
84
+ expect(url).to include('fl=png8')
85
+ }
86
+ end
87
+
88
+ it '#resize_behavior' do
89
+ vcr('asset/find') {
90
+ asset = described_class.find('nyancat')
91
+ url = asset.resize_behavior('thumb').load
92
+
93
+ expect(url).to include('fit=thumb')
94
+ }
95
+ end
96
+
97
+ it '#thumbnail_focused_on' do
98
+ vcr('asset/find') {
99
+ asset = described_class.find('nyancat')
100
+ url = asset.thumbnail_focused_on('face').load
101
+
102
+ expect(url).to include('fit=thumb')
103
+ expect(url).to include('f=face')
104
+ }
105
+ end
106
+
107
+ it '#rounded_corners' do
108
+ vcr('asset/find') {
109
+ asset = described_class.find('nyancat')
110
+ url = asset.rounded_corners(20).load
111
+
112
+ expect(url).to include('r=20')
113
+ }
114
+ end
115
+
116
+ it '#padded_background_color' do
117
+ vcr('asset/find') {
118
+ asset = described_class.find('nyancat')
119
+ url = asset.padded_background_color('rgb:ffffff').load
120
+
121
+ expect(url).to include('fit=pad')
122
+ expect(url).to include('bg=rgb%3Affffff') # '%3A' is the urlencoded hexcode for ':'
123
+ }
124
+ end
125
+
126
+ it 'chaining' do
127
+ vcr('asset/find') {
128
+ asset = described_class.find('nyancat')
129
+ url = asset.resize(10, 20).rounded_corners(30).png_8bit.thumbnail_focused_on('face').load
130
+
131
+ expect(url).to include('w=10')
132
+ expect(url).to include('h=20')
133
+ expect(url).to include('r=30')
134
+ expect(url).to include('fm=png')
135
+ expect(url).to include('fl=png8')
136
+ expect(url).to include('fit=thumb')
137
+ expect(url).to include('f=face')
138
+ }
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ class BelongsToManyParent < ContentfulModel::Base
4
+ self.content_type_id = 'belongsToManyParent'
5
+
6
+ has_many :children, class_name: 'BelongsToManyChild'
7
+ end
8
+
9
+ class BelongsToManyChild < ContentfulModel::Base
10
+ self.content_type_id = 'belongsToManyChild'
11
+
12
+ belongs_to_many :parents, inverse_of: :children, class_name: 'BelongsToManyParent'
13
+ end
14
+
15
+ describe ContentfulModel::Associations::BelongsToMany do
16
+ before :each do
17
+ ContentfulModel.configure do |c|
18
+ c.space = 'a22o2qgm356c'
19
+ c.access_token = '60229df5876f14f499870d0d26f37b1323764ed637289353a5f74a5ea190c214'
20
+ c.entry_mapping = {}
21
+ end
22
+
23
+ BelongsToManyParent.add_entry_mapping
24
+ BelongsToManyChild.add_entry_mapping
25
+ end
26
+
27
+ it 'defines reverse relationship' do
28
+ vcr('association/belongs_to_many') {
29
+ child = BelongsToManyChild.first
30
+ expect(child.name).to eq 'BelongsToMany - Child'
31
+
32
+ expect(child.parents).to be_a ::Array
33
+ expect(child.parents.size).to eq 2
34
+ expect(child.parents.first).to be_a BelongsToManyParent
35
+ expect(child.parents.first.name).to eq 'BelongsToMany - Parent 1'
36
+ }
37
+ end
38
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe ContentfulModel::Associations::BelongsTo do
4
+ before :each do
5
+ ContentfulModel.configure do |c|
6
+ c.default_locale = 'en-US'
7
+ c.access_token = '4d0f55d940975f78139daae5d965b463c0816e88ad16062d2c1ee3d6cb930521'
8
+ c.space = 'facgnwwgj5fe'
9
+ c.entry_mapping = {}
10
+ end
11
+ end
12
+
13
+ it 'is not implemented - on purpose' do
14
+ expect {
15
+ class BelongsToCat < ContentfulModel::Base
16
+ self.content_type_id = 'cat'
17
+
18
+ belongs_to :best_friend, class: Cat
19
+ end
20
+ }.to raise_error "Contentful doesn't have a singular belongs_to relationship. Use belongs_to_many instead."
21
+ end
22
+ end
@@ -0,0 +1,321 @@
1
+ require 'spec_helper'
2
+
3
+ class Nested < ContentfulModel::Base
4
+ self.content_type_id = 'nested'
5
+
6
+ has_many_nested :related
7
+ end
8
+
9
+ class RootNested < ContentfulModel::Base
10
+ self.content_type_id = 'rootNested'
11
+
12
+ has_many_nested :related, root: -> { RootNested.find('4UnXECdw6AAwyeCUcIuqCy') }
13
+ end
14
+
15
+ describe ContentfulModel::Associations::HasManyNested do
16
+ before :each do
17
+ ContentfulModel.configure do |c|
18
+ c.space = 'a22o2qgm356c'
19
+ c.access_token = '60229df5876f14f499870d0d26f37b1323764ed637289353a5f74a5ea190c214'
20
+ c.entry_mapping = {}
21
+ end
22
+
23
+ Nested.add_entry_mapping
24
+ RootNested.add_entry_mapping
25
+ end
26
+
27
+ context 'without root' do
28
+ context 'only has children' do
29
+ it 'parent is nil' do
30
+ vcr('association/nested_without_root_parentless') {
31
+ nested = Nested.find('5CNbxcca5OwgI68eSuWso2')
32
+ expect(nested.parent).to be_nil
33
+ }
34
+ end
35
+
36
+ it 'has children' do
37
+ vcr('association/nested_without_root_parentless') {
38
+ nested = Nested.find('5CNbxcca5OwgI68eSuWso2')
39
+
40
+ expect(nested.children?).to be_truthy
41
+ expect(nested.children).to be_a ::Array
42
+ expect(nested.children.size).to eq 1
43
+ expect(nested.children.first).to be_a Nested
44
+ expect(nested.children.first.name).to eq 'Nested 2'
45
+ }
46
+ end
47
+
48
+ it 'is the root' do
49
+ vcr('association/nested_without_root_parentless') {
50
+ nested = Nested.find('5CNbxcca5OwgI68eSuWso2')
51
+ expect(nested.root?).to be_truthy
52
+ expect(nested.root).to eq nested
53
+ }
54
+ end
55
+
56
+ it 'has no ancestors' do
57
+ vcr('association/nested_without_root_parentless') {
58
+ nested = Nested.find('5CNbxcca5OwgI68eSuWso2')
59
+ expect(nested.ancestors).to be_empty
60
+ }
61
+ end
62
+
63
+ it 'find ancestors will return self - if passed a block and no ancestors are found' do
64
+ vcr('association/nested_without_root_parentless') {
65
+ nested = Nested.find('5CNbxcca5OwgI68eSuWso2')
66
+ expect(nested.find_ancestors { |a| a }).to eq nested
67
+ }
68
+ end
69
+
70
+ it 'find ancestors will be an empty array - if no ancestors and found and no block given' do
71
+ vcr('association/nested_without_root_parentless') {
72
+ nested = Nested.find('5CNbxcca5OwgI68eSuWso2')
73
+ expect(nested.find_ancestors.to_a).to eq []
74
+ }
75
+ end
76
+
77
+ it 'creates a tree of children relationships' do
78
+ vcr('association/nested_without_root_parentless_higher_include') {
79
+ nested = Nested.query.params('sys.id' => '5CNbxcca5OwgI68eSuWso2', include: 3).load.first
80
+ child_1 = nested.children.first
81
+ child_2 = child_1.children.first
82
+ expect(nested.nested_children).not_to be_empty
83
+ expect(nested.nested_children).to eq(
84
+ child_1 => {
85
+ child_2 => nil
86
+ }
87
+ )
88
+ }
89
+ end
90
+
91
+ it 'creates a tree of children by field' do
92
+ vcr('association/nested_without_root_parentless_higher_include') {
93
+ nested = Nested.query.params('sys.id' => '5CNbxcca5OwgI68eSuWso2', include: 3).load.first
94
+ child_1 = nested.children.first
95
+ child_2 = child_1.children.first
96
+ expect(nested.nested_children_by(:name)).not_to be_empty
97
+ expect(nested.nested_children_by(:name)).to eq(
98
+ child_1.name => {
99
+ child_2.name => nil
100
+ }
101
+ )
102
+ }
103
+ end
104
+
105
+ it 'generates flattened structures to use as paths for entries' do
106
+ vcr('association/nested_without_root_parentless_higher_include') {
107
+ nested = Nested.query.params('sys.id' => '5CNbxcca5OwgI68eSuWso2', include: 3).load.first
108
+ child_1 = nested.children.first
109
+ child_2 = child_1.children.first
110
+ expect(nested.all_child_paths_by(:name)).not_to be_empty
111
+ expect(nested.all_child_paths_by(:id)).to eq [[child_1.id, child_2.id]]
112
+ }
113
+ end
114
+
115
+ it 'generates a path for entries that match a value' do
116
+ vcr('association/nested_without_root_parentless_higher_include') {
117
+ nested = Nested.query.params('sys.id' => '5CNbxcca5OwgI68eSuWso2', include: 3).load.first
118
+ child_1 = nested.children.first
119
+ child_2 = child_1.children.first
120
+ expect(nested.find_child_path_by(:name, child_2.name)).not_to be_empty
121
+ expect(nested.find_child_path_by(:name, child_2.name)).to eq [[child_1.name, child_2.name]]
122
+
123
+ expect(nested.find_child_path_by(:name, 'foo')).to be_empty
124
+ }
125
+ end
126
+ end
127
+
128
+ context 'has parent and child' do
129
+ it 'parent exists' do
130
+ vcr('association/nested_without_root_middle') {
131
+ nested = Nested.find('3gpECMvSLSOC8wgkQ6GQ2q')
132
+ expect(nested.parent).to be_a Nested
133
+ expect(nested.parent.name).to eq 'Nested 1'
134
+ }
135
+ end
136
+
137
+ it 'has children' do
138
+ vcr('association/nested_without_root_middle') {
139
+ nested = Nested.find('3gpECMvSLSOC8wgkQ6GQ2q')
140
+
141
+ expect(nested.children?).to be_truthy
142
+ expect(nested.children).to be_a ::Array
143
+ expect(nested.children.size).to eq 1
144
+ expect(nested.children.first).to be_a Nested
145
+ expect(nested.children.first.name).to eq 'Nested 3'
146
+ }
147
+ end
148
+
149
+ it 'is not the root' do
150
+ vcr('association/nested_without_root_middle_parent') {
151
+ nested = Nested.find('3gpECMvSLSOC8wgkQ6GQ2q')
152
+ expect(nested.root?).to be_falsey
153
+ expect(nested.root).to eq nested.parent
154
+ }
155
+ end
156
+
157
+ it 'has ancestors' do
158
+ vcr('association/nested_without_root_middle_parent') {
159
+ nested = Nested.find('3gpECMvSLSOC8wgkQ6GQ2q')
160
+ expect(nested.ancestors).not_to be_empty
161
+ expect(nested.ancestors.size).to eq 1
162
+ expect(nested.ancestors.first.name).to eq 'Nested 1'
163
+ }
164
+ end
165
+
166
+ it 'find ancestors will return an enumerator of ancestors - if no block given' do
167
+ vcr('association/nested_without_root_middle_parent') {
168
+ nested = Nested.find('3gpECMvSLSOC8wgkQ6GQ2q')
169
+ expect(nested.find_ancestors.to_a.map(&:name)).to eq ['Nested 1']
170
+ }
171
+ end
172
+
173
+ it 'find ancestors executes the block for each ancestor - if block given' do
174
+ vcr('association/nested_without_root_middle_parent') {
175
+ nested = Nested.find('3gpECMvSLSOC8wgkQ6GQ2q')
176
+
177
+ ancestor_names = []
178
+ nested.find_ancestors { |a| ancestor_names << a.name }
179
+
180
+ expect(ancestor_names).to eq ['Nested 1']
181
+ }
182
+ end
183
+
184
+ it 'creates a tree of children relationships' do
185
+ vcr('association/nested_without_root_middle_higher_include') {
186
+ nested = Nested.query.params('sys.id' => '3gpECMvSLSOC8wgkQ6GQ2q', include: 3).load.first
187
+ child_1 = nested.children.first
188
+ expect(nested.nested_children).not_to be_empty
189
+ expect(nested.nested_children).to eq(
190
+ child_1 => nil
191
+ )
192
+ }
193
+ end
194
+
195
+ it 'creates a tree of children by field' do
196
+ vcr('association/nested_without_root_middle_higher_include') {
197
+ nested = Nested.query.params('sys.id' => '3gpECMvSLSOC8wgkQ6GQ2q', include: 3).load.first
198
+ child_1 = nested.children.first
199
+ expect(nested.nested_children_by(:name)).not_to be_empty
200
+ expect(nested.nested_children_by(:name)).to eq(
201
+ child_1.name => nil
202
+ )
203
+ }
204
+ end
205
+
206
+ it 'generates flattened structures to use as paths for entries' do
207
+ vcr('association/nested_without_root_middle_higher_include') {
208
+ nested = Nested.query.params('sys.id' => '3gpECMvSLSOC8wgkQ6GQ2q', include: 3).load.first
209
+ child_1 = nested.children.first
210
+ expect(nested.all_child_paths_by(:name)).not_to be_empty
211
+ expect(nested.all_child_paths_by(:id)).to eq [[child_1.id]]
212
+ }
213
+ end
214
+
215
+ it 'generates a path for entries that match a value' do
216
+ vcr('association/nested_without_root_middle_higher_include') {
217
+ nested = Nested.query.params('sys.id' => '3gpECMvSLSOC8wgkQ6GQ2q', include: 3).load.first
218
+ child_1 = nested.children.first
219
+ expect(nested.find_child_path_by(:name, child_1.name)).not_to be_empty
220
+ expect(nested.find_child_path_by(:name, child_1.name)).to eq [[child_1.name]]
221
+
222
+ expect(nested.find_child_path_by(:name, 'foo')).to be_empty
223
+ }
224
+ end
225
+ end
226
+
227
+ context 'only has parent' do
228
+ it 'parent exists' do
229
+ vcr('association/nested_without_root_childless') {
230
+ nested = Nested.find('2pFubMJuVqMSisGeCmoqem')
231
+ expect(nested.parent).to be_a Nested
232
+ expect(nested.parent.name).to eq 'Nested 2'
233
+ }
234
+ end
235
+
236
+ it 'has no children' do
237
+ vcr('association/nested_without_root_childless') {
238
+ nested = Nested.find('2pFubMJuVqMSisGeCmoqem')
239
+
240
+ expect(nested.children?).to be_falsey
241
+ expect(nested.children).to be_a ::Array
242
+ expect(nested.children.size).to eq 0
243
+ }
244
+ end
245
+
246
+ it 'is not the root' do
247
+ vcr('association/nested_without_root_childless') {
248
+ nested = Nested.find('2pFubMJuVqMSisGeCmoqem')
249
+ expect(nested.root?).to be_falsey
250
+ expect(nested.root.name).to eq 'Nested 1'
251
+ }
252
+ end
253
+
254
+ it 'has ancestors' do
255
+ vcr('association/nested_without_root_childless') {
256
+ nested = Nested.find('2pFubMJuVqMSisGeCmoqem')
257
+ expect(nested.ancestors).not_to be_empty
258
+ expect(nested.ancestors.size).to eq 2
259
+ }
260
+ end
261
+
262
+ it 'find ancestors will return an enumerator of ancestors - if no block given' do
263
+ vcr('association/nested_without_root_child_parent') {
264
+ nested = Nested.find('2pFubMJuVqMSisGeCmoqem')
265
+ expect(nested.find_ancestors.to_a.map(&:name)).to eq ['Nested 2', 'Nested 1']
266
+ }
267
+ end
268
+
269
+ it 'find ancestors executes the block for each ancestor - if block given' do
270
+ vcr('association/nested_without_root_child_parent') {
271
+ nested = Nested.find('2pFubMJuVqMSisGeCmoqem')
272
+
273
+ ancestor_names = []
274
+ nested.find_ancestors { |a| ancestor_names << a.name }
275
+
276
+ expect(ancestor_names).to eq ['Nested 2', 'Nested 1']
277
+ }
278
+ end
279
+
280
+ it 'creates a tree of children relationships' do
281
+ vcr('association/nested_without_root_child_higher_include') {
282
+ nested = Nested.query.params('sys.id' => '2pFubMJuVqMSisGeCmoqem', include: 3).load.first
283
+ expect(nested.nested_children).to be_empty
284
+ }
285
+ end
286
+
287
+ it 'creates a tree of children by field' do
288
+ vcr('association/nested_without_root_child_higher_include') {
289
+ nested = Nested.query.params('sys.id' => '2pFubMJuVqMSisGeCmoqem', include: 3).load.first
290
+ expect(nested.nested_children_by(:name)).to be_empty
291
+ }
292
+ end
293
+
294
+ it 'generates flattened structures to use as paths for entries' do
295
+ vcr('association/nested_without_root_child_higher_include') {
296
+ nested = Nested.query.params('sys.id' => '2pFubMJuVqMSisGeCmoqem', include: 3).load.first
297
+ expect(nested.all_child_paths_by(:name)).to be_empty
298
+ }
299
+ end
300
+
301
+ it 'generates a path for entries that match a value' do
302
+ vcr('association/nested_without_root_child_higher_include') {
303
+ nested = Nested.query.params('sys.id' => '2pFubMJuVqMSisGeCmoqem', include: 3).load.first
304
+ expect(nested.find_child_path_by(:name, 'foo')).to be_empty
305
+ }
306
+ end
307
+ end
308
+ end
309
+
310
+ context 'with root' do
311
+ it 'has root method defined' do
312
+ vcr('association/nested_with_root_root') {
313
+ non_root_id = '5sdA4Fv5o4SOeyq4Sq8qmG'
314
+ root_nested = RootNested.find(non_root_id)
315
+
316
+ expect(root_nested.name).to eq 'RootNested - 1'
317
+ expect(root_nested.root_root_nested.name).to eq 'RootNested - Root'
318
+ }
319
+ end
320
+ end
321
+ end