contentful_model 0.2.0 → 1.0.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.
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