reorm 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +219 -0
  7. data/Rakefile +2 -0
  8. data/config/database.yml +11 -0
  9. data/lib/reorm/configuration.rb +12 -0
  10. data/lib/reorm/cursor.rb +162 -0
  11. data/lib/reorm/exceptions.rb +13 -0
  12. data/lib/reorm/field_path.rb +53 -0
  13. data/lib/reorm/model.rb +132 -0
  14. data/lib/reorm/modules/database_modules.rb +67 -0
  15. data/lib/reorm/modules/event_modules.rb +82 -0
  16. data/lib/reorm/modules/validation_modules.rb +29 -0
  17. data/lib/reorm/modules.rb +7 -0
  18. data/lib/reorm/property_errors.rb +53 -0
  19. data/lib/reorm/validators/exclusion_validator.rb +18 -0
  20. data/lib/reorm/validators/inclusion_validator.rb +18 -0
  21. data/lib/reorm/validators/maximum_length_validator.rb +19 -0
  22. data/lib/reorm/validators/minimum_length_validator.rb +19 -0
  23. data/lib/reorm/validators/presence_validator.rb +17 -0
  24. data/lib/reorm/validators/validator.rb +13 -0
  25. data/lib/reorm/validators.rb +10 -0
  26. data/lib/reorm/version.rb +6 -0
  27. data/lib/reorm.rb +47 -0
  28. data/reorm.gemspec +30 -0
  29. data/spec/catwalk/modules/timestamped_spec.rb +17 -0
  30. data/spec/reorm/cursor_spec.rb +214 -0
  31. data/spec/reorm/field_path_spec.rb +65 -0
  32. data/spec/reorm/model_spec.rb +268 -0
  33. data/spec/reorm/modules/event_source_spec.rb +49 -0
  34. data/spec/reorm/modules/table_backed_spec.rb +46 -0
  35. data/spec/reorm/modules/timestamped_spec.rb +28 -0
  36. data/spec/reorm/modules/validation_modules_spec.rb +157 -0
  37. data/spec/reorm/property_errors_spec.rb +120 -0
  38. data/spec/reorm/validators/exclusion_validator_spec.rb +34 -0
  39. data/spec/reorm/validators/inclusion_validator_spec.rb +36 -0
  40. data/spec/reorm/validators/maximum_length_validator_spec.rb +37 -0
  41. data/spec/reorm/validators/minimum_length_validator_spec.rb +39 -0
  42. data/spec/reorm/validators/presence_validator_spec.rb +44 -0
  43. data/spec/reorm/validators/validator_spec.rb +23 -0
  44. data/spec/spec_helper.rb +118 -0
  45. metadata +216 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c4d147f8ec8ab156ae124379903853b37a6aec29
