lotus-model 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +6 -0
- data/.yardopts +5 -0
- data/EXAMPLE.md +217 -0
- data/Gemfile +14 -2
- data/README.md +303 -3
- data/Rakefile +17 -1
- data/lib/lotus-model.rb +1 -0
- data/lib/lotus/entity.rb +157 -0
- data/lib/lotus/model.rb +23 -2
- data/lib/lotus/model/adapters/abstract.rb +167 -0
- data/lib/lotus/model/adapters/implementation.rb +111 -0
- data/lib/lotus/model/adapters/memory/collection.rb +132 -0
- data/lib/lotus/model/adapters/memory/command.rb +90 -0
- data/lib/lotus/model/adapters/memory/query.rb +457 -0
- data/lib/lotus/model/adapters/memory_adapter.rb +149 -0
- data/lib/lotus/model/adapters/sql/collection.rb +209 -0
- data/lib/lotus/model/adapters/sql/command.rb +67 -0
- data/lib/lotus/model/adapters/sql/query.rb +615 -0
- data/lib/lotus/model/adapters/sql_adapter.rb +154 -0
- data/lib/lotus/model/mapper.rb +101 -0
- data/lib/lotus/model/mapping.rb +23 -0
- data/lib/lotus/model/mapping/coercer.rb +80 -0
- data/lib/lotus/model/mapping/collection.rb +336 -0
- data/lib/lotus/model/version.rb +4 -1
- data/lib/lotus/repository.rb +620 -0
- data/lotus-model.gemspec +15 -11
- data/test/entity_test.rb +126 -0
- data/test/fixtures.rb +81 -0
- data/test/model/adapters/abstract_test.rb +75 -0
- data/test/model/adapters/implementation_test.rb +22 -0
- data/test/model/adapters/memory/query_test.rb +91 -0
- data/test/model/adapters/memory_adapter_test.rb +1044 -0
- data/test/model/adapters/sql/query_test.rb +121 -0
- data/test/model/adapters/sql_adapter_test.rb +1078 -0
- data/test/model/mapper_test.rb +94 -0
- data/test/model/mapping/coercer_test.rb +27 -0
- data/test/model/mapping/collection_test.rb +82 -0
- data/test/repository_test.rb +283 -0
- data/test/test_helper.rb +30 -0
- data/test/version_test.rb +7 -0
- metadata +109 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 49e959406b3eb918c7a4bd905a12ba1c10262710
|
4
|
+
data.tar.gz: d94acfc3ddd8cf6b68acda3df7b6316532c3da3c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5fbddd4ce436384d0c76cdb23fe97c836bcb86f68bc43ce18a6c128be5effe992454e0e542d08113e8e0f920498257d06efe8633b9c07256a73c5f9cac24f7fd
|
7
|
+
data.tar.gz: 61a389ebd9c46d67fb19bd5af588c1c5681253f3470eea10b2975c32d246741c66bf53ed888261c07d33f81d99fd7f641ff7b2932f1ac8afc367f84cb2346917
|
data/.gitignore
CHANGED
data/.travis.yml
ADDED
data/.yardopts
ADDED
data/EXAMPLE.md
ADDED
@@ -0,0 +1,217 @@
|
|
1
|
+
# Lotus::Model
|
2
|
+
|
3
|
+
This is a guide that helps you to getting started with [**Lotus::Model**](https://github.com/lotus/model).
|
4
|
+
You can find the full code source [here](https://gist.github.com/jodosha/11211048).
|
5
|
+
|
6
|
+
## Gems
|
7
|
+
|
8
|
+
First of all, we need to setup a `Gemfile`.
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
source 'https://rubygems.org'
|
12
|
+
|
13
|
+
gem 'sqlite3'
|
14
|
+
gem 'lotus-model'
|
15
|
+
```
|
16
|
+
|
17
|
+
Then we can fetch the dependencies with `bundle install`.
|
18
|
+
|
19
|
+
## Setup
|
20
|
+
|
21
|
+
**Lotus::Model** doesn't have migrations, for this example we're gonna use [Sequel](http://sequel.jeremyevans.net).
|
22
|
+
We create the database first, and then two tables: `authors` and `articles`.
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
require 'bundler/setup'
|
26
|
+
require 'sqlite3'
|
27
|
+
require 'lotus/model'
|
28
|
+
require 'lotus/model/adapters/sql_adapter'
|
29
|
+
|
30
|
+
connection_uri = "sqlite://#{ __dir__ }/test.db"
|
31
|
+
|
32
|
+
database = Sequel.connect(connection_uri)
|
33
|
+
|
34
|
+
database.create_table! :authors do
|
35
|
+
primary_key :id
|
36
|
+
String :name
|
37
|
+
end
|
38
|
+
|
39
|
+
database.create_table! :articles do
|
40
|
+
primary_key :id
|
41
|
+
Integer :author_id, null: false
|
42
|
+
String :title
|
43
|
+
Integer :comments_count, default: 0
|
44
|
+
Boolean :published, default: false
|
45
|
+
end
|
46
|
+
```
|
47
|
+
|
48
|
+
## Entities
|
49
|
+
|
50
|
+
We have two entities in our application: `Author` and `Article`.
|
51
|
+
`Author` is a `Struct`, Lotus::Model can persist it.
|
52
|
+
`Article` has a small API concerning its publishing process.
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
Author = Struct.new(:id, :name) do
|
56
|
+
def initialize(attributes = {})
|
57
|
+
@id, @name = attributes.values_at(:id, :name)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class Article
|
62
|
+
include Lotus::Entity
|
63
|
+
self.attributes = :author_id, :title, :comments_count, :published # id is implicit
|
64
|
+
|
65
|
+
def published?
|
66
|
+
!!published
|
67
|
+
end
|
68
|
+
|
69
|
+
def publish!
|
70
|
+
@published = true
|
71
|
+
end
|
72
|
+
end
|
73
|
+
```
|
74
|
+
|
75
|
+
## Repositories
|
76
|
+
|
77
|
+
In order to persist and query the entities above, we define two corresponding repositories:
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
class AuthorRepository
|
81
|
+
include Lotus::Repository
|
82
|
+
end
|
83
|
+
|
84
|
+
class ArticleRepository
|
85
|
+
include Lotus::Repository
|
86
|
+
|
87
|
+
def self.most_recent_by_author(author, limit = 8)
|
88
|
+
query do
|
89
|
+
where(author_id: author.id).
|
90
|
+
desc(:id).
|
91
|
+
limit(limit)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.most_recent_published_by_author(author, limit = 8)
|
96
|
+
most_recent_by_author(author, limit).published
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.published
|
100
|
+
query do
|
101
|
+
where(published: true)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.drafts
|
106
|
+
exclude published
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.rank
|
110
|
+
published.desc(:comments_count)
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.best_article_ever
|
114
|
+
rank.limit(1)
|
115
|
+
end
|
116
|
+
|
117
|
+
def self.comments_average
|
118
|
+
query.average(:comments_count)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
```
|
122
|
+
|
123
|
+
## Mapper
|
124
|
+
|
125
|
+
We create a correspondence between the database columns with the entities' attributes.
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
mapper = Lotus::Model::Mapper.new do
|
129
|
+
collection :authors do
|
130
|
+
entity Author
|
131
|
+
|
132
|
+
attribute :id, Integer
|
133
|
+
attribute :name, String
|
134
|
+
end
|
135
|
+
|
136
|
+
collection :articles do
|
137
|
+
entity Article
|
138
|
+
|
139
|
+
attribute :id, Integer
|
140
|
+
attribute :author_id, Integer
|
141
|
+
attribute :title, String
|
142
|
+
attribute :comments_count, Integer
|
143
|
+
attribute :published, Boolean
|
144
|
+
end
|
145
|
+
end
|
146
|
+
```
|
147
|
+
|
148
|
+
## Loading
|
149
|
+
|
150
|
+
We create an adapter instance, passing `mapper` and the connection URI (see above).
|
151
|
+
Please remember that the setup code is only required for the standalone usage of **Lotus::Model**.
|
152
|
+
A **Lotus** application will handle that configurations for you.
|
153
|
+
|
154
|
+
```ruby
|
155
|
+
adapter = Lotus::Model::Adapters::SqlAdapter.new(mapper, connection_uri)
|
156
|
+
AuthorRepository.adapter = adapter
|
157
|
+
ArticleRepository.adapter = adapter
|
158
|
+
|
159
|
+
mapper.load! # last operation
|
160
|
+
```
|
161
|
+
|
162
|
+
## Persist
|
163
|
+
|
164
|
+
Let's instantiate and persist some objects for our example:
|
165
|
+
|
166
|
+
```ruby
|
167
|
+
author = Author.new(name: 'Luca')
|
168
|
+
AuthorRepository.create(author)
|
169
|
+
|
170
|
+
articles = [
|
171
|
+
Article.new(title: 'Announcing Lotus', author_id: author.id, comments_count: 123, published: true),
|
172
|
+
Article.new(title: 'Introducing Lotus::Router', author_id: author.id, comments_count: 63, published: true),
|
173
|
+
Article.new(title: 'Introducing Lotus::Controller', author_id: author.id, comments_count: 82, published: true),
|
174
|
+
Article.new(title: 'Introducing Lotus::Model', author_id: author.id)
|
175
|
+
]
|
176
|
+
|
177
|
+
articles.each do |article|
|
178
|
+
ArticleRepository.create(article)
|
179
|
+
end
|
180
|
+
```
|
181
|
+
|
182
|
+
## Query
|
183
|
+
|
184
|
+
We can use repositories to query the database and return the entities we're looking for:
|
185
|
+
|
186
|
+
```ruby
|
187
|
+
ArticleRepository.first # => return the first article
|
188
|
+
ArticleRepository.last # => return the last article
|
189
|
+
|
190
|
+
ArticleRepository.published # => return all the published articles
|
191
|
+
ArticleRepository.drafts # => return all the drafts
|
192
|
+
|
193
|
+
ArticleRepository.rank # => all the published articles, sorted by popularity
|
194
|
+
|
195
|
+
ArticleRepository.best_article_ever # => the most commented article
|
196
|
+
|
197
|
+
ArticleRepository.comments_average # => calculates the average of comments across all the published articles.
|
198
|
+
|
199
|
+
ArticleRepository.most_recent_by_author(author) # => most recent articles by an author (drafts and published).
|
200
|
+
ArticleRepository.most_recent_published_by_author(author) # => most recent published articles by an author
|
201
|
+
```
|
202
|
+
|
203
|
+
## Business logic
|
204
|
+
|
205
|
+
As we've seen above, `Article` implements an API for publishing.
|
206
|
+
We're gonna use that logic to alter the state of an article (from draft to published) and then we use the repository to persist this new state.
|
207
|
+
|
208
|
+
```ruby
|
209
|
+
article = ArticleRepository.drafts.first
|
210
|
+
|
211
|
+
article.published? # => false
|
212
|
+
article.publish!
|
213
|
+
|
214
|
+
article.published? # => true
|
215
|
+
|
216
|
+
ArticleRepository.update(article)
|
217
|
+
```
|
data/Gemfile
CHANGED
@@ -1,4 +1,16 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
|
-
|
3
|
-
# Specify your gem's dependencies in lotus-model.gemspec
|
4
2
|
gemspec
|
3
|
+
|
4
|
+
if !ENV['TRAVIS']
|
5
|
+
gem 'byebug', require: false, platforms: :ruby if RUBY_VERSION == '2.1.1'
|
6
|
+
gem 'yard', require: false
|
7
|
+
# gem 'lotus-utils', require: false, github: 'lotus/utils'
|
8
|
+
else
|
9
|
+
# gem 'lotus-utils', '~> 0.1', '> 0.1.0'
|
10
|
+
end
|
11
|
+
|
12
|
+
gem 'lotus-utils', require: false, github: 'lotus/utils'
|
13
|
+
|
14
|
+
gem 'sqlite3', require: false
|
15
|
+
gem 'simplecov', require: false
|
16
|
+
gem 'coveralls', require: false
|
data/README.md
CHANGED
@@ -1,6 +1,41 @@
|
|
1
1
|
# Lotus::Model
|
2
2
|
|
3
|
-
|
3
|
+
A persistence framework for [Lotus](http://lotusrb.org).
|
4
|
+
|
5
|
+
It delivers a convenient public API to execute queries and commands against a database.
|
6
|
+
The architecture allows to keep business logic (entities) separated from details such as persistence or validations.
|
7
|
+
|
8
|
+
It implements the following concepts:
|
9
|
+
|
10
|
+
* [Entity](#entities) - An object defined by its identity.
|
11
|
+
* [Repository](#repositories) - An object that mediates between the entities and the persistence layer.
|
12
|
+
* [Data Mapper](#datamapper) - A persistence mapper that keep entities independent from database details.
|
13
|
+
* [Adapter](#adapters) – A database adapter.
|
14
|
+
* [Query](#queries) - An object that represents a database query.
|
15
|
+
|
16
|
+
Like all the other Lotus compontents, it can be used as a standalone framework or within a full Lotus application.
|
17
|
+
|
18
|
+
## Status
|
19
|
+
|
20
|
+
[](http://badge.fury.io/rb/lotus-model)
|
21
|
+
[](http://travis-ci.org/lotus/model?branch=master)
|
22
|
+
[](https://coveralls.io/r/lotus/model)
|
23
|
+
[](https://codeclimate.com/github/lotus/model)
|
24
|
+
[](https://gemnasium.com/lotus/model)
|
25
|
+
[](http://inch-pages.github.io/github/lotus/model)
|
26
|
+
|
27
|
+
## Contact
|
28
|
+
|
29
|
+
* Home page: http://lotusrb.org
|
30
|
+
* Mailing List: http://lotusrb.org/mailing-list
|
31
|
+
* API Doc: http://rdoc.info/gems/lotus-model
|
32
|
+
* Bugs/Issues: https://github.com/lotus/model/issues
|
33
|
+
* Support: http://stackoverflow.com/questions/tagged/lotus-ruby
|
34
|
+
* Chat: https://gitter.im/lotus/chat
|
35
|
+
|
36
|
+
## Rubies
|
37
|
+
|
38
|
+
__Lotus::View__ supports Ruby (MRI) 2+
|
4
39
|
|
5
40
|
## Installation
|
6
41
|
|
@@ -18,12 +53,277 @@ Or install it yourself as:
|
|
18
53
|
|
19
54
|
## Usage
|
20
55
|
|
21
|
-
|
56
|
+
### Entities
|
57
|
+
|
58
|
+
An object that is defined by its identity.
|
59
|
+
|
60
|
+
An entity is the core of an application, where the part of the domain logic is implemented.
|
61
|
+
It's a small, cohesive object that express coherent and meagniful behaviors.
|
62
|
+
|
63
|
+
It deals with one and only one responsibility that is pertinent to the
|
64
|
+
domain of the application, without caring about details such as persistence
|
65
|
+
or validations.
|
66
|
+
|
67
|
+
This simplicity of design allows developers to focus on behaviors, or
|
68
|
+
message passing if you will, which is the quintessence of Object Oriented Programming.
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
require 'lotus/model'
|
72
|
+
|
73
|
+
class Person
|
74
|
+
include Lotus::Entity
|
75
|
+
self.attributes = :name, :age
|
76
|
+
end
|
77
|
+
```
|
78
|
+
|
79
|
+
When a class includes `Lotus::Entity` it will receive the following interface:
|
80
|
+
|
81
|
+
* `#id`
|
82
|
+
* `#id=`
|
83
|
+
* `#initialize(attributes = {})`
|
84
|
+
|
85
|
+
Also, the usage of `.attributes=` defines accessors for the given attribute names.
|
86
|
+
|
87
|
+
If we expand the code above in **pure Ruby**, it would be:
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
class Person
|
91
|
+
attr_accessor :id, :name, :age
|
92
|
+
|
93
|
+
def initialize(attributes = {})
|
94
|
+
@id, @name, @age = attributes.values_at(:id, :name, :age)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
```
|
98
|
+
|
99
|
+
Indeed, **Lotus::Model** ships `Entity` only for developers's convenience, but the
|
100
|
+
rest of the framework is able to accept any object that implements the interface above.
|
101
|
+
|
102
|
+
### Repositories
|
103
|
+
|
104
|
+
A object that mediates between entites and the persistence layer.
|
105
|
+
It offers a standardized API to query and execute commands on a database.
|
106
|
+
|
107
|
+
A repository is **storage idenpendent**, all the queries and commands are
|
108
|
+
delegated to the current adapter.
|
109
|
+
|
110
|
+
This architecture has several advantages:
|
111
|
+
|
112
|
+
* Applications depends on an standard API, instead of low level details
|
113
|
+
(Dependency Inversion principle)
|
114
|
+
|
115
|
+
* Applications depends on a stable API, that doesn't change if the
|
116
|
+
storage changes
|
117
|
+
|
118
|
+
* Developers can postpone storage decisions
|
119
|
+
|
120
|
+
* Confines persistence logic at a low level
|
121
|
+
|
122
|
+
* Multiple data sources can easily coexist in an application
|
123
|
+
|
124
|
+
When a class includes `Lotus::Repository`, it will receive the following interface:
|
125
|
+
|
126
|
+
* `.persist(entity)` – Create or update an entity
|
127
|
+
* `.create(entity)` – Create a record for the given entity
|
128
|
+
* `.update(entity)` – Update the record correspoding to the given entity
|
129
|
+
* `.delete(entity)` – Delete the record correspoding to the given entity
|
130
|
+
* `.all` - Fetch all the entities from the collection
|
131
|
+
* `.first` - Fetch the first entity from the collection
|
132
|
+
* `.last` - Fetch the last entity from the collection
|
133
|
+
* `.clear` - Delete all the records from the collection
|
134
|
+
* `.query` - Fabricates a query object
|
135
|
+
|
136
|
+
**A collection is a homogenous set of records.**
|
137
|
+
It corresponds to a table for a SQL database or to a MongoDB collection.
|
138
|
+
|
139
|
+
**All the queries are private**.
|
140
|
+
This decision forces developers to define intention revealing API, instead leak storage API details outside of a repository.
|
141
|
+
|
142
|
+
Look at the following code:
|
143
|
+
|
144
|
+
```ruby
|
145
|
+
ArticleRepository.where(author_id: 23).order(:published_at).limit(8)
|
146
|
+
```
|
147
|
+
|
148
|
+
This is **bad** for a variety of reasons:
|
149
|
+
|
150
|
+
* The caller has an intimate knowledge of the internal mechanisms of the Repository.
|
151
|
+
|
152
|
+
* The caller works on several levels of abstraction.
|
153
|
+
|
154
|
+
* It doesn't express a clear intent, it's just a chain of methods.
|
155
|
+
|
156
|
+
* The caller can't be easily tested in isolation.
|
157
|
+
|
158
|
+
* If we change the storage, we are forced to change the code of the caller(s).
|
159
|
+
|
160
|
+
There is a better way:
|
161
|
+
|
162
|
+
```ruby
|
163
|
+
require 'lotus/model'
|
164
|
+
|
165
|
+
class ArticleRepository
|
166
|
+
include Lotus::Repository
|
167
|
+
|
168
|
+
def self.most_recent_by_author(author, limit = 8)
|
169
|
+
query do
|
170
|
+
where(author_id: author.id).
|
171
|
+
order(:published_at)
|
172
|
+
end.limit(limit)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
```
|
176
|
+
|
177
|
+
This is a **huge improvement**, because:
|
178
|
+
|
179
|
+
* The caller doesn't know how the repository fetches the entities.
|
180
|
+
|
181
|
+
* The caller works on a single level of abstraction. It doesn't even know about records, only works with entities.
|
182
|
+
|
183
|
+
* It expresses a clear intent.
|
184
|
+
|
185
|
+
* The caller can be easily tested in isolation. It's just a matter of stub this method.
|
186
|
+
|
187
|
+
* If we change the storage, the callers aren't affected.
|
188
|
+
|
189
|
+
Here an extended example of a repository that uses the SQL adapter.
|
190
|
+
|
191
|
+
```ruby
|
192
|
+
class ArticleRepository
|
193
|
+
include Lotus::Repository
|
194
|
+
|
195
|
+
def self.most_recent_by_author(author, limit = 8)
|
196
|
+
query do
|
197
|
+
where(author_id: author.id).
|
198
|
+
desc(:id).
|
199
|
+
limit(limit)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def self.most_recent_published_by_author(author, limit = 8)
|
204
|
+
most_recent_by_author(author, limit).published
|
205
|
+
end
|
206
|
+
|
207
|
+
def self.published
|
208
|
+
query do
|
209
|
+
where(published: true)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def self.drafts
|
214
|
+
exclude published
|
215
|
+
end
|
216
|
+
|
217
|
+
def self.rank
|
218
|
+
published.desc(:comments_count)
|
219
|
+
end
|
220
|
+
|
221
|
+
def self.best_article_ever
|
222
|
+
rank.limit(1)
|
223
|
+
end
|
224
|
+
|
225
|
+
def self.comments_average
|
226
|
+
query.average(:comments_count)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
```
|
230
|
+
|
231
|
+
### Data Mapper
|
232
|
+
|
233
|
+
A persistence mapper that keep entities independent from database details.
|
234
|
+
It's database independent, it can work with SQL, document, and even with key/value stores.
|
235
|
+
|
236
|
+
The role of a data mapper is to translate database columns into the corresponding attribute of an entity.
|
237
|
+
|
238
|
+
```ruby
|
239
|
+
require 'lotus/model'
|
240
|
+
|
241
|
+
mapper = Lotus::Model::Mapper.new do
|
242
|
+
collection :users do
|
243
|
+
entity User
|
244
|
+
|
245
|
+
attribute :id, Integer
|
246
|
+
attribute :name, String
|
247
|
+
attribute :age, Integer
|
248
|
+
end
|
249
|
+
end
|
250
|
+
```
|
251
|
+
|
252
|
+
For simplicity sake, imagine that the mapper above is used with a SQL database.
|
253
|
+
We use `#collection` to indicate the table that we want to map, `#entity` to indicate the class that we want to associate.
|
254
|
+
In the end, each `#attribute` call, is to associate the column with a Ruby type.
|
255
|
+
|
256
|
+
For advanced mapping and legacy databases, please have a look at the API doc.
|
257
|
+
|
258
|
+
### Adapter
|
259
|
+
|
260
|
+
An adapter is a concrete implementation of persistence logic for a specific database.
|
261
|
+
**Lotus::Model** is shipped with two adapters:
|
262
|
+
|
263
|
+
* SqlAdapter
|
264
|
+
* MemoryAdapter
|
265
|
+
|
266
|
+
An adapter can be associated to one or multiple repositories.
|
267
|
+
|
268
|
+
```ruby
|
269
|
+
require 'pg'
|
270
|
+
require 'lotus/model'
|
271
|
+
require 'lotus/model/adapters/sql_adapter'
|
272
|
+
|
273
|
+
mapper = Lotus::Model::Mapper.new do
|
274
|
+
# ...
|
275
|
+
end
|
276
|
+
|
277
|
+
adapter = Lotus::Model::Adapters::SqlAdapter.new(mapper, 'postgres://host:port/database')
|
278
|
+
|
279
|
+
PersonRepository.adapter = adapter
|
280
|
+
ArticleRepository.adapter = adapter
|
281
|
+
```
|
282
|
+
|
283
|
+
In the example above, we reuse the adpter because the target tables (`people` and `articles`) are defined in the same database.
|
284
|
+
**As rule of thumb, one adapter instance per database.**
|
285
|
+
|
286
|
+
### Query
|
287
|
+
|
288
|
+
An object that implements an interface for quering the database.
|
289
|
+
This interface may vary, according to the adapter's specifications.
|
290
|
+
Think of an adapter for Redis, it will probably employ different strategies to filter records from an SQL query object.
|
291
|
+
|
292
|
+
### Conventions
|
293
|
+
|
294
|
+
* A repository must be named after an entity, by appeding `"Repository"` to the entity class name (eg. `Article` => `ArticleRepository`).
|
295
|
+
|
296
|
+
### Thread safety
|
297
|
+
|
298
|
+
**Lotus::Model**'s is thread safe during the runtime, but it isn't during the loading process.
|
299
|
+
The mapper compiles some code internally, be sure to safely load it before your application starts.
|
300
|
+
|
301
|
+
```ruby
|
302
|
+
Mutex.new.synchronize do
|
303
|
+
mapper.load!
|
304
|
+
end
|
305
|
+
```
|
306
|
+
|
307
|
+
**This is not necessary, when Lotus::Model is used within a Lotus application.**
|
308
|
+
|
309
|
+
## Example
|
310
|
+
|
311
|
+
For a full working example, have a look at [EXAMPLE.md](https://github.com/lotus/model/blob/master/EXAMPLE.md).
|
312
|
+
Please remember that the setup code is only required for the standalone usage of **Lotus::Model**.
|
313
|
+
A **Lotus** application will handle that configurations for you.
|
314
|
+
|
315
|
+
## Versioning
|
316
|
+
|
317
|
+
__Lotus::Model__ uses [Semantic Versioning 2.0.0](http://semver.org)
|
22
318
|
|
23
319
|
## Contributing
|
24
320
|
|
25
|
-
1. Fork it (
|
321
|
+
1. Fork it ( https://github.com/lotus/model/fork )
|
26
322
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
323
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
324
|
4. Push to the branch (`git push origin my-new-feature`)
|
29
325
|
5. Create new Pull Request
|
326
|
+
|
327
|
+
## Copyright
|
328
|
+
|
329
|
+
Copyright 2014 Luca Guidi – Released under MIT License
|