reorm 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +219 -0
- data/Rakefile +2 -0
- data/config/database.yml +11 -0
- data/lib/reorm/configuration.rb +12 -0
- data/lib/reorm/cursor.rb +162 -0
- data/lib/reorm/exceptions.rb +13 -0
- data/lib/reorm/field_path.rb +53 -0
- data/lib/reorm/model.rb +132 -0
- data/lib/reorm/modules/database_modules.rb +67 -0
- data/lib/reorm/modules/event_modules.rb +82 -0
- data/lib/reorm/modules/validation_modules.rb +29 -0
- data/lib/reorm/modules.rb +7 -0
- data/lib/reorm/property_errors.rb +53 -0
- data/lib/reorm/validators/exclusion_validator.rb +18 -0
- data/lib/reorm/validators/inclusion_validator.rb +18 -0
- data/lib/reorm/validators/maximum_length_validator.rb +19 -0
- data/lib/reorm/validators/minimum_length_validator.rb +19 -0
- data/lib/reorm/validators/presence_validator.rb +17 -0
- data/lib/reorm/validators/validator.rb +13 -0
- data/lib/reorm/validators.rb +10 -0
- data/lib/reorm/version.rb +6 -0
- data/lib/reorm.rb +47 -0
- data/reorm.gemspec +30 -0
- data/spec/catwalk/modules/timestamped_spec.rb +17 -0
- data/spec/reorm/cursor_spec.rb +214 -0
- data/spec/reorm/field_path_spec.rb +65 -0
- data/spec/reorm/model_spec.rb +268 -0
- data/spec/reorm/modules/event_source_spec.rb +49 -0
- data/spec/reorm/modules/table_backed_spec.rb +46 -0
- data/spec/reorm/modules/timestamped_spec.rb +28 -0
- data/spec/reorm/modules/validation_modules_spec.rb +157 -0
- data/spec/reorm/property_errors_spec.rb +120 -0
- data/spec/reorm/validators/exclusion_validator_spec.rb +34 -0
- data/spec/reorm/validators/inclusion_validator_spec.rb +36 -0
- data/spec/reorm/validators/maximum_length_validator_spec.rb +37 -0
- data/spec/reorm/validators/minimum_length_validator_spec.rb +39 -0
- data/spec/reorm/validators/presence_validator_spec.rb +44 -0
- data/spec/reorm/validators/validator_spec.rb +23 -0
- data/spec/spec_helper.rb +118 -0
- metadata +216 -0
@@ -0,0 +1,67 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
# Copyright (c), 2015 Peter Wood
|
3
|
+
# See the license.txt for details of the licensing of the code in this file.
|
4
|
+
|
5
|
+
module Reorm
|
6
|
+
module ClassDatabaseSettings
|
7
|
+
@@class_db_settings = {}
|
8
|
+
|
9
|
+
def assign_table_defaults
|
10
|
+
@@class_db_settings[self] = {primary_key: :id,
|
11
|
+
table_name: self.name.split("::").last.tableize}
|
12
|
+
end
|
13
|
+
|
14
|
+
def table_settings
|
15
|
+
assign_table_defaults if !@@class_db_settings.include?(self)
|
16
|
+
@@class_db_settings[self]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module PrimaryKeyedClassMethods
|
21
|
+
def primary_key(field=nil)
|
22
|
+
table_settings[:primary_key] = field if !field.nil?
|
23
|
+
table_settings[:primary_key]
|
24
|
+
end
|
25
|
+
|
26
|
+
def table_name(name=nil)
|
27
|
+
table_settings[:table_name] = name if !name.nil?
|
28
|
+
table_settings[:table_name]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module PrimaryKeyedInstanceMethods
|
33
|
+
def primary_key
|
34
|
+
self.class.primary_key
|
35
|
+
end
|
36
|
+
|
37
|
+
def table_name
|
38
|
+
self.class.table_name
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# A module that defines the data and methods for a class that is backed on to
|
43
|
+
# a database table.
|
44
|
+
module TableBacked
|
45
|
+
def TableBacked.included(target)
|
46
|
+
target.extend(ClassDatabaseSettings)
|
47
|
+
target.extend(PrimaryKeyedClassMethods)
|
48
|
+
target.include(PrimaryKeyedInstanceMethods)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
module Timestamped
|
53
|
+
def set_created_at
|
54
|
+
self.created_at = Time.now
|
55
|
+
self.updated_at = nil
|
56
|
+
end
|
57
|
+
|
58
|
+
def set_updated_at
|
59
|
+
self.updated_at = Time.now
|
60
|
+
end
|
61
|
+
|
62
|
+
def Timestamped.included(target)
|
63
|
+
target.before_create :set_created_at
|
64
|
+
target.before_update :set_updated_at
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
# Copyright (c), 2015 Peter Wood
|
3
|
+
# See the license.txt for details of the licensing of the code in this file.
|
4
|
+
|
5
|
+
module Reorm
|
6
|
+
# A module that defines the class level data used for events.
|
7
|
+
module EventData
|
8
|
+
@@class_events = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
# A module that defines the class level methods for setting event handlers.
|
12
|
+
module EventHandler
|
13
|
+
include EventData
|
14
|
+
|
15
|
+
def after_create(*methods)
|
16
|
+
store_event_handlers(:after_create, *methods)
|
17
|
+
end
|
18
|
+
|
19
|
+
def after_save(*methods)
|
20
|
+
store_event_handlers(:after_save, *methods)
|
21
|
+
end
|
22
|
+
|
23
|
+
def after_update(*methods)
|
24
|
+
store_event_handlers(:after_update, *methods)
|
25
|
+
end
|
26
|
+
|
27
|
+
def after_validate(*methods)
|
28
|
+
store_event_handlers(:after_validate, *methods)
|
29
|
+
end
|
30
|
+
|
31
|
+
def before_create(*methods)
|
32
|
+
store_event_handlers(:before_create, *methods)
|
33
|
+
end
|
34
|
+
|
35
|
+
def before_save(*methods)
|
36
|
+
store_event_handlers(:before_save, *methods)
|
37
|
+
end
|
38
|
+
|
39
|
+
def before_update(*methods)
|
40
|
+
store_event_handlers(:before_update, *methods)
|
41
|
+
end
|
42
|
+
|
43
|
+
def before_validate(*methods)
|
44
|
+
store_event_handlers(:before_validate, *methods)
|
45
|
+
end
|
46
|
+
|
47
|
+
def store_event_handlers(event, *methods)
|
48
|
+
if self.instance_of?(Class)
|
49
|
+
@@class_events[self] = {} if !@@class_events.include?(self)
|
50
|
+
@@class_events[self][event] = [] if !@@class_events[self].include?(event)
|
51
|
+
@@class_events[self][event] = @@class_events[self][event].concat(methods).uniq
|
52
|
+
else
|
53
|
+
self.class.store_event_handlers(event, *methods)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# A module that is used to provide the methods the create events.
|
59
|
+
module EventSource
|
60
|
+
include EventData
|
61
|
+
|
62
|
+
def fire_events(settings={})
|
63
|
+
events = settings[:events]
|
64
|
+
if events && !events.empty?
|
65
|
+
object = (settings[:target] ? settings[:target] : self)
|
66
|
+
handlers = @@class_events[self.instance_of?(Class) ? self : self.class]
|
67
|
+
if handlers && !handlers.empty?
|
68
|
+
events.each do |event|
|
69
|
+
if handlers.include?(event)
|
70
|
+
handlers[event].each do |handler|
|
71
|
+
if !object.respond_to?(handler)
|
72
|
+
raise Error, "Unable to locate a method called '#{event}' for an instance of the #{object.class.name} class."
|
73
|
+
end
|
74
|
+
object.__send__(handler)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
# Copyright (c), 2015 Peter Wood
|
3
|
+
# See the license.txt for details of the licensing of the code in this file.
|
4
|
+
|
5
|
+
module Reorm
|
6
|
+
module Validations
|
7
|
+
def validate_presence_of(field)
|
8
|
+
PresenceValidator.new(*field).validate(self)
|
9
|
+
end
|
10
|
+
|
11
|
+
def validate_length_of(field, options={})
|
12
|
+
if options.include?(:minimum)
|
13
|
+
MinimumLengthValidator.new(options[:minimum], *field).validate(self)
|
14
|
+
end
|
15
|
+
|
16
|
+
if options.include?(:maximum)
|
17
|
+
MaximumLengthValidator.new(options[:maximum], *field).validate(self)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def validate_inclusion_of(field, *values)
|
22
|
+
InclusionValidator.new(values, *field).validate(self)
|
23
|
+
end
|
24
|
+
|
25
|
+
def validate_exclusion_of(field, *values)
|
26
|
+
ExclusionValidator.new(values, *field).validate(self)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
# Copyright (c), 2015 Peter Wood
|
3
|
+
# See the license.txt for details of the licensing of the code in this file.
|
4
|
+
|
5
|
+
module Reorm
|
6
|
+
class PropertyErrors
|
7
|
+
def initialize
|
8
|
+
@errors = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def clear?
|
12
|
+
@errors.empty?
|
13
|
+
end
|
14
|
+
|
15
|
+
def reset
|
16
|
+
@errors = {}
|
17
|
+
end
|
18
|
+
|
19
|
+
def include?(property)
|
20
|
+
@errors.include?(property.to_sym)
|
21
|
+
end
|
22
|
+
|
23
|
+
def add(property, message)
|
24
|
+
if ![nil, ""].include?(message)
|
25
|
+
@errors[property.to_sym] = [] if !@errors.include?(property.to_sym)
|
26
|
+
@errors[property.to_sym] << message
|
27
|
+
end
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
def properties
|
32
|
+
@errors.keys
|
33
|
+
end
|
34
|
+
|
35
|
+
def messages(property)
|
36
|
+
[].concat(@errors.fetch(property.to_sym, []))
|
37
|
+
end
|
38
|
+
|
39
|
+
def each
|
40
|
+
@errors.each {|property, messages| yield(property, messages)}
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_s
|
44
|
+
text = StringIO.new
|
45
|
+
@errors.each do |property, messages|
|
46
|
+
messages.each do |message|
|
47
|
+
text << "#{text.size > 0 ? "\n" : ""}#{property} #{message}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
text.string
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
# Copyright (c), 2015 Peter Wood
|
3
|
+
# See the license.txt for details of the licensing of the code in this file.
|
4
|
+
|
5
|
+
module Reorm
|
6
|
+
class ExclusionValidator < Validator
|
7
|
+
def initialize(values, *field)
|
8
|
+
super("is set to an unpermitted value.", *field)
|
9
|
+
@values = [].concat(values)
|
10
|
+
end
|
11
|
+
|
12
|
+
def validate(object)
|
13
|
+
if @values.include?(field.value(object))
|
14
|
+
object.errors.add(field.to_s, message)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
# Copyright (c), 2015 Peter Wood
|
3
|
+
# See the license.txt for details of the licensing of the code in this file.
|
4
|
+
|
5
|
+
module Reorm
|
6
|
+
class InclusionValidator < Validator
|
7
|
+
def initialize(values, *field)
|
8
|
+
super("is not set to one of its permitted values.", *field)
|
9
|
+
@values = values
|
10
|
+
end
|
11
|
+
|
12
|
+
def validate(object)
|
13
|
+
if !@values.include?(field.value(object))
|
14
|
+
object.errors.add(field.to_s, message)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
# Copyright (c), 2015 Peter Wood
|
3
|
+
# See the license.txt for details of the licensing of the code in this file.
|
4
|
+
|
5
|
+
module Reorm
|
6
|
+
class MaximumLengthValidator < Validator
|
7
|
+
def initialize(length, *field)
|
8
|
+
super("is too long (maximum length is #{length} characters).", *field)
|
9
|
+
@length = length
|
10
|
+
end
|
11
|
+
|
12
|
+
def validate(object)
|
13
|
+
value = field.value(object)
|
14
|
+
if value && value.to_s.length > @length
|
15
|
+
object.errors.add field.to_s, message
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
# Copyright (c), 2015 Peter Wood
|
3
|
+
# See the license.txt for details of the licensing of the code in this file.
|
4
|
+
|
5
|
+
module Reorm
|
6
|
+
class MinimumLengthValidator < Validator
|
7
|
+
def initialize(length, *field)
|
8
|
+
super("is too short (minimum length is #{length} characters).", *field)
|
9
|
+
@length = length
|
10
|
+
end
|
11
|
+
|
12
|
+
def validate(object)
|
13
|
+
value = field.value(object)
|
14
|
+
if [nil, ""].include?(value) || value.to_s.length < @length
|
15
|
+
object.errors.add field.to_s, message
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
# Copyright (c), 2015 Peter Wood
|
3
|
+
# See the license.txt for details of the licensing of the code in this file.
|
4
|
+
|
5
|
+
module Reorm
|
6
|
+
class PresenceValidator < Validator
|
7
|
+
def initialize(*field)
|
8
|
+
super("cannot be blank.", *field)
|
9
|
+
end
|
10
|
+
|
11
|
+
def validate(object)
|
12
|
+
if !field.exists?(object) || [nil, ""].include?(field.value(object))
|
13
|
+
object.errors.add(field.to_s, message)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
# Copyright (c), 2015 Peter Wood
|
3
|
+
# See the license.txt for details of the licensing of the code in this file.
|
4
|
+
|
5
|
+
module Reorm
|
6
|
+
class Validator
|
7
|
+
def initialize(message, *field)
|
8
|
+
@field = FieldPath.new(*field)
|
9
|
+
@message = message
|
10
|
+
end
|
11
|
+
attr_reader :field, :message
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
# Copyright (c), 2015 Peter Wood
|
3
|
+
# See the license.txt for details of the licensing of the code in this file.
|
4
|
+
|
5
|
+
require "reorm/validators/validator"
|
6
|
+
require "reorm/validators/presence_validator"
|
7
|
+
require "reorm/validators/minimum_length_validator"
|
8
|
+
require "reorm/validators/maximum_length_validator"
|
9
|
+
require "reorm/validators/inclusion_validator"
|
10
|
+
require "reorm/validators/exclusion_validator"
|
data/lib/reorm.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
# Copyright (c), 2015 Peter Wood
|
3
|
+
# See the license.txt for details of the licensing of the code in this file.
|
4
|
+
|
5
|
+
require "active_support/inflector"
|
6
|
+
require "configurative"
|
7
|
+
require "connection_pool"
|
8
|
+
require "logjam"
|
9
|
+
require "rethinkdb"
|
10
|
+
require "stringio"
|
11
|
+
require "reorm/version"
|
12
|
+
require "reorm/exceptions"
|
13
|
+
require "reorm/configuration"
|
14
|
+
require "reorm/field_path"
|
15
|
+
require "reorm/modules"
|
16
|
+
require "reorm/validators"
|
17
|
+
require "reorm/model"
|
18
|
+
require "reorm/property_errors"
|
19
|
+
require "reorm/cursor"
|
20
|
+
|
21
|
+
include RethinkDB::Shortcuts
|
22
|
+
|
23
|
+
module Reorm
|
24
|
+
# Constants used with Rethinkdb connections.
|
25
|
+
DEFAULT_SIZE = 5
|
26
|
+
DEFAULT_TIMEOUT = 5
|
27
|
+
SETTINGS_NAMES = [:host, :port, :db, :auth_key, :timeout]
|
28
|
+
|
29
|
+
# Module property containing the connection pool.
|
30
|
+
@@reorm_connections = ConnectionPool.new(size: Configuration.fetch(:size, DEFAULT_SIZE),
|
31
|
+
timeout: Configuration.fetch(:timeout, DEFAULT_TIMEOUT)) do
|
32
|
+
settings = Configuration.instance.to_h.inject({}) do |store, entry|
|
33
|
+
store[entry[0]] = entry[1] if SETTINGS_NAMES.include?(entry[0])
|
34
|
+
store
|
35
|
+
end
|
36
|
+
r.connect(settings)
|
37
|
+
end
|
38
|
+
|
39
|
+
# A method for obtaining a connection from the module connection pool. The
|
40
|
+
# intended usage is that you call this method with a block that will be passed
|
41
|
+
# the connection as a parameter.
|
42
|
+
def self.connection
|
43
|
+
@@reorm_connections.with do |connection|
|
44
|
+
yield connection
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/reorm.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'reorm/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "reorm"
|
8
|
+
spec.version = Reorm::VERSION
|
9
|
+
spec.authors = ["Peter Wood"]
|
10
|
+
spec.email = ["peter.wood@longboat.com"]
|
11
|
+
spec.summary = %q{A library for use with the RethinkDB application.}
|
12
|
+
spec.description = %q{A library the wraps RQL and provides a basic model class for the RethinkDB system.}
|
13
|
+
spec.homepage = "https://github.com/free-beer/reorm"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
spec.add_development_dependency "rspec", "~> 3.3"
|
24
|
+
|
25
|
+
spec.add_dependency "activesupport", "~> 4.2"
|
26
|
+
spec.add_dependency "configurative", "~> 0.1"
|
27
|
+
spec.add_dependency "connection_pool", "~> 2.2"
|
28
|
+
spec.add_dependency "logjam", "~> 1.2"
|
29
|
+
spec.add_dependency "rethinkdb", "~> 2.0"
|
30
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
class TimestampedTestClass < Reorm::Model
|
4
|
+
extend Reorm::Timestamped
|
5
|
+
end
|
6
|
+
|
7
|
+
describe Reorm::Timestamped do
|
8
|
+
subject {
|
9
|
+
TimestampedTestClass
|
10
|
+
}
|
11
|
+
|
12
|
+
it "set the timestamp fields when a record is created" do
|
13
|
+
record = subject.create(label: "First")
|
14
|
+
expect(record.created_at).not_to be_nil
|
15
|
+
expect(record.updated_at).to be_nil
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
class CursorTestModel < Reorm::Model
|
4
|
+
end
|
5
|
+
|
6
|
+
describe Reorm::Cursor do
|
7
|
+
subject {
|
8
|
+
Reorm::Cursor.new(CursorTestModel, r.table(CursorTestModel.table_name))
|
9
|
+
}
|
10
|
+
|
11
|
+
before do
|
12
|
+
CursorTestModel.create(index: 1)
|
13
|
+
CursorTestModel.create(index: 2)
|
14
|
+
CursorTestModel.create(index: 3)
|
15
|
+
CursorTestModel.create(index: 4)
|
16
|
+
CursorTestModel.create(index: 5)
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#close" do
|
20
|
+
it "silently does nothing if the internal cursor isn't open" do
|
21
|
+
expect {
|
22
|
+
subject.close
|
23
|
+
}.not_to raise_exception
|
24
|
+
end
|
25
|
+
|
26
|
+
it "cleans up where the internal cursor is open" do
|
27
|
+
expect(subject.next).not_to be_nil
|
28
|
+
expect {
|
29
|
+
subject.close
|
30
|
+
}.not_to raise_exception
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#count()" do
|
35
|
+
it "returns a count of the number of rows that the cursor will iterate across" do
|
36
|
+
expect(subject.count).to eq(5)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "#exhausted?()" do
|
41
|
+
it "returns false if the internal cursor isn't open" do
|
42
|
+
expect(subject.exhausted?).to eq(false)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "returns false if the internal cursor is open but there are rows remaining" do
|
46
|
+
expect(subject.next).not_to be_nil
|
47
|
+
expect(subject.exhausted?).to eq(false)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "returns true if the internal cursor is open and there are no more rows remaining" do
|
51
|
+
subject.count.times {expect(subject.next).not_to be_nil}
|
52
|
+
expect(subject.exhausted?).to eq(true)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "#find()" do
|
57
|
+
it "returns the first row that matches the search block" do
|
58
|
+
model = subject.find {|record| record.index == 3}
|
59
|
+
expect(model).not_to be_nil
|
60
|
+
expect(model.class).to eq(CursorTestModel)
|
61
|
+
end
|
62
|
+
|
63
|
+
it "returns nil if a matching record cannot be found" do
|
64
|
+
model = subject.find {|record| record.index == 3000}
|
65
|
+
expect(model).to be_nil
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "#next()" do
|
70
|
+
describe "when used without an order by clause" do
|
71
|
+
it "returns the next record from the cursor" do
|
72
|
+
(1..5).each do |index|
|
73
|
+
model = subject.next
|
74
|
+
expect(model).not_to be_nil
|
75
|
+
expect((1..5)).to include(model.index)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
it "raises an exception if called on an exhausted cursor" do
|
80
|
+
5.times {subject.next}
|
81
|
+
expect {
|
82
|
+
subject.next
|
83
|
+
}.to raise_exception(Reorm::Error, "There are no more matching records.")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe "when used with an order by clause" do
|
88
|
+
subject {
|
89
|
+
Reorm::Cursor.new(CursorTestModel, r.table(CursorTestModel.table_name)).order_by(:index)
|
90
|
+
}
|
91
|
+
|
92
|
+
it "returns the next record from the cursor" do
|
93
|
+
(1..5).each do |index|
|
94
|
+
model = subject.next
|
95
|
+
expect(model).not_to be_nil
|
96
|
+
expect((1..5)).to include(model.index)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
it "raises an exception if called on an exhausted cursor" do
|
101
|
+
5.times {subject.next}
|
102
|
+
expect {
|
103
|
+
subject.next
|
104
|
+
}.to raise_exception(Reorm::Error, "There are no more matching records.")
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe "#each()" do
|
110
|
+
describe "when an order by clause hasn't been applied" do
|
111
|
+
it "yields each available record to the specified block" do
|
112
|
+
indices = [1, 2, 3, 4, 5]
|
113
|
+
subject.each do |record|
|
114
|
+
indices.delete(record.index)
|
115
|
+
end
|
116
|
+
expect(indices.empty?).to eq(true)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
describe "when an order by clause has been applied" do
|
121
|
+
subject {
|
122
|
+
Reorm::Cursor.new(CursorTestModel, r.table(CursorTestModel.table_name)).order_by(:index)
|
123
|
+
}
|
124
|
+
|
125
|
+
it "yields each available record to the specified block" do
|
126
|
+
indices = [1, 2, 3, 4, 5]
|
127
|
+
subject.each do |record|
|
128
|
+
indices.delete(record.index)
|
129
|
+
end
|
130
|
+
expect(indices.empty?).to eq(true)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe "#inject()" do
|
136
|
+
it "yields each available record to the specified block" do
|
137
|
+
output = subject.inject([]) {|list, record| list << record.index}
|
138
|
+
expect(output).to include(1)
|
139
|
+
expect(output).to include(2)
|
140
|
+
expect(output).to include(3)
|
141
|
+
expect(output).to include(4)
|
142
|
+
expect(output).to include(5)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
describe "#nth()" do
|
147
|
+
it "returns the record at the specified offset" do
|
148
|
+
expect(subject.nth(2).index).to eq(subject.to_a[2].index)
|
149
|
+
end
|
150
|
+
|
151
|
+
it "returns nil if an invalid offset is specified" do
|
152
|
+
expect(subject.nth(1000)).to be_nil
|
153
|
+
expect(subject.nth(-1)).to be_nil
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
describe "#first()" do
|
158
|
+
let(:first) {
|
159
|
+
subject.nth(0)
|
160
|
+
}
|
161
|
+
|
162
|
+
it "returns the first matching record from the cursor" do
|
163
|
+
expect(subject.first.index).to eq(first.index)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
describe "#last()" do
|
168
|
+
let(:last) {
|
169
|
+
subject.nth(4)
|
170
|
+
}
|
171
|
+
|
172
|
+
it "returns the last matching record from the cursor" do
|
173
|
+
expect(subject.last.index).to eq(last.index)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
describe "#to_a()" do
|
178
|
+
it "returns an array containing all of the matching records from the cursor" do
|
179
|
+
array = subject.to_a
|
180
|
+
expect(array).not_to be_nil
|
181
|
+
expect(array.class).to eq(Array)
|
182
|
+
expect(array.size).to eq(5)
|
183
|
+
expect(array.inject([]) {|list, model| list << model.index}.sort).to eq([1, 2, 3, 4, 5])
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
describe "#filter()" do
|
188
|
+
it "returns a Cursor object" do
|
189
|
+
output = subject.filter({index: 4})
|
190
|
+
expect(output).not_to be_nil
|
191
|
+
expect(output.class).to eq(Reorm::Cursor)
|
192
|
+
end
|
193
|
+
|
194
|
+
it "applies a filter to the cursor records" do
|
195
|
+
cursor = subject.filter({index: 4})
|
196
|
+
expect(cursor.count).to eq(1)
|
197
|
+
expect(cursor.first.index).to eq(4)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
describe "#order_by()" do
|
202
|
+
it "returns a Cursor object" do
|
203
|
+
output = subject.order_by(:index)
|
204
|
+
expect(output).not_to be_nil
|
205
|
+
expect(output.class).to eq(Reorm::Cursor)
|
206
|
+
end
|
207
|
+
|
208
|
+
it "applies an ordering to the cursor records" do
|
209
|
+
cursor = subject.order_by(r.desc(:index))
|
210
|
+
indices = cursor.inject([]) {|list, record| list << record.index}
|
211
|
+
expect(indices).to eq([5, 4, 3, 2, 1])
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|