terrestrial 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -9
  3. data/.rspec +2 -0
  4. data/.ruby-version +1 -0
  5. data/CODE_OF_CONDUCT.md +28 -0
  6. data/Gemfile.lock +73 -0
  7. data/LICENSE.txt +22 -0
  8. data/MissingFeatures.md +64 -0
  9. data/README.md +161 -16
  10. data/Rakefile +30 -0
  11. data/TODO.md +41 -0
  12. data/features/env.rb +60 -0
  13. data/features/example.feature +120 -0
  14. data/features/step_definitions/example_steps.rb +46 -0
  15. data/lib/terrestrial/abstract_record.rb +99 -0
  16. data/lib/terrestrial/association_loaders.rb +52 -0
  17. data/lib/terrestrial/collection_mutability_proxy.rb +81 -0
  18. data/lib/terrestrial/configurations/conventional_association_configuration.rb +186 -0
  19. data/lib/terrestrial/configurations/conventional_configuration.rb +302 -0
  20. data/lib/terrestrial/dataset.rb +49 -0
  21. data/lib/terrestrial/deleted_record.rb +20 -0
  22. data/lib/terrestrial/dirty_map.rb +42 -0
  23. data/lib/terrestrial/graph_loader.rb +63 -0
  24. data/lib/terrestrial/graph_serializer.rb +91 -0
  25. data/lib/terrestrial/identity_map.rb +22 -0
  26. data/lib/terrestrial/lazy_collection.rb +74 -0
  27. data/lib/terrestrial/lazy_object_proxy.rb +55 -0
  28. data/lib/terrestrial/many_to_many_association.rb +138 -0
  29. data/lib/terrestrial/many_to_one_association.rb +66 -0
  30. data/lib/terrestrial/mapper_facade.rb +137 -0
  31. data/lib/terrestrial/one_to_many_association.rb +66 -0
  32. data/lib/terrestrial/public_conveniencies.rb +139 -0
  33. data/lib/terrestrial/query_order.rb +32 -0
  34. data/lib/terrestrial/relation_mapping.rb +50 -0
  35. data/lib/terrestrial/serializer.rb +18 -0
  36. data/lib/terrestrial/short_inspection_string.rb +18 -0
  37. data/lib/terrestrial/struct_factory.rb +17 -0
  38. data/lib/terrestrial/subset_queries_proxy.rb +11 -0
  39. data/lib/terrestrial/upserted_record.rb +15 -0
  40. data/lib/terrestrial/version.rb +1 -1
  41. data/lib/terrestrial.rb +5 -2
  42. data/sequel_mapper.gemspec +31 -0
  43. data/spec/config_override_spec.rb +193 -0
  44. data/spec/custom_serializers_spec.rb +49 -0
  45. data/spec/deletion_spec.rb +101 -0
  46. data/spec/graph_persistence_spec.rb +313 -0
  47. data/spec/graph_traversal_spec.rb +121 -0
  48. data/spec/new_graph_persistence_spec.rb +71 -0
  49. data/spec/object_identity_spec.rb +70 -0
  50. data/spec/ordered_association_spec.rb +51 -0
  51. data/spec/persistence_efficiency_spec.rb +224 -0
  52. data/spec/predefined_queries_spec.rb +62 -0
  53. data/spec/proxying_spec.rb +88 -0
  54. data/spec/querying_spec.rb +48 -0
  55. data/spec/readme_examples_spec.rb +35 -0
  56. data/spec/sequel_mapper/abstract_record_spec.rb +244 -0
  57. data/spec/sequel_mapper/collection_mutability_proxy_spec.rb +135 -0
  58. data/spec/sequel_mapper/deleted_record_spec.rb +59 -0
  59. data/spec/sequel_mapper/dirty_map_spec.rb +214 -0
  60. data/spec/sequel_mapper/lazy_collection_spec.rb +119 -0
  61. data/spec/sequel_mapper/lazy_object_proxy_spec.rb +140 -0
  62. data/spec/sequel_mapper/public_conveniencies_spec.rb +58 -0
  63. data/spec/sequel_mapper/upserted_record_spec.rb +59 -0
  64. data/spec/spec_helper.rb +36 -0
  65. data/spec/support/blog_schema.rb +38 -0
  66. data/spec/support/have_persisted_matcher.rb +19 -0
  67. data/spec/support/mapper_setup.rb +221 -0
  68. data/spec/support/mock_sequel.rb +193 -0
  69. data/spec/support/object_graph_setup.rb +139 -0
  70. data/spec/support/seed_data_setup.rb +165 -0
  71. data/spec/support/sequel_persistence_setup.rb +19 -0
  72. data/spec/support/sequel_test_support.rb +166 -0
  73. metadata +207 -13
  74. data/.travis.yml +0 -4
  75. data/bin/console +0 -14
  76. data/bin/setup +0 -7
  77. data/terrestrial.gemspec +0 -23
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5d357727f08469741639890993a24a4b7e319269
4
- data.tar.gz: 4c623940088221dbab9f78f25d0cf747b6d62c68
3
+ metadata.gz: 0ba5176dafbe142a9b5f25e00902eec58bd5b858
4
+ data.tar.gz: 8674e2b27c6548db3d39b05cd095c6c4d5b9a132
5
5
  SHA512:
