epiphy 0.0.1 → 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/EXAMPLE.md +173 -0
- data/lib/epiphy/adapter/rethinkdb.rb +60 -24
- data/lib/epiphy/entity/timestamp.rb +14 -0
- data/lib/epiphy/model.rb +8 -0
- data/lib/epiphy/repository.rb +67 -30
- data/lib/epiphy/repository/helper.rb +28 -0
- data/lib/epiphy/scheme.rb +19 -0
- data/lib/epiphy/version.rb +1 -1
- metadata +5 -3
- data/lib/epiphy/adapter/helper.rb +0 -114
- data/lib/epiphy/adapter/model.rb +0 -99
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 346e101f2179b4b096e7d9e38ef7c08879598e99
|
4
|
+
data.tar.gz: fc54f4cf44e3a1e25e79d10f5ecd2e85bd0bff0c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a8823aac0ef0a54099141567fcc3155d3289ca71d08b39b506f14ed668bb073f78d99a622d3090a804997f69370ec36841d7c256b7ff98c507fc057c3e4e100e
|
7
|
+
data.tar.gz: e6fd39cb0525b115d1fb6bfb35d094ef3f1661902f4db8bfbb5924eef21f3b74281ed71db385a19ce00bcac225ad8398ba302f8e5d49d200d1a4baa1b842bc74
|
data/EXAMPLE.md
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
# Epiphy
|
2
|
+
|
3
|
+
This is a guide that helps you to getting started with [**Epiphy**](https://github.com/kureikain/epiphy). This file is inspired by Lotus::Model
|
4
|
+
|
5
|
+
## Gems
|
6
|
+
|
7
|
+
First of all, we need to setup a `Gemfile`.
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
source 'https://rubygems.org'
|
11
|
+
|
12
|
+
gem 'epiphy'
|
13
|
+
```
|
14
|
+
|
15
|
+
Then we can fetch the dependencies with `bundle install`.
|
16
|
+
|
17
|
+
## Setup
|
18
|
+
|
19
|
+
We need to feed `Epiphy` with an adapter. Adapter is a lighweight wrap
|
20
|
+
contains a RethinkDB connection and its run option.
|
21
|
+
|
22
|
+
```
|
23
|
+
# Default connect to localhost, 28015, no auth key, use database 'test'
|
24
|
+
# by default
|
25
|
+
connection = Epiphy::Connection.create
|
26
|
+
adapter = Epiphy::Adapter::RethinkDB.new connection, 'test'
|
27
|
+
RethinkDB::Repository.configure do |r|
|
28
|
+
r.adapter = adapter
|
29
|
+
end
|
30
|
+
|
31
|
+
# Or merge them all
|
32
|
+
RethinkDB::Repository.configure do |r|
|
33
|
+
r.adapter = Epiphy::Adapter::RethinkDB.new(Epiphy::Connection.create)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Or connect to a different host, use database `cms`
|
37
|
+
RethinkDB::Repository.configure do |r|
|
38
|
+
r.adapter = Epiphy::Adapter::RethinkDB.new Epiphy::Connection.create({:host => '192.168.1.2'}, 'cms')
|
39
|
+
end
|
40
|
+
|
41
|
+
```
|
42
|
+
|
43
|
+
## Entities
|
44
|
+
|
45
|
+
We have two entities in our application: `Author` and `Article`.
|
46
|
+
`Author` is a `Struct`, Epiphy can persist it.
|
47
|
+
`Article` has a small API concerning its publishing process.
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
Author = Struct.new(:id, :name) do
|
51
|
+
def initialize(attributes = {})
|
52
|
+
@id, @name = attributes.values_at(:id, :name)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class Article
|
57
|
+
include Epiphy::Entity
|
58
|
+
self.attributes = :author_id, :title, :comments_count, :published # id is implicit
|
59
|
+
|
60
|
+
def published?
|
61
|
+
!!published
|
62
|
+
end
|
63
|
+
|
64
|
+
def publish!
|
65
|
+
@published = true
|
66
|
+
end
|
67
|
+
end
|
68
|
+
```
|
69
|
+
|
70
|
+
## Repositories
|
71
|
+
|
72
|
+
In order to persist and query the entities above, we define two corresponding repositories:
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
class AuthorRepository
|
76
|
+
include Epiphy::Repository
|
77
|
+
end
|
78
|
+
|
79
|
+
class ArticleRepository
|
80
|
+
include Epiphy::Repository
|
81
|
+
|
82
|
+
def self.most_recent_by_author(author, limit = 8)
|
83
|
+
query do
|
84
|
+
where(author_id: author.id).
|
85
|
+
desc(:id).
|
86
|
+
limit(limit)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.most_recent_published_by_author(author, limit = 8)
|
91
|
+
most_recent_by_author(author, limit).published
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.published
|
95
|
+
query do
|
96
|
+
where(published: true)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.drafts
|
101
|
+
exclude published
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.rank
|
105
|
+
published.desc(:comments_count)
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.best_article_ever
|
109
|
+
rank.limit(1).first
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.comments_average
|
113
|
+
query.average(:comments_count)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
```
|
117
|
+
|
118
|
+
## Persist
|
119
|
+
|
120
|
+
Let's instantiate and persist some objects for our example:
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
author = Author.new(name: 'Luca')
|
124
|
+
AuthorRepository.create(author)
|
125
|
+
|
126
|
+
articles = [
|
127
|
+
Article.new(title: 'Announcing Epiphy', author_id: author.id, comments_count: 123, published: true),
|
128
|
+
Article.new(title: 'Introducing Epiphy::Router', author_id: author.id, comments_count: 63, published: true),
|
129
|
+
Article.new(title: 'Introducing Epiphy::Controller', author_id: author.id, comments_count: 82, published: true),
|
130
|
+
Article.new(title: 'Introducing Epiphy', author_id: author.id)
|
131
|
+
]
|
132
|
+
|
133
|
+
articles.each do |article|
|
134
|
+
ArticleRepository.create(article)
|
135
|
+
end
|
136
|
+
```
|
137
|
+
|
138
|
+
## Query
|
139
|
+
|
140
|
+
We can use repositories to query the database and return the entities we're looking for:
|
141
|
+
|
142
|
+
```ruby
|
143
|
+
ArticleRepository.first # => return the first article
|
144
|
+
ArticleRepository.last # => return the last article
|
145
|
+
|
146
|
+
ArticleRepository.published # => return all the published articles
|
147
|
+
ArticleRepository.drafts # => return all the drafts
|
148
|
+
|
149
|
+
ArticleRepository.rank # => all the published articles, sorted by popularity
|
150
|
+
|
151
|
+
ArticleRepository.best_article_ever # => the most commented article
|
152
|
+
|
153
|
+
ArticleRepository.comments_average # => calculates the average of comments across all the published articles.
|
154
|
+
|
155
|
+
ArticleRepository.most_recent_by_author(author) # => most recent articles by an author (drafts and published).
|
156
|
+
ArticleRepository.most_recent_published_by_author(author) # => most recent published articles by an author
|
157
|
+
```
|
158
|
+
|
159
|
+
## Business logic
|
160
|
+
|
161
|
+
As we've seen above, `Article` implements an API for publishing.
|
162
|
+
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.
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
article = ArticleRepository.drafts.first
|
166
|
+
|
167
|
+
article.published? # => false
|
168
|
+
article.publish!
|
169
|
+
|
170
|
+
article.published? # => true
|
171
|
+
|
172
|
+
ArticleRepository.update(article)
|
173
|
+
```
|
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'epiphy/adapter/error'
|
2
|
-
|
2
|
+
|
3
3
|
module Epiphy
|
4
4
|
module Adapter
|
5
5
|
class Rethinkdb
|
@@ -102,6 +102,7 @@ module Epiphy
|
|
102
102
|
raise ArgumentError, 'Missing query block' unless block_given?
|
103
103
|
if block_given?
|
104
104
|
rql = get_table(table, database)
|
105
|
+
@current_rql = rql
|
105
106
|
rql = yield(rql, r)
|
106
107
|
end
|
107
108
|
rql.run(@connection)
|
@@ -125,9 +126,15 @@ module Epiphy
|
|
125
126
|
end
|
126
127
|
|
127
128
|
# Insert a document.
|
129
|
+
#
|
130
|
+
# When the ID is already existed, we simply return the ID if insert
|
131
|
+
# succesful. Or, the generated ID will be returned.
|
132
|
+
#
|
128
133
|
# @param collection [Symbol the target collection
|
129
134
|
# @param entity [#id, #id=] the entity to create
|
130
135
|
# @return [Object] the entity
|
136
|
+
#
|
137
|
+
# @raise
|
131
138
|
#
|
132
139
|
# @api private
|
133
140
|
# @since 0.0.1
|
@@ -136,12 +143,17 @@ module Epiphy
|
|
136
143
|
result = query table: collection do |r|
|
137
144
|
r.insert(entity)
|
138
145
|
end
|
139
|
-
rescue
|
140
|
-
|
146
|
+
rescue RethinkDB::RqlRuntimeError => e
|
147
|
+
raise e
|
141
148
|
end
|
142
149
|
|
143
150
|
if result["inserted"]==1
|
144
|
-
result["generated_keys"].
|
151
|
+
return entity["id"] if result["generated_keys"].nil?
|
152
|
+
result["generated_keys"].first
|
153
|
+
else
|
154
|
+
if result['first_error'].include? 'Duplicate primary key'
|
155
|
+
raise Epiphy::Model::EntityExisted, 'Duplicate primary key'
|
156
|
+
end
|
145
157
|
end
|
146
158
|
end
|
147
159
|
|
@@ -179,8 +191,31 @@ module Epiphy
|
|
179
191
|
query table: collection do |r|
|
180
192
|
r
|
181
193
|
end
|
182
|
-
rescue
|
183
|
-
|
194
|
+
rescue RethinkDB::RqlRuntimeError => e
|
195
|
+
raise Epiphy::Model::RuntimeError, e.message
|
196
|
+
rescue Exception =>e
|
197
|
+
raise Epiphy::Model::RuntimeError, e.message
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# Count entity in table
|
202
|
+
#
|
203
|
+
# @param collection [Symbol] the target collection (it must be mapped).
|
204
|
+
#
|
205
|
+
# @return [Integer] How many record?
|
206
|
+
#
|
207
|
+
# @api private
|
208
|
+
# @since 0.2.0
|
209
|
+
def count(collection)
|
210
|
+
# TODO consider to make this lazy (aka remove #all)
|
211
|
+
begin
|
212
|
+
query table: collection do |r|
|
213
|
+
r.count
|
214
|
+
end
|
215
|
+
rescue RethinkDB::RqlRuntimeError => e
|
216
|
+
raise Epiphy::Model::RuntimeError, e.message
|
217
|
+
rescue Exception =>e
|
218
|
+
raise Epiphy::Model::RuntimeError, e.message
|
184
219
|
end
|
185
220
|
end
|
186
221
|
|
@@ -243,8 +278,8 @@ module Epiphy
|
|
243
278
|
return result['deleted']
|
244
279
|
end
|
245
280
|
return false
|
246
|
-
rescue
|
247
|
-
|
281
|
+
rescue RethinkDB::RqlRuntimeError => e
|
282
|
+
raise Epiphy::Model::RuntimeError, e.message
|
248
283
|
end
|
249
284
|
end
|
250
285
|
|
@@ -256,10 +291,14 @@ module Epiphy
|
|
256
291
|
#
|
257
292
|
# @api private
|
258
293
|
# @since 0.1.0
|
259
|
-
def first(collection)
|
260
|
-
|
261
|
-
query
|
262
|
-
|
294
|
+
def first(collection, order_by: nil)
|
295
|
+
begin
|
296
|
+
query table: collection do |q,r|
|
297
|
+
q.order_by(r.asc(order_by)).nth(0)
|
298
|
+
end
|
299
|
+
rescue RethinkDB::RqlRuntimeError => e
|
300
|
+
return nil
|
301
|
+
end
|
263
302
|
end
|
264
303
|
|
265
304
|
# Returns the last record in the given collection.
|
@@ -270,21 +309,18 @@ module Epiphy
|
|
270
309
|
#
|
271
310
|
# @api private
|
272
311
|
# @since 0.1.0
|
273
|
-
def last(collection)
|
274
|
-
|
275
|
-
query
|
276
|
-
|
312
|
+
def last(collection, order_by: nil)
|
313
|
+
begin
|
314
|
+
query table: collection do |q, r|
|
315
|
+
q.order_by(r.desc(order_by)).nth(0)
|
316
|
+
end
|
317
|
+
rescue RethinkDB::RqlRuntimeError => e
|
318
|
+
return nil
|
319
|
+
end
|
320
|
+
|
277
321
|
end
|
278
322
|
|
279
323
|
private
|
280
|
-
def _collection(name)
|
281
|
-
raise NotImplementedError
|
282
|
-
end
|
283
|
-
|
284
|
-
def _mapped_collection(name)
|
285
|
-
@mapper.collection(name)
|
286
|
-
end
|
287
|
-
|
288
324
|
def _find(collection, id)
|
289
325
|
identity = _identity(collection)
|
290
326
|
query(collection).where(identity => _id(collection, identity, id))
|
data/lib/epiphy/model.rb
CHANGED
@@ -9,6 +9,10 @@ module Epiphy
|
|
9
9
|
#
|
10
10
|
# @since 0.1.0
|
11
11
|
module Model
|
12
|
+
|
13
|
+
class RuntimeError < RethinkDB::RqlRuntimeError
|
14
|
+
|
15
|
+
end
|
12
16
|
# Error for not found entity
|
13
17
|
#
|
14
18
|
# @since 0.1.0
|
@@ -33,5 +37,9 @@ module Epiphy
|
|
33
37
|
# @see epiphy::Repository.update
|
34
38
|
class NonPersistedEntityError < ::StandardError
|
35
39
|
end
|
40
|
+
|
41
|
+
class EntityExisted < ::StandardError
|
42
|
+
|
43
|
+
end
|
36
44
|
end
|
37
45
|
end
|
data/lib/epiphy/repository.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'lotus/utils/class_attribute'
|
2
2
|
require 'epiphy/repository/configuration'
|
3
3
|
require 'epiphy/repository/cursor'
|
4
|
+
require 'epiphy/repository/helper'
|
4
5
|
|
5
6
|
module Epiphy
|
6
7
|
# Mediates between the entities and the persistence layer, by offering an API
|
@@ -215,6 +216,7 @@ module Epiphy
|
|
215
216
|
end
|
216
217
|
|
217
218
|
module ClassMethods
|
219
|
+
include Epiphy::Repository::Helper
|
218
220
|
# Assigns an adapter.
|
219
221
|
#
|
220
222
|
# Epiphy::Repository is shipped with an adapters:
|
@@ -289,7 +291,8 @@ module Epiphy
|
|
289
291
|
# Creates a record in the database for the given entity.
|
290
292
|
# It assigns the `id` attribute, in case of success.
|
291
293
|
#
|
292
|
-
# If already persisted (`id` present) it
|
294
|
+
# If already persisted (`id` present), it will try to insert use that id
|
295
|
+
# and will raise an error if the `id` is already exist
|
293
296
|
#
|
294
297
|
# @param entity [#id,#id=] the entity to create
|
295
298
|
#
|
@@ -314,10 +317,16 @@ module Epiphy
|
|
314
317
|
#
|
315
318
|
# ArticleRepository.create(article) # no-op
|
316
319
|
def create(entity)
|
317
|
-
unless entity.id
|
320
|
+
#unless entity.id
|
321
|
+
begin
|
318
322
|
result = @adapter.create(collection, to_document(entity))
|
319
323
|
entity.id = result
|
324
|
+
rescue Epiphy::Model::EntityExisted => e
|
325
|
+
raise e
|
326
|
+
rescue RethinkDB::RqlRuntimeError => e
|
327
|
+
raise Epiphy::Model::RuntimeError, e.message
|
320
328
|
end
|
329
|
+
#end
|
321
330
|
end
|
322
331
|
|
323
332
|
# Updates a record in the database corresponding to the given entity.
|
@@ -504,8 +513,13 @@ module Epiphy
|
|
504
513
|
# end
|
505
514
|
#
|
506
515
|
# ArticleRepository.first # => nil
|
507
|
-
def first
|
508
|
-
@adapter.first(collection)
|
516
|
+
def first(order_by=:id)
|
517
|
+
result = @adapter.first(collection, order_by: order_by)
|
518
|
+
if result
|
519
|
+
to_entity result
|
520
|
+
else
|
521
|
+
result
|
522
|
+
end
|
509
523
|
end
|
510
524
|
|
511
525
|
# Returns the last entity in the database.
|
@@ -533,8 +547,12 @@ module Epiphy
|
|
533
547
|
# end
|
534
548
|
#
|
535
549
|
# ArticleRepository.last # => nil
|
536
|
-
def last
|
537
|
-
@adapter.last(collection)
|
550
|
+
def last(order_by=:id)
|
551
|
+
if result = @adapter.last(collection, order_by: order_by)
|
552
|
+
to_entity result
|
553
|
+
else
|
554
|
+
nil
|
555
|
+
end
|
538
556
|
end
|
539
557
|
|
540
558
|
# Deletes all the records from the current collection.
|
@@ -553,22 +571,16 @@ module Epiphy
|
|
553
571
|
# ArticleRepository.clear # deletes all the records
|
554
572
|
def clear
|
555
573
|
@adapter.clear(collection)
|
556
|
-
end
|
574
|
+
end
|
557
575
|
|
558
|
-
#
|
576
|
+
# Count the entity in this collection
|
559
577
|
#
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
# Drop a collection storage in database
|
567
|
-
#
|
568
|
-
def drop_collection
|
569
|
-
query do |r|
|
570
|
-
r.table_drop(self.collection)
|
571
|
-
end
|
578
|
+
# @param void
|
579
|
+
# @return Interget
|
580
|
+
# @api public
|
581
|
+
# @since 0.2.0
|
582
|
+
def count
|
583
|
+
@adapter.count(collection)
|
572
584
|
end
|
573
585
|
|
574
586
|
private
|
@@ -585,27 +597,34 @@ module Epiphy
|
|
585
597
|
# The returned query SHOULD refer to the entire collection by default.
|
586
598
|
#
|
587
599
|
# Queries can be reused and combined together. See the example below.
|
588
|
-
# IMPORTANT: This feature works only with the Sql adapter.
|
589
600
|
#
|
590
601
|
# A repository is storage independent.
|
591
602
|
# All the queries are delegated to the current adapter, which is
|
592
603
|
# responsible to implement a querying API.
|
593
604
|
#
|
594
|
-
# Epiphy::Model is shipped with
|
605
|
+
# Epiphy::Model is shipped with adapter:
|
595
606
|
#
|
596
|
-
# *
|
597
|
-
# * MemoryAdapter, which yields a Epiphy::Model::Adapters::Memory::Query
|
607
|
+
# * RethinkDB: which yields a RethinkDB::ReQL class.
|
598
608
|
#
|
599
|
-
#
|
600
|
-
#
|
609
|
+
# By default, all return items will be convert into its entity. The
|
610
|
+
# behavious can change by alter `to_entity` parameter
|
601
611
|
#
|
612
|
+
# @param to_entity [Boolean][Optional] to convert the result back to a
|
613
|
+
# entity class or not.
|
614
|
+
#
|
615
|
+
# @param blk [Proc] a block of code that is executed in the context of a
|
616
|
+
# query.
|
617
|
+
# The block will be passed two parameters. First parameter is the `reql`
|
618
|
+
# which is building. the second parameter is th `r` top name space of
|
619
|
+
# RethinkDB. By doing this, Repository doesn't have to include
|
620
|
+
# RethinkDB::Shortcuts
|
621
|
+
#
|
602
622
|
# @return a query, the type depends on the current adapter
|
603
623
|
#
|
604
624
|
# @api public
|
605
625
|
# @since 0.1.0
|
606
626
|
#
|
607
|
-
# @see Epiphy::
|
608
|
-
# @see Epiphy::Model::Adapters::Memory::Query
|
627
|
+
# @see Epiphy::Adapters::Rethinkdb
|
609
628
|
#
|
610
629
|
# @example
|
611
630
|
# require 'epiphy/model'
|
@@ -646,9 +665,27 @@ module Epiphy
|
|
646
665
|
# query.average(:comments_count)
|
647
666
|
# end
|
648
667
|
# end
|
649
|
-
def query(&blk)
|
650
|
-
@adapter.query(collection,
|
668
|
+
def query(to_entity: true, &blk)
|
669
|
+
result = @adapter.query(table: collection, &blk)
|
670
|
+
require 'pp'
|
671
|
+
if result.is_a? RethinkDB::Cursor
|
672
|
+
return Epiphy::Repository::Cursor.new result do |item|
|
673
|
+
to_entity(item)
|
674
|
+
end
|
675
|
+
end
|
676
|
+
|
677
|
+
if result.is_a? Array
|
678
|
+
result.map! do |item|
|
679
|
+
to_entity(item)
|
680
|
+
end
|
681
|
+
end
|
682
|
+
|
683
|
+
if result.is_a? Hash
|
684
|
+
return to_entity(result)
|
685
|
+
end
|
686
|
+
result
|
651
687
|
end
|
688
|
+
|
652
689
|
|
653
690
|
# Negates the filtering conditions of a given query with the logical
|
654
691
|
# opposite operator.
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Epiphy
|
2
|
+
module Repository
|
3
|
+
module Helper
|
4
|
+
|
5
|
+
# Find the first record matching the
|
6
|
+
#
|
7
|
+
# @param Keyword argument [field_name: value]
|
8
|
+
# @return Entity [Object]
|
9
|
+
# @raise Epiphy::Model::EntityNotFound if not found
|
10
|
+
#
|
11
|
+
# @api public
|
12
|
+
# @since 0.2.0
|
13
|
+
def find_by(**option)
|
14
|
+
begin
|
15
|
+
query do |r|
|
16
|
+
r.filter(option).nth(0)
|
17
|
+
end
|
18
|
+
rescue RethinkDB::RqlRuntimeError => e
|
19
|
+
#raise RethinkDB::RqlRuntimeError
|
20
|
+
raise Epiphy::Model::EntityNotFound if e.message.include?("Index out of bounds")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
#private :where, :desc#, :exlude
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Epiphy
|
2
|
+
module Schema
|
3
|
+
# Create a collection storage in database.
|
4
|
+
#
|
5
|
+
def create_collection
|
6
|
+
query do |r|
|
7
|
+
r.table_create(self.collection)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# Drop a collection storage in database
|
12
|
+
#
|
13
|
+
def drop_collection
|
14
|
+
query do |r|
|
15
|
+
r.table_drop(self.collection)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/epiphy/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: epiphy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vinh
|
@@ -59,18 +59,20 @@ executables: []
|
|
59
59
|
extensions: []
|
60
60
|
extra_rdoc_files: []
|
61
61
|
files:
|
62
|
+
- EXAMPLE.md
|
62
63
|
- README.md
|
63
64
|
- lib/epiphy.rb
|
64
65
|
- lib/epiphy/adapter/error.rb
|
65
|
-
- lib/epiphy/adapter/helper.rb
|
66
|
-
- lib/epiphy/adapter/model.rb
|
67
66
|
- lib/epiphy/adapter/rethinkdb.rb
|
68
67
|
- lib/epiphy/connection.rb
|
69
68
|
- lib/epiphy/entity.rb
|
69
|
+
- lib/epiphy/entity/timestamp.rb
|
70
70
|
- lib/epiphy/model.rb
|
71
71
|
- lib/epiphy/repository.rb
|
72
72
|
- lib/epiphy/repository/configuration.rb
|
73
73
|
- lib/epiphy/repository/cursor.rb
|
74
|
+
- lib/epiphy/repository/helper.rb
|
75
|
+
- lib/epiphy/scheme.rb
|
74
76
|
- lib/epiphy/version.rb
|
75
77
|
homepage: http://rubygems.org/gems/hola
|
76
78
|
licenses:
|
@@ -1,114 +0,0 @@
|
|
1
|
-
module Epiphy
|
2
|
-
module Adapter
|
3
|
-
class Rethinkdb
|
4
|
-
# RethinkDB method related. Should be in its helper
|
5
|
-
def insert_object
|
6
|
-
get_table.insert(
|
7
|
-
safe_params.merge(
|
8
|
-
id: params[:id],
|
9
|
-
created_at: Time.now,
|
10
|
-
updated_at: Time.now
|
11
|
-
).merge(parents)
|
12
|
-
).run(@connection)
|
13
|
-
:created
|
14
|
-
end
|
15
|
-
|
16
|
-
def update_object
|
17
|
-
get_table.update(
|
18
|
-
safe_params.merge(
|
19
|
-
id: params[:id],
|
20
|
-
updated_at: Time.now
|
21
|
-
)
|
22
|
-
).run(@connection)
|
23
|
-
:ok
|
24
|
-
end
|
25
|
-
|
26
|
-
def replace_object
|
27
|
-
get_table.replace(
|
28
|
-
safe_params.merge(
|
29
|
-
id: params[:id],
|
30
|
-
created_at: Time.now,
|
31
|
-
updated_at: Time.now
|
32
|
-
).merge(parents)
|
33
|
-
).run(@connection)
|
34
|
-
:ok
|
35
|
-
end
|
36
|
-
|
37
|
-
def delete_object
|
38
|
-
get_table.get(params[:id]).delete(
|
39
|
-
:durability => "hard", :return_vals => false
|
40
|
-
).run(@connection)
|
41
|
-
:no_content
|
42
|
-
end
|
43
|
-
|
44
|
-
def sort(qry)
|
45
|
-
ordering = params[:sort].split(",").map do |attr|
|
46
|
-
if attr[0] == "-"
|
47
|
-
r.desc(attr[1..-1].to_sym)
|
48
|
-
else
|
49
|
-
r.asc(attr.to_sym)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
qry.order_by(*ordering)
|
54
|
-
end
|
55
|
-
|
56
|
-
def select(qry)
|
57
|
-
qry = qry.get_all(*params[:ids].split(",")) if params[:ids]
|
58
|
-
qry
|
59
|
-
end
|
60
|
-
|
61
|
-
def parents
|
62
|
-
params.select {|k,v| k.match(/\A[a-z0-9_]+_id\z/i) }.compact
|
63
|
-
end
|
64
|
-
|
65
|
-
def filter(qry)
|
66
|
-
parents.empty? ? qry : qry.filter(parents)
|
67
|
-
end
|
68
|
-
|
69
|
-
def attrs
|
70
|
-
[ :id ]
|
71
|
-
end
|
72
|
-
|
73
|
-
def get_range(qry)
|
74
|
-
begin
|
75
|
-
rhdr = request.headers[:HTTP_RANGE].split("=")
|
76
|
-
|
77
|
-
if rhdr[0] == collection
|
78
|
-
qry = qry[Range.new(*rhdr[1].split("-").map(&:to_i))]
|
79
|
-
end
|
80
|
-
rescue Exception => e
|
81
|
-
puts e.message
|
82
|
-
raise Exception.new(:bad_request)
|
83
|
-
end
|
84
|
-
qry
|
85
|
-
end
|
86
|
-
|
87
|
-
def get_records
|
88
|
-
qry = get_table
|
89
|
-
qry = sort(qry) if params[:sort]
|
90
|
-
|
91
|
-
fields = if params[:fields]
|
92
|
-
params[:fields].split(",").map {|f| f.to_sym }.select do |field|
|
93
|
-
attrs.include? field
|
94
|
-
end
|
95
|
-
else
|
96
|
-
attrs
|
97
|
-
end
|
98
|
-
|
99
|
-
qry = filter(select(qry)).pluck(fields)
|
100
|
-
qry = get_range(qry) if request.headers[:HTTP_RANGE]
|
101
|
-
|
102
|
-
qry.run(@connection).map do |record|
|
103
|
-
record.merge(href: some_url(record["id"]))
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
def get_object
|
108
|
-
get_table.filter(parents.merge({id: params[:id]})).pluck(attrs).run(@connection).first
|
109
|
-
end
|
110
|
-
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
data/lib/epiphy/adapter/model.rb
DELETED
@@ -1,99 +0,0 @@
|
|
1
|
-
require 'errors'
|
2
|
-
|
3
|
-
class Rethink
|
4
|
-
include RethinkDB::Shortcuts
|
5
|
-
include RethinkdbHelper
|
6
|
-
include Errors
|
7
|
-
|
8
|
-
def initialize
|
9
|
-
create_connect_if_need
|
10
|
-
@r = @@r.table(self.class.name.downcase)
|
11
|
-
end
|
12
|
-
|
13
|
-
def all
|
14
|
-
self
|
15
|
-
end
|
16
|
-
|
17
|
-
# Get a single element by primary key
|
18
|
-
def single(id)
|
19
|
-
@r = @r.get(id)
|
20
|
-
self
|
21
|
-
end
|
22
|
-
|
23
|
-
# Return raw RethinkDB object
|
24
|
-
def raw_query
|
25
|
-
@r
|
26
|
-
end
|
27
|
-
|
28
|
-
def reset
|
29
|
-
@r = @@r.table self.class.name.downcase
|
30
|
-
end
|
31
|
-
|
32
|
-
# Run a RQL
|
33
|
-
def get(r=nil)
|
34
|
-
if r.nil?
|
35
|
-
r = @r
|
36
|
-
end
|
37
|
-
r.run(@@rdb_connection)
|
38
|
-
end
|
39
|
-
|
40
|
-
[:update, "get_all", :limit, :order_by, :slice, :count, :filter].each do |name|
|
41
|
-
define_method(name) do |*arg, &block|
|
42
|
-
@r = @r.send(name, *arg, &block)
|
43
|
-
self
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
# Command work ona single object
|
48
|
-
#{:destroy => :delete}.each do |public_name, name|
|
49
|
-
#define_method(public_name) do |*arg, &block|
|
50
|
-
#r = @@r.table self.class.name.downcase
|
51
|
-
#r.get(*arg)
|
52
|
-
#r.send(name, *arg, &block)
|
53
|
-
#r.run @@rdb_connection
|
54
|
-
#end
|
55
|
-
#end
|
56
|
-
|
57
|
-
def destroy(id)
|
58
|
-
r = @@r.table self.class.name.downcase
|
59
|
-
r.get(id)
|
60
|
-
.delete()
|
61
|
-
.run(@@rdb_connection)
|
62
|
-
end
|
63
|
-
|
64
|
-
# Validation
|
65
|
-
def self.validate(args)
|
66
|
-
if args.has_key? :errors
|
67
|
-
raise ValidationError.new('Bad request', args[:errors])
|
68
|
-
end
|
69
|
-
|
70
|
-
args
|
71
|
-
end
|
72
|
-
|
73
|
-
def self.validate_present(args, *keys)
|
74
|
-
for k in keys
|
75
|
-
unless args.has_key?(k) and args[k].present?
|
76
|
-
args[:errors] ||= {}
|
77
|
-
args[:errors][k] ||= []
|
78
|
-
args[:errors][k] << "can't be blank."
|
79
|
-
end
|
80
|
-
end
|
81
|
-
args
|
82
|
-
end
|
83
|
-
|
84
|
-
def self.validate_email(args)
|
85
|
-
if args.has_key? :email
|
86
|
-
args[:email].downcase!
|
87
|
-
|
88
|
-
unless args[:email].present? and
|
89
|
-
args[:email].match EMAIL_REGEX
|
90
|
-
|
91
|
-
args[:errors] ||= {}
|
92
|
-
args[:errors][:email] ||= []
|
93
|
-
args[:errors][:email] << "is invalid."
|
94
|
-
end
|
95
|
-
end
|
96
|
-
args
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|