4
+ data.tar.gz: c53da10347253a776ecfea2cee5d56d9f49e1622
5
+ SHA512:
6
+ metadata.gz: 356d4b82f8285da450df48392baa417df7760f81c7ebf866eb74257ab0e2352bcfd0c7b2f0e5682f925217e528b2a2e29c3b97c89f1a2376b2247e571cd7ddbd
7
+ data.tar.gz: 0e412ac34981e3f3551538e38263e6b88a5be80333d06e5dfa5f5f6d323d0cc9980fb5b22137a6abce7cd8e15f35e9869c9998ac593451b487c867a0e4fdcf4b
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in reorm.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Peter Wood
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,219 @@
1
+ # Reorm
2
+
3
+ A (possibly naive) ORM for use with the RethinkDB driver for Ruby. The library
4
+ is heavily influenced by the implementations of the active record pattern from
5
+ the Sequel and ActiveRecord libraries. I'm not 100% sure that this is a good
6
+ match for a document oriented store such as RethinkDB but here it is anyway.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'reorm'
14
+ ```
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install reorm
23
+
24
+ ## Usage
25
+
26
+ First things first, the library needs to be configured to connect to your
27
+ RethinkDB instance. The simplest way to do this is create a file called
28
+ database.yml in the current working directory. Place content like the following
29
+ into this file...
30
+
31
+ defaults: &defaults
32
+ host: localhost
33
+ port: 28015
34
+
35
+ development:
36
+ <<: *defaults
37
+ db: reorm_dev
38
+
39
+ test:
40
+ <<: *defaults
41
+ db: reorm_test
42
+
43
+ This will be picked up by the library and used to create a connection based on
44
+ it current environment (this defaults to development but if you set either the
45
+ RAILS_ENV or RACK_ENV environment variables that will be used instead). The
46
+ example above connects to the same RethinkDB instance, on localhost at port
47
+ 28015, and then defaults the database it will use based on the enviornment
48
+ setting. Note the configuration options are more complicated and flexible than
49
+ using this approach but more on that later.
50
+
51
+ Next, declare a model class like so...
52
+
53
+ class MyModel < Reorm::Model
54
+ end
55
+
56
+ This small amount of code declares a class that will expect to save its data
57
+ elements into a table called my_models (it will create this table if it does not
58
+ already exist) and with an assumption that it uses a primary key called id. You
59
+ can change the table name used like so...
60
+
61
+ class MyModel < Reorm::Model
62
+ table_name "other_records"
63
+ end
64
+
65
+ You could now create an instance of your model and save it to the database like
66
+ so...
67
+
68
+ model = MyModel.create(one: 1, two: 2, three: {four: 4})
69
+
70
+ Once a model has been created like this it will have all of the top level fields
71
+ specified as parameters available as properties from the model object generated.
72
+ So, for example, you could access some of the value from the object created
73
+ above in code as follows...
74
+
75
+ model.one # = 1
76
+ model.two # = 2
77
+ model.three # = {four: 4}
78
+
79
+ When an object is created it will automatically have its primary key filled out
80
+ by RethinkDB. By default models use a primary key called id but you can change
81
+ this by adding code like the following to you class declaration...
82
+
83
+ primary_key :my_key
84
+
85
+ This will change the primary key used by the class to the field specified to
86
+ the call to ```primary_key```. When a primary key value has been generated by
87
+ RethinkDB its also available as a property from the object. Given the create
88
+ example above the models primary key can be accessed like any other property of
89
+ the object...
90
+
91
+ model.id # RethinkDB generated primary key value.
92
+
93
+ To retrieve models back from the database you call either call the ```#all()```
94
+ method or use the ```#filter()``` method that is available on all model classes.
95
+ For example, if you had a model called User, you could use this code to iterate
96
+ across all user records...
97
+
98
+ User.all.each do |user|
99
+ # Do some stuff here.
100
+ ...
101
+ end
102
+
103
+ Or you could search for a user with a particular email address using code like
104
+ the following...
105
+
106
+ user = User.filter({email: "user@email.com"}).first
107
+
108
+ If I wanted all users whose email address had a particular domain then I would
109
+ use code like the following...
110
+
111
+ users = User.filter {|record| record["email"].match("@gmail.com$")}
112
+ users.each do |user|
113
+ # Do some stuff here.
114
+ ...
115
+ end
116
+
117
+ Note that in the predicate passed to the filter method the value passed to the
118
+ block is the raw record and not an instance of the model class. This means that
119
+ you have to dereference field values as you would from a Hash. When using the
120
+ output of the filter however you will receive model class instances and can
121
+ use those as normal.
122
+
123
+ The filter code is based on the RethinkDB filter functionality so consult the
124
+ documentation for more information.
125
+
126
+ ### Validations
127
+
128
+ Model classes can provide functionality that allows them to be validated to
129
+ ensure that their data settings are consistent. To do this implement a method on
130
+ your class called ```#validate()```. The first thing to do in this method is to
131
+ make a call to the parent class implementation of this method via a call to
132
+ ```super``` - this is important so don't forget to do it! After that you can
133
+ perform tests on the objects settings and add errors to the model where you
134
+ discover discrepancies. For example...
135
+
136
+ def validate
137
+ super
138
+ if [nil, ""].include?(email)
139
+ errors.add(:email, "cannot be blank.")
140
+ end
141
+ end
142
+
143
+ The library provides a number of helper method to shortcut some common
144
+ validations such as the following...
145
+
146
+ def validate
147
+ super
148
+ validate_presence_of :email
149
+ end
150
+
151
+ Would do the same thing as the first version of the ```validate()``` method
152
+ shown above. Some other examples include...
153
+
154
+ def validate
155
+ super
156
+ validate_length_of :field1, minimum: 5, maximum: 20
157
+ validate_inclusion_of :field2, "One", "Of", "These", "Values"
158
+ validate_exclusion_of :field3 "Not", "One", "Of", "These"
159
+ end
160
+
161
+ If you call te ```valid?()``` method on a model then the model object will be
162
+ validated and this method will return true if no errors were set and false if at
163
+ least one error was set. You can view the errors for a model by accessing its
164
+ ```errors``` property which returns an instance of the
165
+ ```Reorm::Reorm::PropertyErrors``` class.
166
+
167
+ Note that a object is automatically validated any time it is saved and that the
168
+ save request will fail if the object fails validation and an exception will be
169
+ raised. You can turn validation off for a save by passing false as a parameter
170
+ to the save call.
171
+
172
+ ### Event Callbacks
173
+
174
+ The library supports a number of event related callbacks that will be invoked
175
+ when a specific event occurs. You can add an event related callback to a model
176
+ by declaring it within the model class like so...
177
+
178
+ before_save :method_name
179
+
180
+ In this case the library will attempt to invoked a method called ```method_name```
181
+ on the model object after validation but before the object is actually written
182
+ to RethinkDB. Note the method has to actually exist on the model for this to
183
+ work. The following callbacks, in the order in which they occur, are available -
184
+ before_validate, after_validate, before_create, before_update, before_save,
185
+ after_save, after_update and after_create.
186
+
187
+ ### Unit Tests
188
+
189
+ To run the unit tests you'll need a locally running instance of RethinkDB (none
190
+ of that nonsense mocking out DB writes rubbish here!) and then you can run the
191
+ available unit tests using the ```rspec``` command.
192
+
193
+ ### Configuration
194
+
195
+ The library, on load up, looks for a file containing the RethinkDB configuration
196
+ details. It will settle upon the first file that it finds called database.yml,
197
+ rethinkdb.yml or application.yml (note a .json extension is also acceptable).
198
+ This file will be expected to contain a Hash in the appropriate format. Within
199
+ this Hash it will first look for a an environment base entry (i.e. a key of
200
+ 'development', 'production' or 'test'). If it finds no such key it will assume
201
+ that the entire Hash is the configuration, otherwise it will extract the entry
202
+ under the given key and use that as configuration.
203
+
204
+ Next it takes the output from the previous section and looks for an entry keyed
205
+ under 'rethinkdb'. Again, if it does not find it, it will assume the entire
206
+ entry is its configuration, otherwise it will focus down on the keyed entry
207
+ once more. The reasoning here is to allow you to have a separate standalone
208
+ database configuration file or to allow your database configuration values to
209
+ be part of a larger configuration file. The output from this process is expected
210
+ to be the parmaeters that will allow the library to connect to a RethinkDB
211
+ server.
212
+
213
+ ## Contributing
214
+
215
+ 1. Fork it ( https://github.com/[my-github-username]/reorm/fork )
216
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
217
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
218
+ 4. Push to the branch (`git push origin my-new-feature`)
219
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,11 @@
1
+ defaults: &defaults
2
+ host: localhost
3
+ port: 28015
4
+
5
+ development:
6
+ <<: *defaults
7
+ db: reorm_dev
8
+
9
+ test:
10
+ <<: *defaults
11
+ db: reorm_test
@@ -0,0 +1,12 @@
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 Configuration < Configurative::Settings
7
+ files *(Dir.glob(File.join(Dir.getwd, "**", "database.{yml,yaml,json}")) +
8
+ Dir.glob(File.join(Dir.getwd, "**", "rethinkdb.{yml,yaml,json}")) +
9
+ Dir.glob(File.join(Dir.getwd, "**", "application.{yml,yaml,json}")))
10
+ #section "rethinkdb"
11
+ end
12
+ end
@@ -0,0 +1,162 @@
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 Cursor
7
+ def initialize(model_class, query, order_by=nil)
8
+ @model_class = model_class
9
+ @query = query
10
+ @cursor = nil
11
+ @offset = 0
12
+ @total = 0
13
+ @order_by = order_by
14
+ end
15
+ attr_reader :model_class
16
+
17
+ def close
18
+ @cursor.close if @cursor && !@cursor.kind_of?(Array)
19
+ @cursor = nil
20
+ @offset = @total = 0
21
+ self
22
+ end
23
+ alias :reset :close
24
+
25
+ def filter(predicate)
26
+ Cursor.new(model_class, @query.filter(predicate), @order_by)
27
+ end
28
+
29
+ def count
30
+ Reorm.connection do |connection|
31
+ @query.count.run(connection)
32
+ end
33
+ end
34
+
35
+ def exhausted?
36
+ open? && @offset == @total
37
+ end
38
+
39
+ def find
40
+ model = nil
41
+ each do |record|
42
+ found = yield(record)
43
+ if found
44
+ model = record
45
+ break
46
+ end
47
+ end
48
+ model
49
+ end
50
+ alias :detect :find
51
+
52
+ def next
53
+ open if !open?
54
+ if exhausted?
55
+ raise Error, "There are no more matching records."
56
+ end
57
+ data = @order_by.nil? ? @cursor.next : @cursor[@offset]
58
+ @offset += 1
59
+ model_class.new(data)
60
+ end
61
+
62
+ def each(&block)
63
+ @order_by.nil? ? each_without_order_by(&block) : each_with_order_by(&block)
64
+ end
65
+
66
+ def inject(token=nil)
67
+ each do |record|
68
+ yield token, record
69
+ end
70
+ token
71
+ end
72
+
73
+ def nth(offset)
74
+ model = nil
75
+ if offset >= 0 && offset < count
76
+ Reorm.connection do |connection|
77
+ model = model_class.new(@query.nth(offset).run(connection))
78
+ end
79
+ end
80
+ model
81
+ end
82
+
83
+ def first
84
+ nth(0)
85
+ end
86
+
87
+ def last
88
+ nth(count - 1)
89
+ end
90
+
91
+ def to_a
92
+ inject([]) {|list, record| list << record}
93
+ end
94
+
95
+ def limit(size)
96
+ Cursor.new(model_class, @query.limit(size), @order_by)
97
+ end
98
+
99
+ def offset(index)
100
+ Cursor.new(model_class, @query.skip(quantity), @order_by)
101
+ end
102
+ alias :skip :offset
103
+
104
+ def slice(start_at, end_at=nil, left_bound='closed', right_bound="open")
105
+ if end_at
106
+ Cursor.new(model_class, @query.slice(start_at, end_at, left_bound, right_bound), @order_by)
107
+ else
108
+ Cursor.new(model_class, @query.slice(start_at), @order_by)
109
+ end
110
+ end
111
+
112
+ def order_by(*arguments)
113
+ Cursor.new(model_class, @query, arguments)
114
+ end
115
+
116
+ private
117
+
118
+ def open
119
+ Reorm.connection do |connection|
120
+ array_based = false
121
+ if @order_by && @order_by.size > 0
122
+ clause = @order_by.find {|entry| entry.kind_of?(Hash)}
123
+ array_based = clause.nil? || clause.keys != [:index]
124
+ end
125
+
126
+ @offset = 0
127
+ if !array_based
128
+ @total = @query.count.run(connection)
129
+ @cursor = @query.run(connection)
130
+ else
131
+ @cursor = @query.order_by(*@order_by).run(connection)
132
+ @total = @cursor.size
133
+ end
134
+ end
135
+ end
136
+
137
+ def open?
138
+ !@cursor.nil?
139
+ end
140
+
141
+ def each_with_order_by
142
+ Reorm.connection do |connection|
143
+ @query.order_by(*@order_by).run(connection).each do |record|
144
+ yield model_class.new(record)
145
+ end
146
+ end
147
+ end
148
+
149
+ def each_without_order_by
150
+ Reorm.connection do |connection|
151
+ cursor = @query.run(connection)
152
+ begin
153
+ cursor.each do |record|
154
+ yield model_class.new(record)
155
+ end
156
+ ensure
157
+ cursor.close
158
+ end
159
+ end
160
+ end
161
+ end
162
+ 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 Error < StandardError
7
+ def initialize(message, cause=nil)
8
+ super(message)
9
+ @cause = cause
10
+ end
11
+ attr_reader :cause
12
+ end
13
+ 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 FieldPath
7
+ def initialize(*path)
8
+ @path = [].concat(path)
9
+ end
10
+
11
+ def name
12
+ @path.last
13
+ end
14
+
15
+ def value(document)
16
+ locate(document).first
17
+ end
18
+
19
+ def value!(document)
20
+ result = locate(document)
21
+ raise Error, "Unable to locate the #{name} (full path: #{self}) field for an instance of the #{document.class.name} class." if !result[1]
22
+ result[0]
23
+ end
24
+
25
+ def exists?(document)
26
+ locate(document)[1]
27
+ end
28
+
29
+ def to_s
30
+ @path.join(" -> ")
31
+ end
32
+
33
+ private
34
+
35
+ def locate(document)
36
+ result = [nil, false]
37
+ value = document
38
+ @path.each_with_index do |field, index|
39
+ if !value || !value.respond_to?(:include?) || !value.include?(field)
40
+ value = nil
41
+ break
42
+ else
43
+ if index == @path.length - 1
44
+ result = [value[field], true]
45
+ else
46
+ value = value[field]
47
+ end
48
+ end
49
+ end
50
+ result
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,132 @@
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 Model
7
+ extend EventHandler
8
+ extend EventSource
9
+ extend TableBacked
10
+ include EventSource
11
+ include TableBacked
12
+ include Validations
13
+
14
+ @@class_tables = {}
15
+
16
+ def initialize(properties={})
17
+ @properties = {}
18
+ properties.each do |key, value|
19
+ @properties[key.to_sym] = value
20
+ end
21
+ @errors = PropertyErrors.new
22
+ end
23
+ attr_reader :errors
24
+
25
+ def valid?
26
+ validate
27
+ @errors.clear?
28
+ end
29
+
30
+ def validate
31
+ fire_events(events: [:before_validate])
32
+ @errors.reset
33
+ fire_events(events: [:after_validate])
34
+ self
35
+ end
36
+
37
+ def save(validated=true)
38
+ if validated && !valid?
39
+ raise Error, "Validation error encountered saving an instance of the #{self.class.name} class."
40
+ end
41
+
42
+ action_type = (@properties[primary_key] ? :update : :create)
43
+ if action_type == :create
44
+ fire_events(events: [:before_create, :before_save])
45
+ else
46
+ fire_events(events: [:before_update, :before_save])
47
+ end
48
+
49
+ Reorm.connection do |connection|
50
+ ensure_table_exists(connection)
51
+ if !@properties.include?(primary_key)
52
+ result = r.table(table_name).insert(self.to_h, return_changes: true).run(connection)
53
+ if !result["inserted"] || result["inserted"] != 1
54
+ raise Error, "Creation of database record for an instance of the #{self.class.name} class failed."
55
+ end
56
+ @properties[primary_key] = result["generated_keys"].first
57
+ else
58
+ result = r.table(table_name).update(self.to_h).run(connection)
59
+ if !result["replaced"] || !result["replaced"] == 1
60
+ raise Error, "Update of database record for an instance of the #{self.class.name} class failed."
61
+ end
62
+ end
63
+ end
64
+
65
+ if action_type == :create
66
+ fire_events(events: [:after_save, :after_create])
67
+ else
68
+ fire_events(events: [:after_save, :after_update])
69
+ end
70
+ end
71
+
72
+ def [](property_name)
73
+ @properties[property_name.to_sym]
74
+ end
75
+
76
+ def []=(property_name, value)
77
+ @properties[property_name.to_sym] = value
78
+ value
79
+ end
80
+
81
+ def respond_to?(method_name, include_private=false)
82
+ @properties.include?(property_name(method_name)) || super
83
+ end
84
+
85
+ def method_missing(method_name, *arguments, &block)
86
+ if method_name.to_s[-1,1] != "="
87
+ if @properties.include?(property_name(method_name))
88
+ @properties[method_name]
89
+ else
90
+ super
91
+ end
92
+ else
93
+ @properties[property_name(method_name)] = arguments.first
94
+ end
95
+ end
96
+
97
+ def to_h
98
+ {}.merge(@properties)
99
+ end
100
+
101
+ def self.create(properties={})
102
+ object = self.new(properties)
103
+ object.save
104
+ object
105
+ end
106
+
107
+ def self.all
108
+ Cursor.new(self, r.table(table_name))
109
+ end
110
+
111
+ def self.filter(predicate=nil, &block)
112
+ if predicate.nil?
113
+ Cursor.new(self, r.table(table_name).filter(&block))
114
+ else
115
+ Cursor.new(self, r.table(table_name).filter(predicate))
116
+ end
117
+ end
118
+
119
+ private
120
+
121
+ def property_name(name)
122
+ name.to_s[-1,1] == "=" ? name.to_s[0...-1].to_sym : name
123
+ end
124
+
125
+ def ensure_table_exists(connection)
126
+ tables = r.table_list.run(connection)
127
+ if !tables.include?(table_name)
128
+ r.table_create(table_name, primary_key: primary_key).run(connection)
129
+ end
130
+ end
131
+ end
132
+ end