terrestrial 0.1.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
"""
|