6
- metadata.gz: f1f8c89883b65df9b38bd3f2a38b5f7f8ea4e31c19cfb94c932805229aab9f1486ea21036f1cc3192cdc4e023eed20514fd7c7c8774616c884f077a2be7ecec1
7
- data.tar.gz: a105ec0392cdc89a45253f8851a965f1e85e2b86eabbb3fdfc2b9c6d913dbddc12e49417f747f9a041062badb752794c13060294408cae0f313591db7a55d2c0
6
+ metadata.gz: fb86ee61ccd132a3b1bcaa99b0ab225e59b387cd80c1425d09e847c5f5d52b48610287ae95039d249f8d366fc13abc2045d9ba213eeb963a694f11fb3a068ec0
7
+ data.tar.gz: 96c6cfc35fdeba513882de149e1dc751bd6c20628a2743555d1be451b637745dc5ba690c3bcf9db0ac4a7b2a5d52a830d6d9ab8efa8086f89451d2b2ad2ed333
data/.gitignore CHANGED
@@ -1,9 +1 @@
1
- /.bundle/
2
- /.yardoc
3
- /Gemfile.lock
4
- /_yardoc/
5
- /coverage/
6
- /doc/
7
- /pkg/
8
- /spec/reports/
9
- /tmp/
1
+ .env
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.2.3
@@ -0,0 +1,28 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, we pledge to respect all
4
+ people who contribute through reporting issues, posting feature requests,
5
+ updating documentation, submitting pull requests or patches, and other
6
+ activities.
7
+
8
+ We are committed to making participation in this project a harassment-free
9
+ experience for everyone, regardless of level of experience, gender, gender
10
+ identity and expression, sexual orientation, disability, personal appearance,
11
+ body size, race, age, or religion.
12
+
13
+ Examples of unacceptable behavior by participants include the use of sexual
14
+ language or imagery, derogatory comments or personal attacks, trolling, public
15
+ or private harassment, insults, or other unprofessional conduct.
16
+
17
+ Project maintainers have the right and responsibility to remove, edit, or
18
+ reject comments, commits, code, wiki edits, issues, and other contributions
19
+ that are not aligned to this Code of Conduct. Project maintainers who do not
20
+ follow the Code of Conduct may be removed from the project team.
21
+
22
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
23
+ reported by opening an issue or contacting one or more of the project
24
+ maintainers.
25
+
26
+ This Code of Conduct is adapted from the [Contributor
27
+ Covenant](http:contributor-covenant.org), version 1.0.0, available at
28
+ [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
data/Gemfile.lock ADDED
@@ -0,0 +1,73 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ terrestrial (0.0.3)
5
+ activesupport (~> 4.0)
6
+ fetchable (~> 1.0)
7
+ sequel (~> 4.16)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ activesupport (4.2.4)
13
+ i18n (~> 0.7)
14
+ json (~> 1.7, >= 1.7.7)
15
+ minitest (~> 5.1)
16
+ thread_safe (~> 0.3, >= 0.3.4)
17
+ tzinfo (~> 1.1)
18
+ builder (3.2.2)
19
+ coderay (1.1.0)
20
+ cucumber (1.3.20)
21
+ builder (>= 2.1.2)
22
+ diff-lcs (>= 1.1.3)
23
+ gherkin (~> 2.12)
24
+ multi_json (>= 1.7.5, < 2.0)
25
+ multi_test (>= 0.1.2)
26
+ diff-lcs (1.2.5)
27
+ fetchable (1.0.0)
28
+ gherkin (2.12.2)
29
+ multi_json (~> 1.3)
30
+ i18n (0.7.0)
31
+ json (1.8.3)
32
+ method_source (0.8.2)
33
+ minitest (5.8.3)
34
+ multi_json (1.11.1)
35
+ multi_test (0.1.2)
36
+ pg (0.17.1)
37
+ pry (0.10.1)
38
+ coderay (~> 1.1.0)
39
+ method_source (~> 0.8.1)
40
+ slop (~> 3.4)
41
+ rake (10.1.0)
42
+ rspec (3.1.0)
43
+ rspec-core (~> 3.1.0)
44
+ rspec-expectations (~> 3.1.0)
45
+ rspec-mocks (~> 3.1.0)
46
+ rspec-core (3.1.7)
47
+ rspec-support (~> 3.1.0)
48
+ rspec-expectations (3.1.2)
49
+ diff-lcs (>= 1.2.0, < 2.0)
50
+ rspec-support (~> 3.1.0)
51
+ rspec-mocks (3.1.3)
52
+ rspec-support (~> 3.1.0)
53
+ rspec-support (3.1.2)
54
+ sequel (4.26.0)
55
+ slop (3.6.0)
56
+ thread_safe (0.3.5)
57
+ tzinfo (1.2.2)
58
+ thread_safe (~> 0.1)
59
+
60
+ PLATFORMS
61
+ ruby
62
+
63
+ DEPENDENCIES
64
+ bundler (~> 1.7)
65
+ cucumber
66
+ pg (~> 0.17.1)
67
+ pry (~> 0.10.1)
68
+ rake (~> 10.0)
69
+ rspec (~> 3.1)
70
+ terrestrial!
71
+
72
+ BUNDLED WITH
73
+ 1.10.6
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Stephen Best
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,64 @@
1
+ # Missing features
2
+
3
+ The following features not included in Terrestrial are omitted purposefully to
4
+ keep the library simple and encourage good practices in application code.
5
+
6
+ Please open an issue if you feel like any of these features are essential or if
7
+ you think you can contribute to a solution please open an issue to discuss.
8
+
9
+ ## Coercion
10
+
11
+ Database supported types will be returned as expected, `Fixnum`, `DateTime`, `nil` etc.
12
+ Should you wish to enhance this data, every row is passed into the mapping's
13
+ factory function where you have the opportunity to do arbitrary transformations
14
+ before instantiating the domain object.
15
+
16
+ \*see note on transforming row data
17
+
18
+ ## Validation
19
+
20
+ This is the concern of your domain model and/or application boundaries.
21
+ Terrestrial allows you to persist any object you wish assuming schema
22
+ compatibility.
23
+
24
+ ## Database column name aliasing
25
+
26
+ While at first glance this is a simple feature, the abstraction starts to leak
27
+ when the using the query interface and guaranteeing all queries are substituted
28
+ perfectly is beyond the scope of the current version.
29
+
30
+ Should you wish to simply pass a column's key with a different parameter name
31
+ then you can again lean on the factory function to transform the row's data
32
+ before the domain object receives it.
33
+
34
+ \*see note on transforming row data
35
+
36
+ ## Cascade deletion
37
+
38
+ This is chiefly a data concern and is handled by a good database more
39
+ efficiently and effectively than any ORM could hope.
40
+
41
+ ## Database generated IDs and timestamps
42
+
43
+ While database generated values may work, available only after an object is
44
+ retrieved, they are not currently supported.
45
+
46
+ Data important to your domain should be generated in your application layer.
47
+ UUIDs make much more flexible identifiers for domain objects and further enable
48
+ decoupling and fast tests.
49
+
50
+ Timestamps are useful and important to most applications however if they are
51
+ used in your domain they should be pushed from explicitly from application
52
+ layer. You should again find this affords you more flexibility and decoupling.
53
+
54
+ There is absolutely nothing wrong with data added at time of persistence for
55
+ auditing purposes but Terrestrial will make you actively decide whether this
56
+ data should be available to the domain and what should be explicitly added.
57
+
58
+ \* Transforming row data
59
+
60
+ Adding a custom factory method to transform row data before passing it to the
61
+ domain layer is highly encouraged. However, ensure that for each custom factory
62
+ a serializer function is also supplied that Terrestrial can use to reverse the
63
+ operation for persistence.
64
+
data/README.md CHANGED
@@ -1,36 +1,181 @@
1
1
  # Terrestrial
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/terrestrial`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ ## TL;DR
4
4
 
5
- TODO: Delete this and the text above, and describe your gem
5
+ * A Ruby ORM that enables DDD and clean architectural styles.
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)
6
8
 
7
- ## Installation
9
+ Terrestrial is a new, currently experimental [data mapper](http://martinfowler.com/eaaCatalog/dataMapper.html) ORM implementation for Ruby.
10
+
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
+
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
+
15
+ ## Features
16
+
17
+ * Associations (belongs_to, has_many, has_many_through)
18
+ * Automatic 'convention over configuration' that is fully customizable
19
+ * Lazy loading for database read efficiency
20
+ * Dirty tracking for database write efficiency
21
+ * Predefined queries, scopes or subsets
22
+ * Eager loading to avoid the `n + 1` query problem
23
+
24
+ There are some [conspicuous missing features](https://github.com/bestie/terrestrial/blob/master/MissingFeatures.md)
25
+ that you may want to read more about. If you want to contribute to solving any
26
+ of the problems listed please open an issue to discuss.
27
+
28
+ Terrestrial does not reinvent the wheel with querying abstraction and
29
+ migrations, instead these responsibilities are delegated to Sequel such that
30
+ its full power can be utilised.
8
31
 
9
- Add this line to your application's Gemfile:
32
+ For [querying](http://sequel.jeremyevans.net/rdoc/files/doc/querying_rdoc.html),
33
+ [migrations](http://sequel.jeremyevans.net/rdoc/files/doc/migration_rdoc.html)
34
+ and creating your [database connection](http://sequel.jeremyevans.net/rdoc/files/doc/opening_databases_rdoc.html)
35
+ see the Sequel documentation.
36
+
37
+ ## Getting started
38
+
39
+ Please try this out, experiment, open issues and pull requests. Please read the
40
+ code of conduct first.
10
41
 
11
42
  ```ruby
12
- gem 'terrestrial'
43
+
44
+ # 1. Define some domain objects, structs will surfice for the example
45
+
46
+ User = Struct.new(:id, :first_name, :last_name, :email, :posts)
47
+ Post = Struct.new(:id, :author, :subject, :body, :created_at, :categories)
48
+ Category = Struct.new(:id, :name, :posts)
49
+
50
+ ## Also assume that a conventional database schema (think Rails) is in place,
51
+ ## a column for each of the struct's attributes will be present. The posts
52
+ ## table will have `author_id` as a foreign key to the users table. There is
53
+ ## a join table named `categories_to_posts` which facilitates the many to
54
+ ## many relationship.
55
+
56
+ # 2. Configure a Sequel database connection
57
+
58
+ ## Terrestrial does not manage your connection for you.
59
+ ## Example assumes Postgres however Sequel supports many other databases.
60
+
61
+ DB = Sequel.postgres(
62
+ host: ENV.fetch("PGHOST"),
63
+ user: ENV.fetch("PGUSER"),
64
+ database: ENV.fetch("PGDATABASE"),
65
+ )
66
+
67
+ # 3. Configure mappings and associations
68
+
69
+ ## This is kept separate from your domain models as knowledge of the schema
70
+ ## is required to wire them up.
71
+
72
+ USER_MAPPER_CONFIG = Terrestrial.config(DB)
73
+ .setup_mapping(:users) { |users|
74
+ users.has_many(:posts, foreign_key: :author_id)
75
+ }
76
+ .setup_mapping(:posts) { |posts|
77
+ posts.belongs_to(:author, mapping_name: :users)
78
+ posts.has_many_through(:categories)
79
+ }
80
+ .setup_mapping(:categories) { |categories|
81
+ categories.has_many_through(:posts)
82
+ }
83
+
84
+ # 4. Create a mapper by combining a connection and a configuration
85
+
86
+ USER_MAPPER = Terrestrial.mapper(
87
+ datastore: DB,
88
+ config: USER_MAPPER_CONFIG,
89
+ name: :users,
90
+ )
91
+
92
+ ## You are not limted to one mapper configuration or one database connection.
93
+ ## To handle complex situations you may create several segregated mappings
94
+ ## for your separate aggregate roots, potentially utilising multiple
95
+ ## databases and different domain object classes/compositions.
96
+
97
+ # 5. Create some objects
98
+
99
+ user = User.new(
100
+ "2f0f791c-47cf-4a00-8676-e582075bcd65",
101
+ "Hansel",
102
+ "Trickett",
103
+ "hansel@tricketts.org",
104
+ [],
105
+ )
106
+
107
+ user.posts << Post.new(
108
+ "9b75fe2b-d694-4b90-9137-6201d426dda2",
109
+ user,
110
+ "Things that I like",
111
+ "I like fish and scratching",
112
+ Time.parse("2015-10-03 21:00:00 UTC"),
113
+ [],
114
+ )
115
+
116
+ # 6. Save them
117
+
118
+ USER_MAPPER.save(user)
119
+
120
+ ## Only the (aggregate) root object needs to be passed to the mapper.
121
+
122
+ # 7. Query
123
+
124
+ user = USER_MAPPER.where(id: "2f0f791c-47cf-4a00-8676-e582075bcd65").first
125
+
126
+ # => #<struct User
127
+ # id="2f0f791c-47cf-4a00-8676-e582075bcd65",
128
+ # first_name="Stephen",
129
+ # last_name="Best",
130
+ # email="bestie@gmail.com",
131
+ # posts=#<Terrestrial::CollectionMutabilityProxy:7ff57192d510 >,
132
+
13
133
  ```
14
134
 
15
- And then execute:
135
+ ## Running the tests
16
136
 
17
- $ bundle
137
+ ### Set the following environment variables
138
+ * PGHOST
139
+ * PGUSER
140
+ * PGDATABASE
18
141
 
19
- Or install it yourself as:
142
+ ### Create a test database
20
143
 
21
- $ gem install terrestrial
144
+ This will create a database named from the value of `PGDATABASE`
145
+
146
+ ```
147
+ $ bundle exec rake db:create
148
+ ```
149
+
150
+ ### Run all tests (RSpec and Cucumber)
151
+ ```
152
+ $ bundle exec rake
153
+ ```
22
154
 
23
- ## Usage
155
+ ### Should anything go awry
24
156
 
25
- TODO: Write usage instructions here
157
+ Drop the test database and start fresh
26
158
 
27
- ## Development
159
+ ```
160
+ $ bundle exec rake db:drop
161
+ ```
28
162
 
29
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake false` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
163
+ ## Installation
30
164
 
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
165
+ This library is still pre 1.0 so please lock down your version and update with
166
+ care.
32
167
 
33
- ## Contributing
168
+ Add the following to your `Gemfile`.
169
+
170
+ ```
171
+ gem "terrestrial", "0.0.3"
172
+ ```
34
173
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/terrestrial.
174
+ And then execute:
175
+
176
+ $ bundle
177
+
178
+ Or install it manually:
179
+
180
+ $ gem install terrestrial
36
181
 
data/Rakefile CHANGED
@@ -1 +1,31 @@
1
1
  require "bundler/gem_tasks"
2
+
3
+ require 'rspec/core/rake_task'
4
+ require 'cucumber/rake/task'
5
+
6
+ RSpec::Core::RakeTask.new
7
+ Cucumber::Rake::Task.new
8
+
9
+ task :default => [
10
+ :spec,
11
+ :cucumber,
12
+ ]
13
+
14
+ require_relative "spec/support/sequel_test_support"
15
+ require_relative "spec/support/blog_schema"
16
+
17
+ namespace :db do
18
+ include Terrestrial::SequelTestSupport
19
+
20
+ task :setup => [:create] do
21
+ create_tables(BLOG_SCHEMA)
22
+ end
23
+
24
+ task :create do
25
+ create_database
26
+ end
27
+
28
+ task :drop do
29
+ drop_database
30
+ end
31
+ end
data/TODO.md ADDED
@@ -0,0 +1,41 @@
1
+ # TODOs
2
+
3
+ In no particular order
4
+
5
+ ## General
6
+ * Refactor, methods too big, objects missing
7
+ * Name things better
8
+ * Better support swapping out DB for in memory datasets
9
+ * `#eager_load!` that raises an error when traversing outside the eagerly
10
+ loaded data
11
+
12
+ ## Querying
13
+ * Querying API, what would a repository with some arbitrary queries look like?
14
+ - e.g. an association on post called `burger_comments` that finds comments
15
+ with the word burger in them
16
+ * Add other querying methods from association proxies or remove entirely
17
+ - Depends on nailing down the querying API
18
+ * When possible optimise blocks given to `AssociationProxy#select` with
19
+ Sequel's `#where` with block [querying API](http://sequel.jeremyevans.net/rdoc/files/doc/cheat_sheet_rdoc.html#label-AND%2FOR%2FNOT)
20
+
21
+ ## Associations
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
28
+
29
+ # Hopefully done
30
+
31
+ ## Persistence
32
+ * Efficient saving
33
+ - Part one, if it wasn't loaded it wasn't modified, check identity map
34
+ - Part two, dirty tracking
35
+
36
+ ## Associations
37
+ * Eager loading
38
+
39
+ ## Configuration
40
+ * Automatic config generation based on schema, foreign keys etc
41
+ * Config to take either a classes or callable factory
data/features/env.rb ADDED
@@ -0,0 +1,60 @@
1
+ require "pry"
2
+ require "sequel"
3
+ require "terrestrial"
4
+ require_relative "../spec/support/sequel_test_support"
5
+
6
+ module ExampleRunnerSupport
7
+ def example_eval_concat(code_strings)
8
+ example_eval(code_strings.join("\n"))
9
+ end
10
+
11
+ def example_eval(code_string)
12
+ example_module.module_eval(code_string)
13
+ rescue Object => e
14
+ binding.pry if ENV["DEBUG"]
15
+ raise e
16
+ end
17
+
18
+ def example_exec(&block)
19
+ example_exec.module_eval(&block)
20
+ end
21
+
22
+ def example_module
23
+ @example_module ||= Module.new
24
+ end
25
+
26
+ def normalise_inspection_string(string)
27
+ string
28
+ .strip
29
+ .gsub(/[\n\s]+/, " ")
30
+ .gsub(/\:[0-9a-f]{12}/, ":<<object id removed>>")
31
+ end
32
+
33
+ def parse_schema_table(string)
34
+ string.each_line.drop(2).map { |line|
35
+ name, type = line.split("|").map(&:strip)
36
+ {
37
+ name: name,
38
+ type: Object.const_get(type),
39
+ }
40
+ }
41
+ end
42
+ end
43
+
44
+ module DatabaseSupport
45
+ def create_table(name, schema)
46
+ Terrestrial::SequelTestSupport.create_tables(
47
+ tables: {
48
+ name => schema,
49
+ },
50
+ foreign_keys: [],
51
+ )
52
+ end
53
+ end
54
+
55
+ Before do
56
+ Terrestrial::SequelTestSupport.drop_tables
57
+ end
58
+
59
+ World(ExampleRunnerSupport)
60
+ World(DatabaseSupport)
@@ -0,0 +1,120 @@
1
+ Feature: Basic setup
2
+
3
+ Scenario: Setup with conventional configuration
4
+ Given the domain objects are defined
5
+ """
6
+ User = Struct.new(:id, :first_name, :last_name, :email, :posts)
7
+ Post = Struct.new(:id, :author, :subject, :body, :created_at, :categories)
8
+ Category = Struct.new(:id, :name, :posts)
9
+ """
10
+ And a conventionally similar database schema for table "users"
11
+ """
12
+ Column | Type
13
+ ------------ +---------
14
+ id | String
15
+ first_name | String
16
+ last_name | String
17
+ email | String
18
+ """
19
+ And a conventionally similar database schema for table "posts"
20
+ """
21
+ Column | Type
22
+ -------------+---------
23
+ id | String
24
+ author_id | String
25
+ subject | String
26
+ body | String
27
+ created_at | DateTime
28
+ """
29
+ And a conventionally similar database schema for table "categories"
30
+ """
31
+ Column | Type
32
+ -------------+---------
33
+ id | String
34
+ name | String
35
+ """
36
+ And a conventionally similar database schema for table "categories_to_posts"
37
+ """
38
+ Column | Type
39
+ -------------+---------
40
+ post_id | String
41
+ category_id | String
42
+ """
43
+ And a database connection is established
44
+ """
45
+ DB = Sequel.postgres(
46
+ host: ENV.fetch("PGHOST"),
47
+ user: ENV.fetch("PGUSER"),
48
+ database: ENV.fetch("PGDATABASE"),
49
+ )
50
+ """
51
+ And the associations are defined in the mapper configuration
52
+ """
53
+ MAPPINGS_CONFIG = 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 mapper is instantiated
69
+ """
70
+ MAPPERS = Terrestrial.mappers(
71
+ datastore: DB,
72
+ mappings: MAPPINGS_CONFIG,
73
+ )
74
+ """
75
+ When a new graph of objects are created
76
+ """
77
+ user = User.new(
78
+ "2f0f791c-47cf-4a00-8676-e582075bcd65",
79
+ "Hansel",
80
+ "Trickett",
81
+ "hansel@tricketts.org",
82
+ [],
83
+ )
84
+
85
+ user.posts << Post.new(
86
+ "9b75fe2b-d694-4b90-9137-6201d426dda2",
87
+ user,
88
+ "Things that I like",
89
+ "I like fish and scratching",
90
+ Time.parse("2015-10-03 21:00:00 UTC"),
91
+ [],
92
+ )
93
+ """
94
+ And the new graph is saved
95
+ """
96
+ MAPPERS[:users].save(user)
97
+ """
98
+ And the following query is executed
99
+ """
100
+ user = MAPPERS[:users].where(id: "2f0f791c-47cf-4a00-8676-e582075bcd65").first
101
+ """
102
+ Then the persisted user object is returned with lazy associations
103
+ """
104
+ #<struct User id="2f0f791c-47cf-4a00-8676-e582075bcd65",
105
+ first_name="Hansel",
106
+ last_name="Trickett",
107
+ email="hansel@tricketts.org",
108
+ posts=#<Terrestrial::CollectionMutabilityProxy:7fa4817aa148
109
+ >>
110
+ """
111
+ And the user's posts will be loaded once the association proxy receives an Enumerable message
112
+ """
113
+ [#<struct Post id="9b75fe2b-d694-4b90-9137-6201d426dda2",
114
+ author=#<Terrestrial::LazyObjectProxy:7fec5ac2a5f8 key_fields={:id=>"2f0f791c-47cf-4a00-8676-e582075bcd65"} lazy_object=nil>,
115
+ subject="Things that I like",
116
+ body="I like fish and scratching",
117
+ created_at=2015-10-03 21:00:00 UTC,
118
+ categories=#<Terrestrial::CollectionMutabilityProxy:7fec5ac296f8
119
+ >>]
120
+ """