reorm 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.
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