terrestrial 0.1.1 → 0.3.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.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/Gemfile.lock +29 -24
- data/README.md +35 -17
- data/Rakefile +4 -9
- data/TODO.md +25 -18
- data/bin/test +31 -0
- data/docs/domain_object_contract.md +50 -0
- data/features/env.rb +4 -6
- data/features/example.feature +28 -28
- data/features/step_definitions/example_steps.rb +2 -2
- data/lib/terrestrial/adapters/memory_adapter.rb +241 -0
- data/lib/terrestrial/collection_mutability_proxy.rb +7 -2
- data/lib/terrestrial/dirty_map.rb +5 -0
- data/lib/terrestrial/error.rb +69 -0
- data/lib/terrestrial/graph_loader.rb +58 -35
- data/lib/terrestrial/graph_serializer.rb +37 -30
- data/lib/terrestrial/inspection_string.rb +19 -0
- data/lib/terrestrial/lazy_collection.rb +2 -2
- data/lib/terrestrial/lazy_object_proxy.rb +1 -1
- data/lib/terrestrial/many_to_one_association.rb +17 -11
- data/lib/terrestrial/public_conveniencies.rb +125 -95
- data/lib/terrestrial/relation_mapping.rb +30 -0
- data/lib/terrestrial/{mapper_facade.rb → relational_store.rb} +11 -1
- data/lib/terrestrial/version.rb +1 -1
- data/spec/config_override_spec.rb +10 -14
- data/spec/custom_serializers_spec.rb +4 -6
- data/spec/deletion_spec.rb +12 -14
- data/spec/error_handling/factory_error_handling_spec.rb +61 -0
- data/spec/error_handling/serialization_error_spec.rb +50 -0
- data/spec/error_handling/upsert_error_spec.rb +132 -0
- data/spec/graph_persistence_spec.rb +80 -24
- data/spec/graph_traversal_spec.rb +14 -6
- data/spec/new_graph_persistence_spec.rb +43 -9
- data/spec/object_identity_spec.rb +5 -7
- data/spec/ordered_association_spec.rb +4 -6
- data/spec/predefined_queries_spec.rb +4 -6
- data/spec/querying_spec.rb +4 -12
- data/spec/readme_examples_spec.rb +3 -6
- data/spec/{persistence_efficiency_spec.rb → sequel_query_efficiency_spec.rb} +101 -19
- data/spec/spec_helper.rb +24 -2
- data/spec/support/memory_adapter_test_support.rb +21 -0
- data/spec/support/{mapper_setup.rb → object_store_setup.rb} +5 -5
- data/spec/support/seed_data_setup.rb +3 -1
- data/spec/support/sequel_test_support.rb +58 -25
- data/spec/{sequel_mapper → terrestrial}/abstract_record_spec.rb +0 -0
- data/spec/{sequel_mapper → terrestrial}/collection_mutability_proxy_spec.rb +0 -0
- data/spec/{sequel_mapper → terrestrial}/deleted_record_spec.rb +0 -0
- data/spec/{sequel_mapper → terrestrial}/dirty_map_spec.rb +38 -6
- data/spec/{sequel_mapper → terrestrial}/lazy_collection_spec.rb +2 -3
- data/spec/{sequel_mapper → terrestrial}/lazy_object_proxy_spec.rb +0 -0
- data/spec/{sequel_mapper → terrestrial}/public_conveniencies_spec.rb +12 -7
- data/spec/{sequel_mapper → terrestrial}/upserted_record_spec.rb +0 -0
- data/{sequel_mapper.gemspec → terrestrial.gemspec} +3 -3
- metadata +47 -39
- data/lib/terrestrial/short_inspection_string.rb +0 -18
- data/spec/proxying_spec.rb +0 -88
- data/spec/support/mock_sequel.rb +0 -193
- data/spec/support/sequel_persistence_setup.rb +0 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c61b139216aa29d6468f16cabdbb135d7c68cbc1
|
4
|
+
data.tar.gz: ab6960d54b3c7546f8d95a6e337efddaef166a7f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b9a11c083365c86dce49908720970407824645d40802fb5adbfd9d9b7adb8fd22f4a2b539e35edd4d630be0881413f3b0ee5063b3a20011707b33edc0687d72d
|
7
|
+
data.tar.gz: 7e3a194ff7a687b28fdf62ba993af5b83d5e4294c439d14cce8489f8f2439eadc8684842cfc557a642574076b831e6a5e6eb94347e5ee53079b41793c3349348
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.3.1
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
terrestrial (0.0
|
4
|
+
terrestrial (0.3.0)
|
5
5
|
activesupport (~> 4.0)
|
6
6
|
fetchable (~> 1.0)
|
7
7
|
sequel (~> 4.16)
|
@@ -9,49 +9,54 @@ PATH
|
|
9
9
|
GEM
|
10
10
|
remote: https://rubygems.org/
|
11
11
|
specs:
|
12
|
-
activesupport (4.2.
|
12
|
+
activesupport (4.2.7.1)
|
13
13
|
i18n (~> 0.7)
|
14
14
|
json (~> 1.7, >= 1.7.7)
|
15
15
|
minitest (~> 5.1)
|
16
16
|
thread_safe (~> 0.3, >= 0.3.4)
|
17
17
|
tzinfo (~> 1.1)
|
18
18
|
builder (3.2.2)
|
19
|
-
coderay (1.1.
|
20
|
-
cucumber (
|
19
|
+
coderay (1.1.1)
|
20
|
+
cucumber (2.4.0)
|
21
21
|
builder (>= 2.1.2)
|
22
|
+
cucumber-core (~> 1.5.0)
|
23
|
+
cucumber-wire (~> 0.0.1)
|
22
24
|
diff-lcs (>= 1.1.3)
|
23
|
-
gherkin (~>
|
25
|
+
gherkin (~> 4.0)
|
24
26
|
multi_json (>= 1.7.5, < 2.0)
|
25
27
|
multi_test (>= 0.1.2)
|
28
|
+
cucumber-core (1.5.0)
|
29
|
+
gherkin (~> 4.0)
|
30
|
+
cucumber-wire (0.0.1)
|
26
31
|
diff-lcs (1.2.5)
|
27
32
|
fetchable (1.0.0)
|
28
|
-
gherkin (
|
29
|
-
multi_json (~> 1.3)
|
33
|
+
gherkin (4.0.0)
|
30
34
|
i18n (0.7.0)
|
31
35
|
json (1.8.3)
|
32
36
|
method_source (0.8.2)
|
33
|
-
minitest (5.
|
34
|
-
multi_json (1.
|
37
|
+
minitest (5.9.1)
|
38
|
+
multi_json (1.12.1)
|
35
39
|
multi_test (0.1.2)
|
36
40
|
pg (0.17.1)
|
37
|
-
pry (0.10.
|
41
|
+
pry (0.10.4)
|
38
42
|
coderay (~> 1.1.0)
|
39
43
|
method_source (~> 0.8.1)
|
40
44
|
slop (~> 3.4)
|
41
|
-
rake (10.
|
42
|
-
rspec (3.
|
43
|
-
rspec-core (~> 3.
|
44
|
-
rspec-expectations (~> 3.
|
45
|
-
rspec-mocks (~> 3.
|
46
|
-
rspec-core (3.
|
47
|
-
rspec-support (~> 3.
|
48
|
-
rspec-expectations (3.
|
45
|
+
rake (10.5.0)
|
46
|
+
rspec (3.5.0)
|
47
|
+
rspec-core (~> 3.5.0)
|
48
|
+
rspec-expectations (~> 3.5.0)
|
49
|
+
rspec-mocks (~> 3.5.0)
|
50
|
+
rspec-core (3.5.3)
|
51
|
+
rspec-support (~> 3.5.0)
|
52
|
+
rspec-expectations (3.5.0)
|
49
53
|
diff-lcs (>= 1.2.0, < 2.0)
|
50
|
-
rspec-support (~> 3.
|
51
|
-
rspec-mocks (3.
|
52
|
-
|
53
|
-
|
54
|
-
|
54
|
+
rspec-support (~> 3.5.0)
|
55
|
+
rspec-mocks (3.5.0)
|
56
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
57
|
+
rspec-support (~> 3.5.0)
|
58
|
+
rspec-support (3.5.0)
|
59
|
+
sequel (4.39.0)
|
55
60
|
slop (3.6.0)
|
56
61
|
thread_safe (0.3.5)
|
57
62
|
tzinfo (1.2.2)
|
@@ -70,4 +75,4 @@ DEPENDENCIES
|
|
70
75
|
terrestrial!
|
71
76
|
|
72
77
|
BUNDLED WITH
|
73
|
-
1.
|
78
|
+
1.12.5
|
data/README.md
CHANGED
@@ -4,22 +4,27 @@
|
|
4
4
|
|
5
5
|
* A Ruby ORM that enables DDD and clean architectural styles.
|
6
6
|
* Persists plain objects while supporting arbitrarily deeply nested / circular associations
|
7
|
-
* Provides excellent database and query building support courtesy of [Sequel library](https://github.com/jeremyevans/sequel)
|
7
|
+
* Provides excellent database and query building support courtesy of the [Sequel library](https://github.com/jeremyevans/sequel)
|
8
8
|
|
9
9
|
Terrestrial is a new, currently experimental [data mapper](http://martinfowler.com/eaaCatalog/dataMapper.html) ORM implementation for Ruby.
|
10
10
|
|
11
11
|
The aim is to provide a convenient way to query and persist graphs of Ruby objects (think models with associations), while keeping those object completely isolated and decoupled from the database.
|
12
12
|
|
13
13
|
In contrast to Ruby's many [active record](http://martinfowler.com/eaaCatalog/activeRecord.html) implementations, domain objects require no special inherited or mixed in behavior in order to be persisted.
|
14
|
+
In fact Terrestrial has no specific requirements for domain objects at all.
|
15
|
+
While there is a simple default, `.new` and `#to_h`, you may define arbitrary
|
16
|
+
functions (per mapping) and expose no reader methods at all.
|
14
17
|
|
15
18
|
## Features
|
16
19
|
|
20
|
+
* Absolute minimum coupling between domain and persistence
|
21
|
+
* Persistence of plain or arbitrary objects
|
17
22
|
* Associations (belongs_to, has_many, has_many_through)
|
18
23
|
* Automatic 'convention over configuration' that is fully customizable
|
19
|
-
* Lazy loading
|
24
|
+
* Lazy loading of associations
|
25
|
+
* Optional eager loading to avoid the `n + 1` query problem
|
20
26
|
* Dirty tracking for database write efficiency
|
21
27
|
* Predefined queries, scopes or subsets
|
22
|
-
* Eager loading to avoid the `n + 1` query problem
|
23
28
|
|
24
29
|
There are some [conspicuous missing features](https://github.com/bestie/terrestrial/blob/master/MissingFeatures.md)
|
25
30
|
that you may want to read more about. If you want to contribute to solving any
|
@@ -69,30 +74,34 @@ code of conduct first.
|
|
69
74
|
## This is kept separate from your domain models as knowledge of the schema
|
70
75
|
## is required to wire them up.
|
71
76
|
|
72
|
-
|
77
|
+
MAPPINGS = Terrestrial.config(DB)
|
73
78
|
.setup_mapping(:users) { |users|
|
79
|
+
users.class(User) # Specify a class and the constructor will be used
|
74
80
|
users.has_many(:posts, foreign_key: :author_id)
|
75
81
|
}
|
76
82
|
.setup_mapping(:posts) { |posts|
|
83
|
+
# To avoid directly specifiying a class, a factory function can be used instead
|
84
|
+
posts.factory(->(attrs) { Post.new(attrs) })
|
77
85
|
posts.belongs_to(:author, mapping_name: :users)
|
78
86
|
posts.has_many_through(:categories)
|
79
87
|
}
|
80
88
|
.setup_mapping(:categories) { |categories|
|
89
|
+
categories.class(Category)
|
81
90
|
categories.has_many_through(:posts)
|
82
91
|
}
|
83
92
|
|
84
|
-
# 4. Create
|
93
|
+
# 4. Create an object store by combining a connection and a configuration
|
85
94
|
|
86
|
-
|
95
|
+
OBJECT_STORE = Terrestrial.object_store(
|
87
96
|
datastore: DB,
|
88
|
-
|
89
|
-
name: :users,
|
97
|
+
mappings: MAPPINGS,
|
90
98
|
)
|
91
99
|
|
92
|
-
## You are not limted to one
|
93
|
-
## To handle complex situations you may create several segregated
|
94
|
-
## for your separate aggregate roots, potentially
|
95
|
-
## databases and different domain object
|
100
|
+
## You are not limted to one object store configuration or one database
|
101
|
+
## connection. To handle complex situations you may create several segregated
|
102
|
+
## mappings and object stores for your separate aggregate roots, potentially
|
103
|
+
## utilising multiple databases and different domain object
|
104
|
+
## classes/compositions.
|
96
105
|
|
97
106
|
# 5. Create some objects
|
98
107
|
|
@@ -115,13 +124,13 @@ code of conduct first.
|
|
115
124
|
|
116
125
|
# 6. Save them
|
117
126
|
|
118
|
-
|
127
|
+
OBJECT_STORE[:users].save(user)
|
119
128
|
|
120
129
|
## Only the (aggregate) root object needs to be passed to the mapper.
|
121
130
|
|
122
131
|
# 7. Query
|
123
132
|
|
124
|
-
user =
|
133
|
+
user = OBJECT_STORE[:users].where(id: "2f0f791c-47cf-4a00-8676-e582075bcd65").first
|
125
134
|
|
126
135
|
# => #<struct User
|
127
136
|
# id="2f0f791c-47cf-4a00-8676-e582075bcd65",
|
@@ -134,7 +143,10 @@ code of conduct first.
|
|
134
143
|
|
135
144
|
## Running the tests
|
136
145
|
|
137
|
-
###
|
146
|
+
### ENV vars
|
147
|
+
|
148
|
+
The test suite expects the following standard Postgres environment variables.
|
149
|
+
|
138
150
|
* PGHOST
|
139
151
|
* PGUSER
|
140
152
|
* PGDATABASE
|
@@ -147,9 +159,15 @@ This will create a database named from the value of `PGDATABASE`
|
|
147
159
|
$ bundle exec rake db:create
|
148
160
|
```
|
149
161
|
|
150
|
-
### Run all tests
|
162
|
+
### Run all tests RSpec and Cucumber
|
163
|
+
|
164
|
+
The RSpec tests run twice, once against Sequel/Postgres and again against
|
165
|
+
an in-memory datastore.
|
166
|
+
|
167
|
+
Cucumber runs only against the Sequel/Postgres backend.
|
168
|
+
|
151
169
|
```
|
152
|
-
$
|
170
|
+
$ bin/test
|
153
171
|
```
|
154
172
|
|
155
173
|
### Should anything go awry
|
data/Rakefile
CHANGED
@@ -1,15 +1,10 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
RSpec::Core::RakeTask.new
|
7
|
-
Cucumber::Rake::Task.new
|
3
|
+
task :test_suite do
|
4
|
+
puts "Run bin/test to run the entire test suite"
|
5
|
+
end
|
8
6
|
|
9
|
-
task :default => [
|
10
|
-
:spec,
|
11
|
-
:cucumber,
|
12
|
-
]
|
7
|
+
task :default => [:test_suite]
|
13
8
|
|
14
9
|
require_relative "spec/support/sequel_test_support"
|
15
10
|
require_relative "spec/support/blog_schema"
|
data/TODO.md
CHANGED
@@ -2,29 +2,20 @@
|
|
2
2
|
|
3
3
|
In no particular order
|
4
4
|
|
5
|
-
|
5
|
+
* User defined default settings for all mappings
|
6
6
|
* Refactor, methods too big, objects missing
|
7
7
|
* Name things better
|
8
|
-
*
|
9
|
-
|
10
|
-
loaded data
|
8
|
+
* Full adaptorization of the database, refactor some Sequel specific things
|
9
|
+
into the adapter
|
11
10
|
|
12
|
-
##
|
13
|
-
*
|
14
|
-
|
15
|
-
|
16
|
-
* Add other querying methods from association proxies or remove entirely
|
17
|
-
- Depends on nailing down the querying API
|
11
|
+
## Candidate features to consider
|
12
|
+
* Column aliasing
|
13
|
+
* Callbacks e.g. after_save, after_insert as functions defined in mapping
|
14
|
+
* Database generated IDs and Timestamps (perhaps implemented as callbacks)
|
18
15
|
* When possible optimise blocks given to `AssociationProxy#select` with
|
19
16
|
Sequel's `#where` with block [querying API](http://sequel.jeremyevans.net/rdoc/files/doc/cheat_sheet_rdoc.html#label-AND%2FOR%2FNOT)
|
20
|
-
|
21
|
-
|
22
|
-
* Read only associations
|
23
|
-
- Loaded objects would be immutable
|
24
|
-
- Collection proxy would have no #push or #remove
|
25
|
-
- Skipped when dumping
|
26
|
-
* Associations defined with a join
|
27
|
-
* Composable associations
|
17
|
+
* `#eager_load!` that raises an error when traversing outside the eagerly
|
18
|
+
loaded data
|
28
19
|
|
29
20
|
# Hopefully done
|
30
21
|
|
@@ -32,10 +23,26 @@ In no particular order
|
|
32
23
|
* Efficient saving
|
33
24
|
- Part one, if it wasn't loaded it wasn't modified, check identity map
|
34
25
|
- Part two, dirty tracking
|
26
|
+
* Support swapping out DB for in memory datasets
|
35
27
|
|
36
28
|
## Associations
|
37
29
|
* Eager loading
|
38
30
|
|
31
|
+
## Querying
|
32
|
+
* Querying API, what would a repository with some arbitrary queries look like?
|
33
|
+
- e.g. an association on post called `burger_comments` that finds comments
|
34
|
+
with the word burger in them
|
35
|
+
|
39
36
|
## Configuration
|
40
37
|
* Automatic config generation based on schema, foreign keys etc
|
41
38
|
* Config to take either a classes or callable factory
|
39
|
+
|
40
|
+
# Not happening (at least for now)
|
41
|
+
|
42
|
+
## Associations
|
43
|
+
* Read only associations
|
44
|
+
- Loaded objects would be immutable
|
45
|
+
- Collection proxy would have no #push or #remove
|
46
|
+
- Skipped when dumping
|
47
|
+
* Associations defined with a join
|
48
|
+
* Composable associations
|
data/bin/test
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler"
|
4
|
+
Bundler.setup
|
5
|
+
|
6
|
+
ADAPTERS = ["memory", "sequel"]
|
7
|
+
|
8
|
+
module TerrestrialTesting
|
9
|
+
module_function def run_rspec_with_adapter(adapter)
|
10
|
+
puts "Running RSpec suite for `#{adapter}` adapter"
|
11
|
+
puts "Run following command to replicate"
|
12
|
+
puts "ADAPTER=#{adapter} bundle exec rspec"
|
13
|
+
puts ""
|
14
|
+
|
15
|
+
system("ADAPTER=#{adapter} rspec")
|
16
|
+
end
|
17
|
+
|
18
|
+
module_function def run_cucumber
|
19
|
+
system("cucumber")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
exit_successes = ADAPTERS.map do |adapter|
|
24
|
+
TerrestrialTesting.run_rspec_with_adapter(adapter)
|
25
|
+
end
|
26
|
+
|
27
|
+
if exit_successes.all?
|
28
|
+
exit_successes.push(TerrestrialTesting.run_cucumber)
|
29
|
+
end
|
30
|
+
|
31
|
+
exit exit_successes.all? ? 0 : 1
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# Domain object contract
|
2
|
+
|
3
|
+
## Configurable defaults
|
4
|
+
|
5
|
+
For saving a new or updating an existing object, Terrestrial assumes that your
|
6
|
+
domain objects implement a `#to_h` method that returns a Hash (keyed with
|
7
|
+
Symbols) of attributes that can be directly inserted into the database.
|
8
|
+
|
9
|
+
The `#to_h` interface is common in Ruby, `Struct` and `OpenStruct` both
|
10
|
+
implement this behavior as standard.
|
11
|
+
|
12
|
+
*This is the only method terrestrial will ever call on your objects*
|
13
|
+
|
14
|
+
For loading previously persisted objects, Terrestrial assumes that your domain
|
15
|
+
objects can be instantiated by calling `.new` on the specified or inferred
|
16
|
+
class with a Hash (keyed with Symbols) of attributes that match the database
|
17
|
+
column names.
|
18
|
+
|
19
|
+
This attributes Hash into constructor interface is supported by `OpenStruct`
|
20
|
+
but not by `Struct`. Terrestrial will treats `Struct` as a special case and
|
21
|
+
translates attributes into an ordered array of values. See `Terrestrial::StructAdapter`
|
22
|
+
|
23
|
+
## Custom serializers and factories
|
24
|
+
|
25
|
+
If you prefer Terrestrial not to call any methods on your domain objects this
|
26
|
+
is absolutely possible and encouraged.
|
27
|
+
|
28
|
+
A serializer function should be a lambda like object capable of converting a
|
29
|
+
domain object into a Hash (keyed by Symbols) and its persistable values.
|
30
|
+
|
31
|
+
For the default scenario detailed above Terrestrial generates the following
|
32
|
+
lambda.
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
->(domain_object) { domain_object.to_h }
|
36
|
+
```
|
37
|
+
|
38
|
+
A factory lambda will be called with same Hash of Symbol to value as above.
|
39
|
+
|
40
|
+
The default configuration would generate something like this for a users mapping.
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
->(attributes_hash) { User.new(attributes_hash) }
|
44
|
+
```
|
45
|
+
|
46
|
+
Providing bespoke functions can achieve many goals including:
|
47
|
+
* Looser coupling to Terrestrial
|
48
|
+
* Translation, modification or decoration of incoming and outgoing data
|
49
|
+
* Dynamically select a class (like Rails STI that doesn't necessarily need
|
50
|
+
inheritance)
|
data/features/env.rb
CHANGED
@@ -27,7 +27,8 @@ module ExampleRunnerSupport
|
|
27
27
|
string
|
28
28
|
.strip
|
29
29
|
.gsub(/[\n\s]+/, " ")
|
30
|
-
.gsub(
|
30
|
+
.gsub(/ \>/, ">")
|
31
|
+
.gsub(/\:0x[0-9a-f]{14}/, ":<<object id removed>>")
|
31
32
|
end
|
32
33
|
|
33
34
|
def parse_schema_table(string)
|
@@ -42,12 +43,9 @@ module ExampleRunnerSupport
|
|
42
43
|
end
|
43
44
|
|
44
45
|
module DatabaseSupport
|
45
|
-
def create_table(name,
|
46
|
+
def create_table(name, columns)
|
46
47
|
Terrestrial::SequelTestSupport.create_tables(
|
47
|
-
|
48
|
-
name => schema,
|
49
|
-
},
|
50
|
-
foreign_keys: [],
|
48
|
+
name => columns,
|
51
49
|
)
|
52
50
|
end
|
53
51
|
end
|
data/features/example.feature
CHANGED
@@ -48,28 +48,28 @@ Feature: Basic setup
|
|
48
48
|
database: ENV.fetch("PGDATABASE"),
|
49
49
|
)
|
50
50
|
"""
|
51
|
-
And the associations are defined in the
|
52
|
-
"""
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
"""
|
68
|
-
And a
|
69
|
-
"""
|
70
|
-
|
51
|
+
And the associations are defined in the configuration
|
52
|
+
"""
|
53
|
+
MAPPINGS = Terrestrial.config(DB)
|
54
|
+
.setup_mapping(:users) { |users|
|
55
|
+
users.class(User)
|
56
|
+
users.has_many(:posts, foreign_key: :author_id)
|
57
|
+
}
|
58
|
+
.setup_mapping(:posts) { |posts|
|
59
|
+
posts.class(Post)
|
60
|
+
posts.belongs_to(:author, mapping_name: :users)
|
61
|
+
posts.has_many_through(:categories)
|
62
|
+
}
|
63
|
+
.setup_mapping(:categories) { |categories|
|
64
|
+
categories.class(Category)
|
65
|
+
categories.has_many_through(:posts)
|
66
|
+
}
|
67
|
+
"""
|
68
|
+
And a object store is instantiated
|
69
|
+
"""
|
70
|
+
OBJECT_STORE = Terrestrial.object_store(
|
71
71
|
datastore: DB,
|
72
|
-
mappings:
|
72
|
+
mappings: MAPPINGS,
|
73
73
|
)
|
74
74
|
"""
|
75
75
|
When a new graph of objects are created
|
@@ -93,11 +93,11 @@ Feature: Basic setup
|
|
93
93
|
"""
|
94
94
|
And the new graph is saved
|
95
95
|
"""
|
96
|
-
|
96
|
+
OBJECT_STORE[:users].save(user)
|
97
97
|
"""
|
98
98
|
And the following query is executed
|
99
99
|
"""
|
100
|
-
user =
|
100
|
+
user = OBJECT_STORE[:users].where(id: "2f0f791c-47cf-4a00-8676-e582075bcd65").first
|
101
101
|
"""
|
102
102
|
Then the persisted user object is returned with lazy associations
|
103
103
|
"""
|
@@ -105,16 +105,16 @@ Feature: Basic setup
|
|
105
105
|
first_name="Hansel",
|
106
106
|
last_name="Trickett",
|
107
107
|
email="hansel@tricketts.org",
|
108
|
-
posts=#<Terrestrial::CollectionMutabilityProxy:
|
109
|
-
|
108
|
+
posts=#<Terrestrial::CollectionMutabilityProxy:0x007f9d8aa93bf0>
|
109
|
+
>
|
110
110
|
"""
|
111
111
|
And the user's posts will be loaded once the association proxy receives an Enumerable message
|
112
112
|
"""
|
113
113
|
[#<struct Post id="9b75fe2b-d694-4b90-9137-6201d426dda2",
|
114
|
-
author=#<Terrestrial::LazyObjectProxy:
|
114
|
+
author=#<Terrestrial::LazyObjectProxy:0x007f81b2b86d30 key_fields={:id=>"2f0f791c-47cf-4a00-8676-e582075bcd65"} lazy_object=nil>,
|
115
115
|
subject="Things that I like",
|
116
116
|
body="I like fish and scratching",
|
117
117
|
created_at=2015-10-03 21:00:00 UTC,
|
118
|
-
categories=#<Terrestrial::CollectionMutabilityProxy:
|
119
|
-
|
118
|
+
categories=#<Terrestrial::CollectionMutabilityProxy:0x007f9d8ad88d38>
|
119
|
+
>]
|
120
120
|
"""
|