nobrainer 0.8.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 +7 -0
- data/LICENSE.md +7 -0
- data/README.md +6 -0
- data/lib/no_brainer/autoload.rb +14 -0
- data/lib/no_brainer/config.rb +61 -0
- data/lib/no_brainer/connection.rb +53 -0
- data/lib/no_brainer/criteria.rb +20 -0
- data/lib/no_brainer/criteria/chainable/core.rb +67 -0
- data/lib/no_brainer/criteria/chainable/limit.rb +31 -0
- data/lib/no_brainer/criteria/chainable/order_by.rb +76 -0
- data/lib/no_brainer/criteria/chainable/raw.rb +25 -0
- data/lib/no_brainer/criteria/chainable/scope.rb +41 -0
- data/lib/no_brainer/criteria/chainable/where.rb +198 -0
- data/lib/no_brainer/criteria/termination/cache.rb +71 -0
- data/lib/no_brainer/criteria/termination/count.rb +19 -0
- data/lib/no_brainer/criteria/termination/delete.rb +11 -0
- data/lib/no_brainer/criteria/termination/eager_loading.rb +64 -0
- data/lib/no_brainer/criteria/termination/enumerable.rb +24 -0
- data/lib/no_brainer/criteria/termination/first.rb +25 -0
- data/lib/no_brainer/criteria/termination/inc.rb +14 -0
- data/lib/no_brainer/criteria/termination/update.rb +13 -0
- data/lib/no_brainer/database.rb +41 -0
- data/lib/no_brainer/decorated_symbol.rb +15 -0
- data/lib/no_brainer/document.rb +18 -0
- data/lib/no_brainer/document/association.rb +41 -0
- data/lib/no_brainer/document/association/belongs_to.rb +64 -0
- data/lib/no_brainer/document/association/core.rb +64 -0
- data/lib/no_brainer/document/association/has_many.rb +68 -0
- data/lib/no_brainer/document/attributes.rb +124 -0
- data/lib/no_brainer/document/core.rb +20 -0
- data/lib/no_brainer/document/criteria.rb +62 -0
- data/lib/no_brainer/document/dirty.rb +88 -0
- data/lib/no_brainer/document/dynamic_attributes.rb +12 -0
- data/lib/no_brainer/document/id.rb +49 -0
- data/lib/no_brainer/document/index.rb +102 -0
- data/lib/no_brainer/document/injection_layer.rb +12 -0
- data/lib/no_brainer/document/persistance.rb +124 -0
- data/lib/no_brainer/document/polymorphic.rb +43 -0
- data/lib/no_brainer/document/serialization.rb +9 -0
- data/lib/no_brainer/document/store_in.rb +33 -0
- data/lib/no_brainer/document/timestamps.rb +18 -0
- data/lib/no_brainer/document/validation.rb +35 -0
- data/lib/no_brainer/error.rb +10 -0
- data/lib/no_brainer/fork.rb +14 -0
- data/lib/no_brainer/index_manager.rb +6 -0
- data/lib/no_brainer/loader.rb +5 -0
- data/lib/no_brainer/locale/en.yml +4 -0
- data/lib/no_brainer/query_runner.rb +37 -0
- data/lib/no_brainer/query_runner/connection.rb +17 -0
- data/lib/no_brainer/query_runner/database_on_demand.rb +26 -0
- data/lib/no_brainer/query_runner/driver.rb +8 -0
- data/lib/no_brainer/query_runner/logger.rb +29 -0
- data/lib/no_brainer/query_runner/run_options.rb +34 -0
- data/lib/no_brainer/query_runner/table_on_demand.rb +44 -0
- data/lib/no_brainer/query_runner/write_error.rb +28 -0
- data/lib/no_brainer/railtie.rb +36 -0
- data/lib/no_brainer/railtie/database.rake +34 -0
- data/lib/no_brainer/util.rb +23 -0
- data/lib/no_brainer/version.rb +3 -0
- data/lib/nobrainer.rb +59 -0
- metadata +152 -0
@@ -0,0 +1,12 @@
|
|
1
|
+
module NoBrainer::Document::InjectionLayer
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
module ClassMethods
|
5
|
+
def inject_in_layer(name, code=nil, file=nil, line=nil, &block)
|
6
|
+
mod = class_eval "module NoBrainer; module #{name.to_s.camelize}; self; end; end"
|
7
|
+
mod.module_eval(code, file, line) if code
|
8
|
+
mod.module_exec(&block) if block
|
9
|
+
include mod
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module NoBrainer::Document::Persistance
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
extend ActiveModel::Callbacks
|
6
|
+
define_model_callbacks :create, :update, :save, :destroy, :terminator => 'false'
|
7
|
+
end
|
8
|
+
|
9
|
+
# TODO after_initialize, after_find callback
|
10
|
+
def initialize(attrs={}, options={})
|
11
|
+
super
|
12
|
+
@new_record = !options[:from_db]
|
13
|
+
end
|
14
|
+
|
15
|
+
def new_record?
|
16
|
+
!!@new_record
|
17
|
+
end
|
18
|
+
|
19
|
+
def destroyed?
|
20
|
+
!!@destroyed
|
21
|
+
end
|
22
|
+
|
23
|
+
def persisted?
|
24
|
+
!new_record? && !destroyed?
|
25
|
+
end
|
26
|
+
|
27
|
+
def reload(options={})
|
28
|
+
unless options[:keep_ivars]
|
29
|
+
id = self.id
|
30
|
+
instance_variables.each { |ivar| remove_instance_variable(ivar) }
|
31
|
+
@attributes = {}
|
32
|
+
self.id = id
|
33
|
+
end
|
34
|
+
assign_attributes(selector.raw.first!, :pristine => true, :from_db => true)
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
def _create(options={})
|
39
|
+
run_callbacks :create do
|
40
|
+
if options[:validate] && !valid?
|
41
|
+
false
|
42
|
+
else
|
43
|
+
keys = self.class.insert_all(attributes)
|
44
|
+
self.id ||= keys.first
|
45
|
+
@new_record = false
|
46
|
+
true
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def update(options={}, &block)
|
52
|
+
run_callbacks :update do
|
53
|
+
if options[:validate] && !valid?
|
54
|
+
false
|
55
|
+
else
|
56
|
+
selector.update_all(&block)
|
57
|
+
true
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def replace(options={}, &block)
|
63
|
+
run_callbacks :update do
|
64
|
+
if options[:validate] && !valid?
|
65
|
+
false
|
66
|
+
else
|
67
|
+
selector.replace_all(&block)
|
68
|
+
true
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def save(options={})
|
74
|
+
options = options.reverse_merge(:validate => true)
|
75
|
+
run_callbacks :save do
|
76
|
+
new_record? ? _create(options) : replace(options) { attributes }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def save!(*args)
|
81
|
+
save(*args) or raise NoBrainer::Error::DocumentInvalid, self
|
82
|
+
end
|
83
|
+
|
84
|
+
def update_attributes(attrs, options={})
|
85
|
+
assign_attributes(attrs, options)
|
86
|
+
save(options)
|
87
|
+
end
|
88
|
+
|
89
|
+
def update_attributes!(*args)
|
90
|
+
update_attributes(*args) or raise NoBrainer::Error::DocumentInvalid, self
|
91
|
+
end
|
92
|
+
|
93
|
+
def delete
|
94
|
+
unless @destroyed
|
95
|
+
selector.delete_all
|
96
|
+
@destroyed = true
|
97
|
+
end
|
98
|
+
# TODO freeze attributes
|
99
|
+
true
|
100
|
+
end
|
101
|
+
|
102
|
+
def destroy
|
103
|
+
run_callbacks(:destroy) { delete }
|
104
|
+
end
|
105
|
+
|
106
|
+
module ClassMethods
|
107
|
+
def create(attrs={}, options={})
|
108
|
+
new(attrs, options).tap { |doc| doc.save(options) }
|
109
|
+
end
|
110
|
+
|
111
|
+
def create!(attrs={}, options={})
|
112
|
+
new(attrs, options).tap { |doc| doc.save!(options) }
|
113
|
+
end
|
114
|
+
|
115
|
+
def insert_all(*attrs)
|
116
|
+
result = NoBrainer.run(rql_table.insert(*attrs))
|
117
|
+
result['generated_keys'].to_a
|
118
|
+
end
|
119
|
+
|
120
|
+
def sync
|
121
|
+
NoBrainer.run(rql_table.sync)['synced'] == 1
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module NoBrainer::Document::Polymorphic
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
include ActiveSupport::DescendantsTracker
|
4
|
+
|
5
|
+
included do
|
6
|
+
class_attribute :root_class
|
7
|
+
self.root_class = self
|
8
|
+
end
|
9
|
+
|
10
|
+
def assign_attributes(*args)
|
11
|
+
super
|
12
|
+
self._type ||= self.class.type_value unless self.class.is_root_class?
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
def inherited(subclass)
|
17
|
+
super
|
18
|
+
subclass.field :_type if is_root_class?
|
19
|
+
end
|
20
|
+
|
21
|
+
def type_value
|
22
|
+
name
|
23
|
+
end
|
24
|
+
|
25
|
+
def descendants_type_values
|
26
|
+
([self] + descendants).map(&:type_value)
|
27
|
+
end
|
28
|
+
|
29
|
+
def is_root_class?
|
30
|
+
self == root_class
|
31
|
+
end
|
32
|
+
|
33
|
+
def klass_from_attrs(attrs)
|
34
|
+
attrs['_type'].try(:constantize) || root_class
|
35
|
+
end
|
36
|
+
|
37
|
+
def all
|
38
|
+
criterion = super
|
39
|
+
criterion = criterion.where(:_type.in => descendants_type_values) unless is_root_class?
|
40
|
+
criterion
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module NoBrainer::Document::Serialization
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
include ActiveModel::Serialization
|
5
|
+
include ActiveModel::Serializers::JSON
|
6
|
+
include ActiveModel::Serializers::Xml
|
7
|
+
|
8
|
+
included { self.include_root_in_json = NoBrainer::Config.include_root_in_json }
|
9
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module NoBrainer::Document::StoreIn
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
class_attribute :store_in_options
|
6
|
+
self.store_in_options = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def store_in(options)
|
11
|
+
raise "store_in() must be called on the parent class" unless root_class?
|
12
|
+
self.store_in_options.merge!(options)
|
13
|
+
end
|
14
|
+
|
15
|
+
def database_name
|
16
|
+
db = self.store_in_options[:database]
|
17
|
+
db.is_a?(Proc) ? db.call : db
|
18
|
+
end
|
19
|
+
|
20
|
+
def table_name
|
21
|
+
table = store_in_options[:table]
|
22
|
+
table_name = table.is_a?(Proc) ? table.call : table
|
23
|
+
table_name || root_class.name.underscore.gsub('/', '__').pluralize
|
24
|
+
end
|
25
|
+
|
26
|
+
def rql_table
|
27
|
+
db = self.database_name
|
28
|
+
rql = RethinkDB::RQL.new
|
29
|
+
rql = rql.db(db) if db
|
30
|
+
rql.table(table_name)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module NoBrainer::Document::Timestamps
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
self.field :created_at
|
6
|
+
self.field :updated_at
|
7
|
+
|
8
|
+
before_create { self.created_at = Time.now if self.respond_to?(:created_at=) }
|
9
|
+
before_save { self.updated_at = Time.now if self.respond_to?(:updated_at=) }
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def disable_timestamps
|
14
|
+
self.remove_field :created_at
|
15
|
+
self.remove_field :updated_at
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module NoBrainer::Document::Validation
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
include ActiveModel::Validations
|
4
|
+
include ActiveModel::Validations::Callbacks
|
5
|
+
|
6
|
+
# TODO Test that thing
|
7
|
+
def valid?(context=nil)
|
8
|
+
super(context || (new_record? ? :create : :update))
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def validates_uniqueness_of(*attr_names)
|
13
|
+
validates_with UniquenessValidator, _merge_attributes(attr_names)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class UniquenessValidator < ActiveModel::EachValidator
|
18
|
+
def validate_each(doc, attr, value)
|
19
|
+
criteria = doc.root_class.unscoped.where(attr => value)
|
20
|
+
criteria = apply_scopes(criteria, doc)
|
21
|
+
criteria = exclude_doc(criteria, doc) if doc.persisted?
|
22
|
+
is_unique = criteria.count == 0
|
23
|
+
doc.errors.add(attr, :taken, options.except(:scope).merge(:value => value)) unless is_unique
|
24
|
+
is_unique
|
25
|
+
end
|
26
|
+
|
27
|
+
def apply_scopes(criteria, doc)
|
28
|
+
criteria.where([*options[:scope]].map { |k| {k => doc.read_attribute(k)} })
|
29
|
+
end
|
30
|
+
|
31
|
+
def exclude_doc(criteria, doc)
|
32
|
+
criteria.where(:id.ne => doc.id)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module NoBrainer::Error
|
2
|
+
class Connection < StandardError; end
|
3
|
+
class DocumentNotFound < StandardError; end
|
4
|
+
class DocumentInvalid < StandardError; end
|
5
|
+
class DocumentNotSaved < StandardError; end
|
6
|
+
class ChildrenExist < StandardError; end
|
7
|
+
class CannotUseIndex < StandardError; end
|
8
|
+
class InvalidType < StandardError; end
|
9
|
+
class AssociationNotSaved < StandardError; end
|
10
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'middleware'
|
2
|
+
require 'rethinkdb'
|
3
|
+
|
4
|
+
module NoBrainer::QueryRunner
|
5
|
+
extend NoBrainer::Autoload
|
6
|
+
|
7
|
+
class Middleware
|
8
|
+
def initialize(runner)
|
9
|
+
@runner = runner
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
autoload :Driver, :DatabaseOnDemand, :TableOnDemand, :WriteError,
|
14
|
+
:Connection, :Selection, :RunOptions, :Logger
|
15
|
+
|
16
|
+
class << self
|
17
|
+
attr_accessor :stack
|
18
|
+
|
19
|
+
def run(*args, &block)
|
20
|
+
options = args.extract_options!
|
21
|
+
raise ArgumentError unless args.size == 1 || block
|
22
|
+
query = args.first || block.call(RethinkDB::RQL.new)
|
23
|
+
stack.call(:query => query, :options => options)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# thread-safe, since require() is ran with a mutex.
|
28
|
+
self.stack = ::Middleware::Builder.new do
|
29
|
+
use RunOptions
|
30
|
+
use Connection
|
31
|
+
use WriteError
|
32
|
+
use DatabaseOnDemand
|
33
|
+
use TableOnDemand
|
34
|
+
use Logger
|
35
|
+
use Driver
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class NoBrainer::QueryRunner::Connection < NoBrainer::QueryRunner::Middleware
|
2
|
+
def call(env)
|
3
|
+
@runner.call(env)
|
4
|
+
rescue RuntimeError, NoBrainer::Error::DocumentNotSaved => e
|
5
|
+
if e.message =~ /cannot perform (read|write): lost contact with master/
|
6
|
+
env[:connection_retries] ||= 0
|
7
|
+
# TODO sleep in between? timing out should be time based?
|
8
|
+
|
9
|
+
# XXX Possibly dangerous, as we could reexecute a non idempotent operation
|
10
|
+
# Check the semantics of the db
|
11
|
+
|
12
|
+
# TODO Unit test
|
13
|
+
retry if (env[:connection_retries] += 1) < NoBrainer::Config.max_reconnection_tries
|
14
|
+
end
|
15
|
+
raise e
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class NoBrainer::QueryRunner::DatabaseOnDemand < NoBrainer::QueryRunner::Middleware
|
2
|
+
def call(env)
|
3
|
+
@runner.call(env)
|
4
|
+
rescue RuntimeError => e
|
5
|
+
if NoBrainer::Config.auto_create_databases &&
|
6
|
+
e.message =~ /^Database `(.+)` does not exist\.$/
|
7
|
+
auto_create_database(env, $1)
|
8
|
+
retry
|
9
|
+
end
|
10
|
+
raise
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def auto_create_database(env, database_name)
|
16
|
+
if env[:auto_create_database] == database_name
|
17
|
+
raise "Auto database creation is not working with #{database_name}"
|
18
|
+
end
|
19
|
+
env[:auto_create_database] = database_name
|
20
|
+
|
21
|
+
NoBrainer.db_create(database_name)
|
22
|
+
rescue RuntimeError => e
|
23
|
+
# We might have raced with another db_create
|
24
|
+
raise unless e.message =~ /Database `#{database_name}` already exists/
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
class NoBrainer::QueryRunner::Driver < NoBrainer::QueryRunner::Middleware
|
2
|
+
def call(env)
|
3
|
+
env[:query].run(NoBrainer.connection, env[:options])
|
4
|
+
rescue NoMethodError => e
|
5
|
+
raise "NoBrainer is not connected to a RethinkDB instance" unless NoBrainer.connection
|
6
|
+
raise e
|
7
|
+
end
|
8
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class NoBrainer::QueryRunner::Logger < NoBrainer::QueryRunner::Middleware
|
2
|
+
def call(env)
|
3
|
+
start_time = Time.now
|
4
|
+
res = @runner.call(env)
|
5
|
+
if NoBrainer.logger and NoBrainer.logger.debug?
|
6
|
+
duration = Time.now - start_time
|
7
|
+
msg = env[:query].inspect.gsub(/\n/, '').gsub(/ +/, ' ')
|
8
|
+
|
9
|
+
# Need to fix the rethinkdb gem.
|
10
|
+
if msg =~ /Erroneous_Portion_Constructed/
|
11
|
+
msg = "r.the_rethinkdb_gem_is_flipping_out_with_Erroneous_Portion_Constructed"
|
12
|
+
end
|
13
|
+
|
14
|
+
msg = "(#{env[:db_name]}) #{msg}" if env[:db_name]
|
15
|
+
msg = "[#{(duration * 1000.0).round(1)}ms] #{msg}"
|
16
|
+
|
17
|
+
if NoBrainer::Config.colorize_logger
|
18
|
+
case NoBrainer::Util.rql_type(env[:query])
|
19
|
+
when :write then msg = "\e[1;31m#{msg}\e[0m" # red
|
20
|
+
when :read then msg = "\e[1;32m#{msg}\e[0m" # green
|
21
|
+
when :management then msg = "\e[1;33m#{msg}\e[0m" # yellow
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
NoBrainer.logger.debug(msg)
|
26
|
+
end
|
27
|
+
res
|
28
|
+
end
|
29
|
+
end
|