epiphy 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +109 -0
- data/lib/epiphy.rb +2 -0
- data/lib/epiphy/adapter/error.rb +7 -0
- data/lib/epiphy/adapter/helper.rb +114 -0
- data/lib/epiphy/adapter/model.rb +99 -0
- data/lib/epiphy/adapter/rethinkdb.rb +326 -0
- data/lib/epiphy/connection.rb +17 -0
- data/lib/epiphy/entity.rb +152 -0
- data/lib/epiphy/model.rb +37 -0
- data/lib/epiphy/repository.rb +759 -0
- data/lib/epiphy/repository/configuration.rb +33 -0
- data/lib/epiphy/repository/cursor.rb +47 -0
- data/lib/epiphy/version.rb +6 -0
- metadata +100 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3c60ab2f9d0a39156bab381f3217ad2c8fd210d8
|
4
|
+
data.tar.gz: 24fde7963aa8d74479e2a90030928b7fc8fc7d89
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1e18a93aef681e795efd164d3467f7136fe6f938bea9d08448cfe854b4a2a4a50dd4b5ea4bc46c54a18f0bd6996c902c747449dd9448a0f3fd83cf1694ab39e5
|
7
|
+
data.tar.gz: f3549f8a25a32cc5bc2f5a6c565e2a081dd029653d499be1aa28743e293fa975de93c7f8b1a12b0915bb3e9d1153d5e25cbc156ada19fa2fc8c656a9c70f5b8c
|
data/README.md
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
# Epiphy [![wercker status](https://app.wercker.com/status/63dd458158948712a03a00d69a96f67b/m "wercker status")](https://app.wercker.com/project/bykey/63dd458158948712a03a00d69a96f67b)
|
2
|
+
|
3
|
+
## [Simply RethinkDB](http://leanpub.com/simplyrethink)
|
4
|
+
|
5
|
+
I also write this book to practice RethinkDB. Please consider buying a
|
6
|
+
copy if you want to support the author.
|
7
|
+
|
8
|
+
# Introduction
|
9
|
+
|
10
|
+
A persistence framework for [RethinkDB](http://rethinkdb.com). The library is used on [phim365.today](http://phim365.today). Its API is based on Lotus::Model.
|
11
|
+
|
12
|
+
I love Lotus::Model so much because it's lightweight, does the job, no
|
13
|
+
magic. I also should fork Lotus and add RethinkDB adapter. However, I want
|
14
|
+
to learn more Ruby and reinvent the wheel because I didn't know how the
|
15
|
+
wheel was created. More than that, my bad code will not be able to make
|
16
|
+
it to Lotus::Model.
|
17
|
+
|
18
|
+
It delivers a convenient public API to execute queries and commands against a database.
|
19
|
+
The architecture eases keeping the business logic (entities) separated from details such as persistence or validations.
|
20
|
+
|
21
|
+
It implements the following concepts:
|
22
|
+
|
23
|
+
* [Entity](#entities) - An object defined by its identity.
|
24
|
+
* [Repository](#repositories) - An object that mediates between the entities and the persistence layer.
|
25
|
+
* [Adapter](#adapter) – A database adapter.
|
26
|
+
* [Query](#query) - An object that represents a database query.
|
27
|
+
|
28
|
+
`Epiphy` is name after `Epiphyllum`, my spouse's name.
|
29
|
+
|
30
|
+
# Install
|
31
|
+
|
32
|
+
```
|
33
|
+
gem install epiphy
|
34
|
+
```
|
35
|
+
|
36
|
+
or add
|
37
|
+
|
38
|
+
```
|
39
|
+
gem 'epiphy'
|
40
|
+
```
|
41
|
+
|
42
|
+
to your `Gemfile` if you use Bundle. Run `bundle install`
|
43
|
+
|
44
|
+
|
45
|
+
# Testing
|
46
|
+
|
47
|
+
`Minitest` is used for testing.
|
48
|
+
|
49
|
+
Make sure you have a working RethinkDB with default connection
|
50
|
+
information that is localhost, port 28015, without authentication key
|
51
|
+
and run
|
52
|
+
|
53
|
+
```
|
54
|
+
$ bundle install
|
55
|
+
$ rake test
|
56
|
+
```
|
57
|
+
|
58
|
+
A testing database will be created during the testing. The testing data
|
59
|
+
will hit your RethinkDB. Depend on your storge system, test can fast or
|
60
|
+
slow.
|
61
|
+
|
62
|
+
# Example
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
connection = Epiphy::Connection.create
|
66
|
+
adapter = Epiphy::Adapter::RethinkDB.new connection
|
67
|
+
RethinkDB::Repository.configure do |r|
|
68
|
+
r.adapter = adapter
|
69
|
+
end
|
70
|
+
|
71
|
+
class Movie
|
72
|
+
include Epiphy::Entity
|
73
|
+
include Epiphy::Entity::Timestamp
|
74
|
+
|
75
|
+
attributes :title, :url
|
76
|
+
end
|
77
|
+
|
78
|
+
class MovieRepository
|
79
|
+
include Epiphy::Repository
|
80
|
+
end
|
81
|
+
|
82
|
+
movie = MovieRepository.find id # Find by id
|
83
|
+
|
84
|
+
movie = MovieRepository.first
|
85
|
+
movie = MovieRepository.last
|
86
|
+
|
87
|
+
movie = Movie.new
|
88
|
+
movie.title = "A movie"
|
89
|
+
MovieRepository.create movie
|
90
|
+
MovieRepository.update movie
|
91
|
+
|
92
|
+
|
93
|
+
|
94
|
+
```
|
95
|
+
|
96
|
+
# Contributing to epiphy
|
97
|
+
|
98
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
99
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
|
100
|
+
* Fork the project.
|
101
|
+
* Start a feature/bugfix branch.
|
102
|
+
* Commit and push until you are happy with your contribution.
|
103
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
104
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
105
|
+
|
106
|
+
# Copyright
|
107
|
+
|
108
|
+
Copyright (c) 2014 kureikain. See LICENSE.txt for
|
109
|
+
further details.
|
data/lib/epiphy.rb
ADDED
@@ -0,0 +1,114 @@
|
|
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
|
+
|
@@ -0,0 +1,99 @@
|
|
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
|
+
|
@@ -0,0 +1,326 @@
|
|
1
|
+
require 'epiphy/adapter/error'
|
2
|
+
#require 'epiphy/adapter/helper'
|
3
|
+
module Epiphy
|
4
|
+
module Adapter
|
5
|
+
class Rethinkdb
|
6
|
+
include RethinkDB::Shortcuts
|
7
|
+
# Create an adapter object, with the option to pass a connection
|
8
|
+
# object as dependency.
|
9
|
+
#
|
10
|
+
# Without the dattabase string, Epiphy will assumes a `test`
|
11
|
+
# database just as RethinkDB do by default.
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# connection = Epiphy::Connection.create
|
15
|
+
# adapter = Epiphy::Adapter::Rethinkdb.new(connection,
|
16
|
+
# default_database)
|
17
|
+
#
|
18
|
+
# @param connection [RethinkDB::Connection]
|
19
|
+
# @param database [String] database name
|
20
|
+
#
|
21
|
+
# @api public
|
22
|
+
# @since 0.0.1
|
23
|
+
#
|
24
|
+
#
|
25
|
+
def initialize(conn, database: 'test')
|
26
|
+
self.connection=(conn)
|
27
|
+
self.database=(database) unless database.nil?
|
28
|
+
end
|
29
|
+
|
30
|
+
# Assign a RethinkDB connection for this adapter.
|
31
|
+
#
|
32
|
+
# @param connection [RethinkDB::Connection]
|
33
|
+
# @return [Object] the current adapter. enable us to continue
|
34
|
+
# chain it
|
35
|
+
#
|
36
|
+
# @api public
|
37
|
+
# @since 0.1.0
|
38
|
+
def connection= (connection)
|
39
|
+
@connection = connection
|
40
|
+
end
|
41
|
+
|
42
|
+
# Set the current, default database for this connection.
|
43
|
+
#
|
44
|
+
# At, any time, you can re-set this to change the database.
|
45
|
+
# Doing so will trigger the adapter to switch to the new
|
46
|
+
# database.
|
47
|
+
#
|
48
|
+
# At RethinkDB level, this is similar with what we do with
|
49
|
+
# r.db('foo')
|
50
|
+
#
|
51
|
+
# @example
|
52
|
+
# adapter.database("test")
|
53
|
+
# # Subsequent query will be run on `test` database
|
54
|
+
# adapter.database("foo")
|
55
|
+
# # Subsequent query will be run on `foo` database
|
56
|
+
#
|
57
|
+
#
|
58
|
+
def database= (db)
|
59
|
+
@database = db
|
60
|
+
end
|
61
|
+
|
62
|
+
# Execute a ReQL query. The database and table is passed as
|
63
|
+
# parameter and the query is build by a block.
|
64
|
+
#
|
65
|
+
# The table param can be omitted so we can run the drop, create
|
66
|
+
# table
|
67
|
+
#
|
68
|
+
# With a valid database param, only this query will be run on it. To set
|
69
|
+
# a persitent different database for subsequent queries, consider
|
70
|
+
# set a different database.
|
71
|
+
#
|
72
|
+
# The block is passed 2 object. The first is the ReQL at the
|
73
|
+
# table() level. The second is the ReQL at top name space level
|
74
|
+
#
|
75
|
+
# @see Epiphy::Adapter::Rethinkdb#database=
|
76
|
+
#
|
77
|
+
# @example
|
78
|
+
# # To create a table
|
79
|
+
# adapter.query do |r, rt|
|
80
|
+
# r.table_create('table_name')
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
# # To filter
|
84
|
+
# adapter.query table: 'movie' do |r|
|
85
|
+
# r.filter({category: 'romantic'})
|
86
|
+
# end
|
87
|
+
#
|
88
|
+
# # To Drop a database
|
89
|
+
# adapter.query do |t, r|
|
90
|
+
# r.db_drop 'dbname'
|
91
|
+
# end
|
92
|
+
#
|
93
|
+
# @param collection [Epiphy::Repository]
|
94
|
+
# @param table [String]
|
95
|
+
# @param database [String]
|
96
|
+
#
|
97
|
+
# @return query result of RethinkDB::run
|
98
|
+
#
|
99
|
+
# @since 0.0.1
|
100
|
+
# @api private
|
101
|
+
def query(table: nil, database: nil)
|
102
|
+
raise ArgumentError, 'Missing query block' unless block_given?
|
103
|
+
if block_given?
|
104
|
+
rql = get_table(table, database)
|
105
|
+
rql = yield(rql, r)
|
106
|
+
end
|
107
|
+
rql.run(@connection)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Creates or updates a record in the database for the given entity.
|
111
|
+
#
|
112
|
+
# @param collection [Symbol] the target collection (it must be mapped).
|
113
|
+
# @param entity [#id, #id=] the entity to persist
|
114
|
+
#
|
115
|
+
# @return [Object] the entity
|
116
|
+
#
|
117
|
+
# @api private
|
118
|
+
# @since 0.1.0
|
119
|
+
def persist(collection, entity)
|
120
|
+
if entity["id"]
|
121
|
+
update(collection, entity)
|
122
|
+
else
|
123
|
+
create(collection, entity)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Insert a document.
|
128
|
+
# @param collection [Symbol the target collection
|
129
|
+
# @param entity [#id, #id=] the entity to create
|
130
|
+
# @return [Object] the entity
|
131
|
+
#
|
132
|
+
# @api private
|
133
|
+
# @since 0.0.1
|
134
|
+
def create(collection, entity)
|
135
|
+
begin
|
136
|
+
result = query table: collection do |r|
|
137
|
+
r.insert(entity)
|
138
|
+
end
|
139
|
+
rescue
|
140
|
+
return false
|
141
|
+
end
|
142
|
+
|
143
|
+
if result["inserted"]==1
|
144
|
+
result["generated_keys"].first
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# Insert a document.
|
149
|
+
# @param collection [Symbol the target collection
|
150
|
+
# @param entity [#id, #id=] the entity to create
|
151
|
+
# @return [Object] the entity
|
152
|
+
#
|
153
|
+
# @api private
|
154
|
+
# @since 0.0.1
|
155
|
+
def update(collection, entity)
|
156
|
+
begin
|
157
|
+
result = query table: collection do |r|
|
158
|
+
r.get(entity["id"]).update(entity)
|
159
|
+
end
|
160
|
+
rescue
|
161
|
+
return false
|
162
|
+
end
|
163
|
+
return result["replaced"]
|
164
|
+
end
|
165
|
+
|
166
|
+
|
167
|
+
# Returns all the records for the given collection
|
168
|
+
#
|
169
|
+
# @param collection [Symbol] the target collection (it must be mapped).
|
170
|
+
#
|
171
|
+
# @return [Array] all the records
|
172
|
+
#
|
173
|
+
# @api private
|
174
|
+
# @since 0.1.0
|
175
|
+
def all(collection)
|
176
|
+
# TODO consider to make this lazy (aka remove #all)
|
177
|
+
#query(collection).all
|
178
|
+
begin
|
179
|
+
query table: collection do |r|
|
180
|
+
r
|
181
|
+
end
|
182
|
+
rescue
|
183
|
+
return false
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
# Returns an unique record from the given collection, with the given
|
188
|
+
# id.
|
189
|
+
#
|
190
|
+
# @param collection [Symbol] the target collection (it must be mapped).
|
191
|
+
# @param id [Object] the identity of the object.
|
192
|
+
#
|
193
|
+
# @return [Object] the entity
|
194
|
+
#
|
195
|
+
# @api private
|
196
|
+
# @since 0.1.0
|
197
|
+
def find(collection, id)
|
198
|
+
#begin
|
199
|
+
result = query table: collection do |r|
|
200
|
+
r.get(id)
|
201
|
+
end
|
202
|
+
#rescue
|
203
|
+
#end
|
204
|
+
result
|
205
|
+
end
|
206
|
+
|
207
|
+
# Remove the record from the given collection, with the given id
|
208
|
+
#
|
209
|
+
# @param collection [Symbol] the target collection
|
210
|
+
# @param id [Object] the identity of the object
|
211
|
+
#
|
212
|
+
# @return [Object] the entity
|
213
|
+
#
|
214
|
+
# @api private
|
215
|
+
# @since 0.1.0
|
216
|
+
def delete(collection, id)
|
217
|
+
begin
|
218
|
+
result = query table: collection do |r|
|
219
|
+
r.get(id).delete()
|
220
|
+
end
|
221
|
+
if result["errors"] == 0
|
222
|
+
return result["deleted"]
|
223
|
+
end
|
224
|
+
return false
|
225
|
+
rescue
|
226
|
+
return false
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
# Delete all records from the table
|
231
|
+
#
|
232
|
+
# @param collection [Symbol] the target collection
|
233
|
+
# @return how many entry we removed
|
234
|
+
#
|
235
|
+
# @since 0.0.1
|
236
|
+
# @api private
|
237
|
+
def clear(collection)
|
238
|
+
begin
|
239
|
+
result = query table: collection do |r|
|
240
|
+
r.delete()
|
241
|
+
end
|
242
|
+
if result["errors"] == 0
|
243
|
+
return result['deleted']
|
244
|
+
end
|
245
|
+
return false
|
246
|
+
rescue
|
247
|
+
return false
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
# Returns the first record in the given collection.
|
252
|
+
#
|
253
|
+
# @param collection [Symbol] the target collection (it must be mapped).
|
254
|
+
#
|
255
|
+
# @return [Object] the first entity
|
256
|
+
#
|
257
|
+
# @api private
|
258
|
+
# @since 0.1.0
|
259
|
+
def first(collection)
|
260
|
+
_first(
|
261
|
+
query(collection).asc(_identity(collection))
|
262
|
+
)
|
263
|
+
end
|
264
|
+
|
265
|
+
# Returns the last record in the given collection.
|
266
|
+
#
|
267
|
+
# @param collection [Symbol] the target collection (it must be mapped).
|
268
|
+
#
|
269
|
+
# @return [Object] the last entity
|
270
|
+
#
|
271
|
+
# @api private
|
272
|
+
# @since 0.1.0
|
273
|
+
def last(collection)
|
274
|
+
_first(
|
275
|
+
query(collection).desc(_identity(collection))
|
276
|
+
)
|
277
|
+
end
|
278
|
+
|
279
|
+
private
|
280
|
+
def _collection(name)
|
281
|
+
raise NotImplementedError
|
282
|
+
end
|
283
|
+
|
284
|
+
def _mapped_collection(name)
|
285
|
+
@mapper.collection(name)
|
286
|
+
end
|
287
|
+
|
288
|
+
def _find(collection, id)
|
289
|
+
identity = _identity(collection)
|
290
|
+
query(collection).where(identity => _id(collection, identity, id))
|
291
|
+
end
|
292
|
+
|
293
|
+
def _first(query)
|
294
|
+
query.limit(1).first
|
295
|
+
end
|
296
|
+
|
297
|
+
def _identity(collection)
|
298
|
+
_mapped_collection(collection).identity
|
299
|
+
end
|
300
|
+
|
301
|
+
def _id(collection, column, value)
|
302
|
+
_mapped_collection(collection).deserialize_attribute(column, value)
|
303
|
+
end
|
304
|
+
|
305
|
+
protected
|
306
|
+
|
307
|
+
# Return a ReQL wrapper of a table to start query chaining.
|
308
|
+
#
|
309
|
+
# This is just a lighweight wrapper of `r.db().table()`
|
310
|
+
# @see
|
311
|
+
# @param database [string]. default to current database
|
312
|
+
# @param table [string]
|
313
|
+
# @api private
|
314
|
+
# @since 0.0.1
|
315
|
+
def get_table(table, database=nil)
|
316
|
+
database = @database if database.nil?
|
317
|
+
raise Epiphy::Adapter::MissingDatabaseError "Missing a default database name" if database.nil?
|
318
|
+
rql = r.db(database)
|
319
|
+
rql = rql.table(table) unless table.nil?
|
320
|
+
rql
|
321
|
+
end
|
322
|
+
|
323
|
+
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|