vorpal 0.1.0.rc3 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +50 -33
- data/lib/vorpal/aggregate_mapper.rb +11 -0
- data/lib/vorpal/db_loader.rb +3 -4
- data/lib/vorpal/driver/postgresql.rb +181 -0
- data/lib/vorpal/dsl/config_builder.rb +37 -22
- data/lib/vorpal/dsl/configuration.rb +5 -4
- data/lib/vorpal/dsl/defaults_generator.rb +14 -4
- data/lib/vorpal/engine.rb +7 -8
- data/lib/vorpal/identity_map.rb +15 -14
- data/lib/vorpal/version.rb +1 -1
- data/vorpal.gemspec +11 -7
- metadata +57 -65
- data/.editorconfig +0 -13
- data/.gitignore +0 -16
- data/.rspec +0 -1
- data/.ruby-version +0 -1
- data/.yardopts +0 -1
- data/Gemfile +0 -4
- data/Rakefile +0 -10
- data/lib/vorpal/db_driver.rb +0 -143
- data/spec/helpers/db_helpers.rb +0 -51
- data/spec/helpers/profile_helpers.rb +0 -26
- data/spec/integration_spec_helper.rb +0 -36
- data/spec/unit_spec_helper.rb +0 -0
- data/spec/vorpal/acceptance/aggregate_mapper_spec.rb +0 -911
- data/spec/vorpal/integration/db_driver_spec.rb +0 -42
- data/spec/vorpal/performance/performance_spec.rb +0 -195
- data/spec/vorpal/unit/configs_spec.rb +0 -117
- data/spec/vorpal/unit/db_loader_spec.rb +0 -103
- data/spec/vorpal/unit/dsl/config_builder_spec.rb +0 -19
- data/spec/vorpal/unit/dsl/defaults_generator_spec.rb +0 -75
- data/spec/vorpal/unit/identity_map_spec.rb +0 -62
- data/spec/vorpal/unit/loaded_objects_spec.rb +0 -22
- data/spec/vorpal/unit/util/string_utils_spec.rb +0 -25
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c866f3e83daac73f28e5c9a53875b73d7f8db95cbf437200f0b0c69cce81d317
|
4
|
+
data.tar.gz: e1e91f848552e1f3710849370978d4a1e322e65b12c65bebcd995b76960ba44e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5bcd5518f56b4c43f4180d3f63c75c6186c1bd89bee939b8410e656f3df411920238eaf51da4a590367f929507c3bdb1ada98cf972d6ebf6785432417018b9a2
|
7
|
+
data.tar.gz: 85e53e6946ea0d0f06e14d8913ac3eba45668b3d08bfc0036a789620ea05af587cb5a97991467eadfea1f76d5dd3fccaf71c96aeec2345327d9538e714e0e720
|
data/README.md
CHANGED
@@ -1,16 +1,13 @@
|
|
1
|
-
# Vorpal [![Build Status](https://travis-ci.
|
1
|
+
# Vorpal [![Build Status](https://travis-ci.com/nulogy/vorpal.svg?branch=master)](https://travis-ci.com/nulogy/vorpal) [![Code Climate](https://codeclimate.com/github/nulogy/vorpal/badges/gpa.svg)](https://codeclimate.com/github/nulogy/vorpal) [![Code Coverage](https://codecov.io/gh/nulogy/vorpal/branch/master/graph/badge.svg)](https://codecov.io/gh/nulogy/vorpal/branch/master)
|
2
2
|
|
3
3
|
Separate your domain model from your persistence mechanism. Some problems call for a really sharp tool.
|
4
4
|
|
5
|
-
|
6
|
-
>
|
7
|
-
|
8
|
-
> The vorpal blade went snicker-snack!
|
9
|
-
|
10
|
-
> He left it dead, and with its head
|
11
|
-
|
5
|
+
> One, two! One, two! and through and through<br/>
|
6
|
+
> The vorpal blade went snicker-snack!<br/>
|
7
|
+
> He left it dead, and with its head<br/>
|
12
8
|
> He went galumphing back.
|
13
9
|
|
10
|
+
\- [Jabberwocky](https://www.poetryfoundation.org/poems/42916/jabberwocky) by Lewis Carroll
|
14
11
|
|
15
12
|
## Overview
|
16
13
|
Vorpal is a [Data Mapper](http://martinfowler.com/eaaCatalog/dataMapper.html)-style ORM (object relational mapper) framelet that persists POROs (plain old Ruby objects) to a relational DB. It has been heavily influenced by concepts from [Domain Driven Design](http://www.infoq.com/minibooks/domain-driven-design-quickly).
|
@@ -45,8 +42,6 @@ Or install it yourself as:
|
|
45
42
|
|
46
43
|
## Usage
|
47
44
|
|
48
|
-
**Warning! API still in flux! Expect it to change with every release until 0.1.0. After this point, semantic versioning will be used.**
|
49
|
-
|
50
45
|
Start with a domain model of POROs and AR::Base objects that form an aggregate:
|
51
46
|
|
52
47
|
```ruby
|
@@ -145,7 +140,8 @@ module TreeRepository
|
|
145
140
|
end
|
146
141
|
```
|
147
142
|
|
148
|
-
Here we've used the `owned` flag on the `belongs_to` from the Tree to the Gardener to show
|
143
|
+
Here we've used the `owned: false` flag on the `belongs_to` from the Tree to the Gardener to show
|
144
|
+
that the Gardener is on the aggregate boundary.
|
149
145
|
|
150
146
|
And use it:
|
151
147
|
|
@@ -176,7 +172,7 @@ It also does not do some things that you might expect from other ORMs:
|
|
176
172
|
1. No lazy loading of associations. This might sound like a big deal, but with [correctly designed aggregates](http://dddcommunity.org/library/vernon_2011/) it turns out not to be.
|
177
173
|
1. No managing of transactions. It is the strong opinion of the authors that managing transactions is an application-level concern.
|
178
174
|
1. No support for validations. Validations are not a persistence concern.
|
179
|
-
1. No AR-style callbacks. Use Infrastructure, Application, or Domain
|
175
|
+
1. No AR-style callbacks. Use [Infrastructure, Application, or Domain services](http://martinfowler.com/bliki/EvansClassification.html) instead.
|
180
176
|
1. No has-many-through associations. Use two has-many associations to a join entity instead.
|
181
177
|
1. The `id` attribute is reserved for database primary keys. If you have a natural key/id on your domain model, name it something that makes sense for your domain. It is the strong opinion of the authors that using natural keys as foreign keys is a bad idea. This mixes domain and persistence concerns.
|
182
178
|
|
@@ -185,13 +181,11 @@ It also does not do some things that you might expect from other ORMs:
|
|
185
181
|
1. Only supports PostgreSQL.
|
186
182
|
|
187
183
|
## Future Enhancements
|
188
|
-
*
|
189
|
-
* Support for other DBMSs (no MySQL support until ids can be generated without inserting into a table!)
|
190
|
-
* Support for other ORMs.
|
191
|
-
* Value objects.
|
192
|
-
* Remove dependency on ActiveRecord (optimistic locking? updated_at, created_at support? Data type conversions? TimeZone support?)
|
193
|
-
* More efficient updates (use fewer queries.)
|
184
|
+
* Support for UUID primary keys.
|
194
185
|
* Nicer DSL for specifying attributes that have different names in the domain model than in the DB.
|
186
|
+
* Show how to implement POROs without using Virtus (it is unsupported and can be crazy slow)
|
187
|
+
* Aggregate updated_at.
|
188
|
+
* Better support for value objects.
|
195
189
|
|
196
190
|
## FAQ
|
197
191
|
|
@@ -207,11 +201,12 @@ It also does not do some things that you might expect from other ORMs:
|
|
207
201
|
|
208
202
|
**A.** Create a method on a [Repository](http://martinfowler.com/eaaCatalog/repository.html)! They have full access to the DB/ORM so you can use [Arel](https://github.com/rails/arel) and go [crazy](http://asciicasts.com/episodes/239-activerecord-relation-walkthrough) or use direct SQL if you want.
|
209
203
|
|
210
|
-
For example:
|
204
|
+
For example, use the [#query](https://rubydoc.info/github/nulogy/vorpal/master/Vorpal/AggregateMapper#query-instance_method) method on the [AggregateMapper](https://rubydoc.info/github/nulogy/vorpal/master/Vorpal/AggregateMapper) to access the underyling [ActiveRecordRelation](https://api.rubyonrails.org/classes/ActiveRecord/Relation.html):
|
211
205
|
|
212
206
|
```ruby
|
213
|
-
def
|
214
|
-
|
207
|
+
def find_special_ones
|
208
|
+
# use `load_all` or `load_one` to convert from ActiveRecord objects to domain POROs.
|
209
|
+
@mapper.query.where(special: true).load_all
|
215
210
|
end
|
216
211
|
```
|
217
212
|
|
@@ -233,19 +228,9 @@ For example:
|
|
233
228
|
|
234
229
|
**A.** You can use [ActiveModel::Serialization](http://api.rubyonrails.org/classes/ActiveModel/Serialization.html) or [ActiveModel::Serializers](https://github.com/rails-api/active_model_serializers) but they are not heartily recommended. The former is too coupled to the model and the latter is too coupled to Rails controllers. Vorpal uses [SimpleSerializer](https://github.com/nulogy/simple_serializer) for this purpose.
|
235
230
|
|
236
|
-
|
237
|
-
|
238
|
-
1. Start a PostgreSQL server.
|
239
|
-
2. Either:
|
240
|
-
* Create a DB user called `vorpal` with password `pass`. OR:
|
241
|
-
* Modify `spec/helpers/db_helpers.rb`.
|
242
|
-
3. Run `rake` from the terminal.
|
243
|
-
|
244
|
-
## Contributors
|
231
|
+
**Q.** Are `updated_at` and `created_at` supported?
|
245
232
|
|
246
|
-
|
247
|
-
* [Paul Sobocinski](https://github.com/psobocinski)
|
248
|
-
* [Jason Cheong-Kee-You](https://github.com/jchunky)
|
233
|
+
**A.** Yes. If they exist on your database tables, they will behave exactly as if you were using vanilla ActiveRecord.
|
249
234
|
|
250
235
|
## Contributing
|
251
236
|
|
@@ -254,3 +239,35 @@ For example:
|
|
254
239
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
255
240
|
4. Push to the branch (`git push origin my-new-feature`)
|
256
241
|
5. Create a new Pull Request
|
242
|
+
|
243
|
+
## OSX Environment setup
|
244
|
+
|
245
|
+
1. Install [Homebrew](https://brew.sh/)
|
246
|
+
2. Install [rbenv](https://github.com/rbenv/rbenv#installation) ([RVM](https://rvm.io/) can work too)
|
247
|
+
3. Install [DirEnv](https://direnv.net/docs/installation.html) (`brew install direnv`)
|
248
|
+
4. Install Docker Desktop Community Edition (`brew cask install docker`)
|
249
|
+
5. Start Docker Desktop Community Edition (`CMD+space docker ENTER`)
|
250
|
+
6. Install Ruby (`rbenv install 2.7.0`)
|
251
|
+
7. Install PostgreSQL (`brew install postgresql`)
|
252
|
+
8. Clone the repo (`git clone git@github.com:nulogy/vorpal.git`) and `cd` to the project root.
|
253
|
+
8. Copy the contents of `gemfiles/rails_<version>.gemfile.lock` into a `Gemfile.lock` file
|
254
|
+
at the root of the project. (`cp gemfiles/rails_6_0.gemfile.lock gemfile.lock`)
|
255
|
+
9. `bundle`
|
256
|
+
|
257
|
+
### Running Tests
|
258
|
+
|
259
|
+
1. Start a PostgreSQL server using `docker-compose up`
|
260
|
+
3. Run `rake` from the terminal to run all specs or `rspec <path to spec file>` to
|
261
|
+
run a single spec.
|
262
|
+
|
263
|
+
### Running Tests for a specific version of Rails
|
264
|
+
|
265
|
+
1. Start a PostgreSQL server using `docker-compose up`
|
266
|
+
2. Run `appraisal rails-5-2 rake` from the terminal to run all specs or
|
267
|
+
`appraisal rails-5-2 rspec <path to spec file>` to run a single spec.
|
268
|
+
|
269
|
+
Please see the [Appraisal gem docs](https://github.com/thoughtbot/appraisal) for more information.
|
270
|
+
|
271
|
+
## Contributors
|
272
|
+
|
273
|
+
See who's [contributed](https://github.com/nulogy/vorpal/graphs/contributors)!
|
@@ -84,6 +84,17 @@ module Vorpal
|
|
84
84
|
@engine
|
85
85
|
end
|
86
86
|
|
87
|
+
# Returns a 'Vorpal-aware' [ActiveRecord::Relation](https://api.rubyonrails.org/classes/ActiveRecord/Relation.html)
|
88
|
+
# for the ActiveRecord object underlying the domain entity mapped by this mapper.
|
89
|
+
#
|
90
|
+
# This method allows you to easily access the power of ActiveRecord::Relation to do more complex
|
91
|
+
# queries in your repositories.
|
92
|
+
#
|
93
|
+
# The ActiveRecord::Relation is 'Vorpal-aware' because it has the {#load_one} and {#load_many} methods
|
94
|
+
# mixed in so that you can get the POROs from your domain model instead of the ActiveRecord
|
95
|
+
# objects normally returned by ActiveRecord::Relation.
|
96
|
+
#
|
97
|
+
# @return [ActiveRecord::Relation]
|
87
98
|
def query
|
88
99
|
@engine.query(@domain_class)
|
89
100
|
end
|
data/lib/vorpal/db_loader.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'vorpal/loaded_objects'
|
2
2
|
require 'vorpal/util/array_hash'
|
3
|
-
require 'vorpal/db_driver'
|
4
3
|
|
5
4
|
module Vorpal
|
6
5
|
# Handles loading of objects from the database.
|
@@ -13,7 +12,7 @@ module Vorpal
|
|
13
12
|
end
|
14
13
|
|
15
14
|
def load_from_db(ids, config)
|
16
|
-
db_roots = @db_driver.load_by_id(config, ids)
|
15
|
+
db_roots = @db_driver.load_by_id(config.db_class, ids)
|
17
16
|
load_from_db_objects(db_roots, config)
|
18
17
|
end
|
19
18
|
|
@@ -123,7 +122,7 @@ module Vorpal
|
|
123
122
|
|
124
123
|
def load_all(db_driver)
|
125
124
|
return [] if @ids.empty?
|
126
|
-
db_driver.load_by_id(@config, @ids)
|
125
|
+
db_driver.load_by_id(@config.db_class, @ids)
|
127
126
|
end
|
128
127
|
end
|
129
128
|
|
@@ -138,7 +137,7 @@ module Vorpal
|
|
138
137
|
|
139
138
|
def load_all(db_driver)
|
140
139
|
return [] if @fk_values.empty?
|
141
|
-
db_driver.load_by_foreign_key(@config, @fk_values, @fk_info)
|
140
|
+
db_driver.load_by_foreign_key(@config.db_class, @fk_values, @fk_info)
|
142
141
|
end
|
143
142
|
end
|
144
143
|
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
require 'vorpal/util/string_utils.rb'
|
2
|
+
|
3
|
+
module Vorpal
|
4
|
+
module Driver
|
5
|
+
# Interface between the database and Vorpal for PostgreSQL using ActiveRecord.
|
6
|
+
class Postgresql
|
7
|
+
def initialize
|
8
|
+
@sequence_names = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def insert(db_class, db_objects)
|
12
|
+
if ActiveRecord::VERSION::MAJOR >= 6
|
13
|
+
return if db_objects.empty?
|
14
|
+
|
15
|
+
update_timestamps_on_create(db_class, db_objects)
|
16
|
+
db_class.insert_all!(db_objects.map(&:attributes))
|
17
|
+
elsif defined? ActiveRecord::Import
|
18
|
+
db_class.import(db_objects, validate: false)
|
19
|
+
else
|
20
|
+
db_objects.each do |db_object|
|
21
|
+
db_object.save!(validate: false)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def update(db_class, db_objects)
|
27
|
+
if ActiveRecord::VERSION::MAJOR >= 6
|
28
|
+
return if db_objects.empty?
|
29
|
+
|
30
|
+
update_timestamps_on_update(db_class, db_objects)
|
31
|
+
db_class.upsert_all(db_objects.map(&:attributes))
|
32
|
+
else
|
33
|
+
db_objects.each do |db_object|
|
34
|
+
db_object.save!(validate: false)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def destroy(db_class, ids)
|
40
|
+
db_class.where(id: ids).delete_all
|
41
|
+
end
|
42
|
+
|
43
|
+
# Loads instances of the given class by primary key.
|
44
|
+
#
|
45
|
+
# @param db_class [Class] A subclass of ActiveRecord::Base
|
46
|
+
# @return [[Object]] An array of entities.
|
47
|
+
def load_by_id(db_class, ids)
|
48
|
+
db_class.where(id: ids).to_a
|
49
|
+
end
|
50
|
+
|
51
|
+
# Loads instances of the given class whose foreign key has the given value.
|
52
|
+
#
|
53
|
+
# @param db_class [Class] A subclass of ActiveRecord::Base
|
54
|
+
# @param id [Integer] The value of the foreign key to find by. (Can also be an array of ids.)
|
55
|
+
# @param foreign_key_info [ForeignKeyInfo] Meta data for the foreign key.
|
56
|
+
# @return [[Object]] An array of entities.
|
57
|
+
def load_by_foreign_key(db_class, id, foreign_key_info)
|
58
|
+
arel = db_class.where(foreign_key_info.fk_column => id)
|
59
|
+
arel = arel.where(foreign_key_info.fk_type_column => foreign_key_info.fk_type) if foreign_key_info.polymorphic?
|
60
|
+
arel.to_a
|
61
|
+
end
|
62
|
+
|
63
|
+
# Fetches primary key values to be used for new entities.
|
64
|
+
#
|
65
|
+
# @param db_class [Class] A subclass of ActiveRecord::Base
|
66
|
+
# @return [[Integer]] An array of unused primary keys.
|
67
|
+
def get_primary_keys(db_class, count)
|
68
|
+
result = execute("select nextval($1) from generate_series(1,$2);", [sequence_name(db_class), count])
|
69
|
+
result.rows.map(&:first).map(&:to_i)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Builds an ORM Class for accessing data in the given DB table.
|
73
|
+
#
|
74
|
+
# @param model_class [Class] The PORO class that we are creating a DB interface class for.
|
75
|
+
# @param table_name [String] Name of the DB table the DB class should interface with.
|
76
|
+
# @return [Class] ActiveRecord::Base Class
|
77
|
+
def build_db_class(model_class, table_name)
|
78
|
+
db_class = Class.new(ActiveRecord::Base) do
|
79
|
+
class << self
|
80
|
+
# This is overridden for two reasons:
|
81
|
+
# 1) For anonymous classes, #name normally returns nil. Class names in Ruby come from the
|
82
|
+
# name of the constant they are assigned to.
|
83
|
+
# 2) Because the default implementation for Class#name for anonymous classes is very, very
|
84
|
+
# slow. https://bugs.ruby-lang.org/issues/11119
|
85
|
+
# Remove this override once #2 has been fixed!
|
86
|
+
def name
|
87
|
+
@name ||= "Vorpal_generated_ActiveRecord__Base_class_for_#{vorpal_model_class_name}"
|
88
|
+
end
|
89
|
+
|
90
|
+
# Overridden because, like #name, the default implementation for anonymous classes is very,
|
91
|
+
# very slow.
|
92
|
+
def to_s
|
93
|
+
name
|
94
|
+
end
|
95
|
+
|
96
|
+
attr_accessor :vorpal_model_class_name
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
db_class.vorpal_model_class_name = Util::StringUtils.escape_class_name(model_class.name)
|
101
|
+
db_class.table_name = table_name
|
102
|
+
db_class
|
103
|
+
end
|
104
|
+
|
105
|
+
# Builds a composable query object (e.g. ActiveRecord::Relation) with Vorpal methods mixed in
|
106
|
+
# for querying for instances of the given AR::Base class.
|
107
|
+
#
|
108
|
+
# @param db_class [Class] A subclass of ActiveRecord::Base
|
109
|
+
def query(db_class, aggregate_mapper)
|
110
|
+
db_class.unscoped.extending(ArelQueryMethods.new(aggregate_mapper))
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
# Adapted from https://github.com/rails/rails/blob/614580270d7789e5275defc3da020ce27b3b2302/activerecord/lib/active_record/timestamp.rb#L99
|
116
|
+
def update_timestamps_on_create(db_class, db_objects)
|
117
|
+
return unless db_class.record_timestamps
|
118
|
+
|
119
|
+
current_time = db_class.current_time_from_proper_timezone
|
120
|
+
db_objects.each do |db_object|
|
121
|
+
db_class.all_timestamp_attributes_in_model.each do |column|
|
122
|
+
db_object.write_attribute(column, current_time) unless db_object.read_attribute(column)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
#Adapted from https://github.com/rails/rails/blob/614580270d7789e5275defc3da020ce27b3b2302/activerecord/lib/active_record/timestamp.rb#L111
|
128
|
+
def update_timestamps_on_update(db_class, db_objects)
|
129
|
+
return unless db_class.record_timestamps
|
130
|
+
|
131
|
+
current_time = db_class.current_time_from_proper_timezone
|
132
|
+
db_objects.each do |db_object|
|
133
|
+
db_class.timestamp_attributes_for_update_in_model.each do |column|
|
134
|
+
db_object.write_attribute(column, current_time)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def sequence_name(db_class)
|
140
|
+
@sequence_names[db_class] ||= execute(
|
141
|
+
"SELECT substring(column_default from '''(.*)''') FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = $1 AND column_name = 'id' LIMIT 1",
|
142
|
+
[db_class.table_name]
|
143
|
+
).rows.first.first
|
144
|
+
end
|
145
|
+
|
146
|
+
def execute(sql, binds)
|
147
|
+
binds = binds.map { |row| [nil, row] }
|
148
|
+
ActiveRecord::Base.connection.exec_query(sql, 'SQL', binds)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
class ArelQueryMethods < Module
|
153
|
+
def initialize(mapper)
|
154
|
+
@mapper = mapper
|
155
|
+
end
|
156
|
+
|
157
|
+
def extended(descendant)
|
158
|
+
super
|
159
|
+
descendant.extend(Methods)
|
160
|
+
descendant.vorpal_aggregate_mapper = @mapper
|
161
|
+
end
|
162
|
+
|
163
|
+
# Methods in this module will appear on any composable
|
164
|
+
module Methods
|
165
|
+
attr_writer :vorpal_aggregate_mapper
|
166
|
+
|
167
|
+
# See {AggregateMapper#load_many}.
|
168
|
+
def load_many
|
169
|
+
db_roots = self.to_a
|
170
|
+
@vorpal_aggregate_mapper.load_many(db_roots)
|
171
|
+
end
|
172
|
+
|
173
|
+
# See {AggregateMapper#load_one}.
|
174
|
+
def load_one
|
175
|
+
db_root = self.first
|
176
|
+
@vorpal_aggregate_mapper.load_one(db_root)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
@@ -22,43 +22,58 @@ module Vorpal
|
|
22
22
|
@attributes.concat(attributes)
|
23
23
|
end
|
24
24
|
|
25
|
-
# Defines a one-to-many association
|
25
|
+
# Defines a one-to-many association to another type where the foreign key is stored on the child.
|
26
26
|
#
|
27
|
-
#
|
27
|
+
# In Object-Oriented programming, associations are *directed*. This means that they can only be
|
28
|
+
# traversed in one direction: from the type that defines the association (the one with the
|
29
|
+
# getter) to the type that is associated. They end that defines the association is called the
|
30
|
+
# 'Parent' and the end that is associated is called the 'Child'.
|
31
|
+
#
|
32
|
+
# @param name [String] Name of the association getter.
|
28
33
|
# @param options [Hash]
|
29
|
-
# @option options [Boolean] :owned
|
30
|
-
# @option options [String] :fk
|
31
|
-
# @option options [String] :fk_type
|
32
|
-
# @option options [Class] :child_class
|
34
|
+
# @option options [Boolean] :owned (True) True if the child type belongs to the aggregate. Changes to any object belonging to the aggregate will be persisted when the aggregate is persisted.
|
35
|
+
# @option options [String] :fk (Parent class name converted to snakecase and appended with a '_id') The name of the DB column on the child that contains the foreign key reference to the parent.
|
36
|
+
# @option options [String] :fk_type The name of the DB column on the child that contains the parent class name. Only needed when there is an association from the child side that is polymorphic.
|
37
|
+
# @option options [Class] :child_class (name converted to a Class) The child class.
|
33
38
|
def has_many(name, options={})
|
34
39
|
@has_manys << {name: name}.merge(options)
|
35
40
|
end
|
36
41
|
|
37
|
-
# Defines a one-to-one association
|
38
|
-
# is stored on the
|
42
|
+
# Defines a one-to-one association to another type where the foreign key
|
43
|
+
# is stored on the child.
|
44
|
+
#
|
45
|
+
# In Object-Oriented programming, associations are *directed*. This means that they can only be
|
46
|
+
# traversed in one direction: from the type that defines the association (the one with the
|
47
|
+
# getter) to the type that is associated. They end that defines the association is called the
|
48
|
+
# 'Parent' and the end that is associated is called the 'Child'.
|
39
49
|
#
|
40
|
-
# @param name [String] Name of the
|
50
|
+
# @param name [String] Name of the association getter.
|
41
51
|
# @param options [Hash]
|
42
|
-
# @option options [Boolean] :owned
|
43
|
-
# @option options [String] :fk
|
44
|
-
# @option options [String] :fk_type
|
45
|
-
# @option options [Class] :child_class
|
52
|
+
# @option options [Boolean] :owned (True) True if the child type belongs to the aggregate. Changes to any object belonging to the aggregate will be persisted when the aggregate is persisted.
|
53
|
+
# @option options [String] :fk (Parent class name converted to snakecase and appended with a '_id') The name of the DB column on the child that contains the foreign key reference to the parent.
|
54
|
+
# @option options [String] :fk_type The name of the DB column on the child that contains the parent class name. Only needed when there is an association from the child side that is polymorphic.
|
55
|
+
# @option options [Class] :child_class (name converted to a Class) The child class.
|
46
56
|
def has_one(name, options={})
|
47
57
|
@has_ones << {name: name}.merge(options)
|
48
58
|
end
|
49
59
|
|
50
|
-
# Defines a one-to-one association with another
|
51
|
-
# is stored on
|
60
|
+
# Defines a one-to-one association with another type where the foreign key
|
61
|
+
# is stored on the parent.
|
62
|
+
#
|
63
|
+
# This association can be polymorphic. I.E. children can be of different types.
|
52
64
|
#
|
53
|
-
# This
|
65
|
+
# In Object-Oriented programming, associations are *directed*. This means that they can only be
|
66
|
+
# traversed in one direction: from the type that defines the association (the one with the
|
67
|
+
# getter) to the type that is associated. They end that defines the association is called the
|
68
|
+
# 'Parent' and the end that is associated is called the 'Child'.
|
54
69
|
#
|
55
|
-
# @param name [String] Name of the
|
70
|
+
# @param name [String] Name of the association getter.
|
56
71
|
# @param options [Hash]
|
57
|
-
# @option options [Boolean] :owned
|
58
|
-
# @option options [String] :fk
|
59
|
-
# @option options [String] :fk_type
|
60
|
-
# @option options [Class] :child_class
|
61
|
-
# @option options [[Class]] :child_classes
|
72
|
+
# @option options [Boolean] :owned (True) True if the child type belongs to the aggregate. Changes to any object belonging to the aggregate will be persisted when the aggregate is persisted.
|
73
|
+
# @option options [String] :fk (Child class name converted to snakecase and appended with a '_id') The name of the DB column on the parent that contains the foreign key reference to the child.
|
74
|
+
# @option options [String] :fk_type The name of the DB column on the parent that contains the child class name. Only needed when the association is polymorphic.
|
75
|
+
# @option options [Class] :child_class (name converted to a Class) The child class.
|
76
|
+
# @option options [[Class]] :child_classes The list of possible classes that can be children. This is for polymorphic associations. Takes precedence over `:child_class`.
|
62
77
|
def belongs_to(name, options={})
|
63
78
|
@belongs_tos << {name: name}.merge(options)
|
64
79
|
end
|