terrestrial 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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
+ """