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.
- checksums.yaml +5 -5
- data/lib/contentful_model.rb +17 -13
- data/lib/contentful_model/asset.rb +30 -0
- data/lib/contentful_model/asset_dimension_query.rb +70 -0
- data/lib/contentful_model/asset_dimensions.rb +54 -0
- data/lib/contentful_model/associations/associations.rb +10 -4
- data/lib/contentful_model/associations/belongs_to.rb +9 -4
- data/lib/contentful_model/associations/belongs_to_many.rb +22 -45
- data/lib/contentful_model/associations/has_many.rb +15 -13
- data/lib/contentful_model/associations/has_many_nested.rb +39 -37
- data/lib/contentful_model/associations/has_one.rb +22 -14
- data/lib/contentful_model/base.rb +115 -74
- data/lib/contentful_model/client.rb +14 -3
- data/lib/contentful_model/errors.rb +0 -1
- data/lib/contentful_model/manageable.rb +49 -21
- data/lib/contentful_model/management.rb +1 -0
- data/lib/contentful_model/migrations/content_type.rb +24 -24
- data/lib/contentful_model/migrations/content_type_factory.rb +9 -6
- data/lib/contentful_model/migrations/migration.rb +9 -4
- data/lib/contentful_model/queries.rb +60 -9
- data/lib/contentful_model/query.rb +148 -5
- data/lib/contentful_model/validations/lambda_validation.rb +17 -0
- data/lib/contentful_model/validations/validates_presence_of.rb +4 -1
- data/lib/contentful_model/validations/validations.rb +56 -8
- data/lib/contentful_model/version.rb +1 -1
- data/spec/asset_spec.rb +141 -0
- data/spec/associations/belongs_to_many_spec.rb +38 -0
- data/spec/associations/belongs_to_spec.rb +22 -0
- data/spec/associations/has_many_nested_spec.rb +321 -0
- data/spec/associations/has_many_spec.rb +33 -0
- data/spec/associations/has_one_spec.rb +32 -0
- data/spec/base_spec.rb +199 -0
- data/spec/chainable_queries_spec.rb +199 -0
- data/spec/client_spec.rb +11 -0
- data/spec/contentful_model_spec.rb +25 -0
- data/spec/fixtures/vcr_cassettes/asset/all.yml +161 -0
- data/spec/fixtures/vcr_cassettes/asset/find.yml +118 -0
- data/spec/fixtures/vcr_cassettes/association/belongs_to_many.yml +439 -0
- data/spec/fixtures/vcr_cassettes/association/has_many.yml +161 -0
- data/spec/fixtures/vcr_cassettes/association/has_one.yml +161 -0
- data/spec/fixtures/vcr_cassettes/association/nested_with_root_root.yml +240 -0
- data/spec/fixtures/vcr_cassettes/association/nested_without_root_child_higher_include.yml +123 -0
- data/spec/fixtures/vcr_cassettes/association/nested_without_root_child_parent.yml +609 -0
- data/spec/fixtures/vcr_cassettes/association/nested_without_root_childless.yml +688 -0
- data/spec/fixtures/vcr_cassettes/association/nested_without_root_middle.yml +319 -0
- data/spec/fixtures/vcr_cassettes/association/nested_without_root_middle_higher_include.yml +161 -0
- data/spec/fixtures/vcr_cassettes/association/nested_without_root_middle_parent.yml +489 -0
- data/spec/fixtures/vcr_cassettes/association/nested_without_root_parentless.yml +331 -0
- data/spec/fixtures/vcr_cassettes/association/nested_without_root_parentless_higher_include.yml +82 -0
- data/spec/fixtures/vcr_cassettes/associations/nested_without_root_middle.yml +161 -0
- data/spec/fixtures/vcr_cassettes/associations/nested_without_root_middle_higher_include.yml +82 -0
- data/spec/fixtures/vcr_cassettes/base/content_type.yml +185 -0
- data/spec/fixtures/vcr_cassettes/base/return_nil_for_empty.yml +202 -0
- data/spec/fixtures/vcr_cassettes/base/return_nil_for_empty_with_value.yml +124 -0
- data/spec/fixtures/vcr_cassettes/client.yml +98 -0
- data/spec/fixtures/vcr_cassettes/dog.yml +161 -0
- data/spec/fixtures/vcr_cassettes/human.yml +199 -0
- data/spec/fixtures/vcr_cassettes/management/client.yml +1362 -0
- data/spec/fixtures/vcr_cassettes/management/nyancat.yml +1449 -0
- data/spec/fixtures/vcr_cassettes/management/nyancat_2.yml +1449 -0
- data/spec/fixtures/vcr_cassettes/management/nyancat_publish.yml +481 -0
- data/spec/fixtures/vcr_cassettes/management/nyancat_refetch_and_fail.yml +558 -0
- data/spec/fixtures/vcr_cassettes/management/nyancat_refetch_and_save.yml +620 -0
- data/spec/fixtures/vcr_cassettes/management/nyancat_save.yml +479 -0
- data/spec/fixtures/vcr_cassettes/management/nyancat_save_2.yml +479 -0
- data/spec/fixtures/vcr_cassettes/nyancat.yml +196 -0
- data/spec/fixtures/vcr_cassettes/playground/nyancat.yml +177 -0
- data/spec/fixtures/vcr_cassettes/query/each_entry.yml +319 -0
- data/spec/fixtures/vcr_cassettes/query/each_page.yml +319 -0
- data/spec/fixtures/vcr_cassettes/query/empty.yml +180 -0
- data/spec/fixtures/vcr_cassettes/query/load.yml +197 -0
- data/spec/fixtures/vcr_cassettes/query/manual_pagination.yml +161 -0
- data/spec/fixtures/vcr_cassettes/query/nyancat_invalid_elements.yml +247 -0
- data/spec/manageable_spec.rb +186 -0
- data/spec/management_spec.rb +17 -0
- data/spec/migrations/content_type_factory_spec.rb +41 -0
- data/spec/migrations/content_type_spec.rb +176 -0
- data/spec/migrations/migration_spec.rb +75 -0
- data/spec/queries_spec.rb +85 -0
- data/spec/query_spec.rb +126 -0
- data/spec/spec_helper.rb +58 -0
- data/spec/validations/validations_spec.rb +182 -0
- metadata +213 -19
- 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
|
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
|
-
|
27
|
-
|
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
|
data/spec/asset_spec.rb
ADDED
@@ -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
|