nobrainer 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.md +7 -0
  3. data/README.md +6 -0
  4. data/lib/no_brainer/autoload.rb +14 -0
  5. data/lib/no_brainer/config.rb +61 -0
  6. data/lib/no_brainer/connection.rb +53 -0
  7. data/lib/no_brainer/criteria.rb +20 -0
  8. data/lib/no_brainer/criteria/chainable/core.rb +67 -0
  9. data/lib/no_brainer/criteria/chainable/limit.rb +31 -0
  10. data/lib/no_brainer/criteria/chainable/order_by.rb +76 -0
  11. data/lib/no_brainer/criteria/chainable/raw.rb +25 -0
  12. data/lib/no_brainer/criteria/chainable/scope.rb +41 -0
  13. data/lib/no_brainer/criteria/chainable/where.rb +198 -0
  14. data/lib/no_brainer/criteria/termination/cache.rb +71 -0
  15. data/lib/no_brainer/criteria/termination/count.rb +19 -0
  16. data/lib/no_brainer/criteria/termination/delete.rb +11 -0
  17. data/lib/no_brainer/criteria/termination/eager_loading.rb +64 -0
  18. data/lib/no_brainer/criteria/termination/enumerable.rb +24 -0
  19. data/lib/no_brainer/criteria/termination/first.rb +25 -0
  20. data/lib/no_brainer/criteria/termination/inc.rb +14 -0
  21. data/lib/no_brainer/criteria/termination/update.rb +13 -0
  22. data/lib/no_brainer/database.rb +41 -0
  23. data/lib/no_brainer/decorated_symbol.rb +15 -0
  24. data/lib/no_brainer/document.rb +18 -0
  25. data/lib/no_brainer/document/association.rb +41 -0
  26. data/lib/no_brainer/document/association/belongs_to.rb +64 -0
  27. data/lib/no_brainer/document/association/core.rb +64 -0
  28. data/lib/no_brainer/document/association/has_many.rb +68 -0
  29. data/lib/no_brainer/document/attributes.rb +124 -0
  30. data/lib/no_brainer/document/core.rb +20 -0
  31. data/lib/no_brainer/document/criteria.rb +62 -0
  32. data/lib/no_brainer/document/dirty.rb +88 -0
  33. data/lib/no_brainer/document/dynamic_attributes.rb +12 -0
  34. data/lib/no_brainer/document/id.rb +49 -0
  35. data/lib/no_brainer/document/index.rb +102 -0
  36. data/lib/no_brainer/document/injection_layer.rb +12 -0
  37. data/lib/no_brainer/document/persistance.rb +124 -0
  38. data/lib/no_brainer/document/polymorphic.rb +43 -0
  39. data/lib/no_brainer/document/serialization.rb +9 -0
  40. data/lib/no_brainer/document/store_in.rb +33 -0
  41. data/lib/no_brainer/document/timestamps.rb +18 -0
  42. data/lib/no_brainer/document/validation.rb +35 -0
  43. data/lib/no_brainer/error.rb +10 -0
  44. data/lib/no_brainer/fork.rb +14 -0
  45. data/lib/no_brainer/index_manager.rb +6 -0
  46. data/lib/no_brainer/loader.rb +5 -0
  47. data/lib/no_brainer/locale/en.yml +4 -0
  48. data/lib/no_brainer/query_runner.rb +37 -0
  49. data/lib/no_brainer/query_runner/connection.rb +17 -0
  50. data/lib/no_brainer/query_runner/database_on_demand.rb +26 -0
  51. data/lib/no_brainer/query_runner/driver.rb +8 -0
  52. data/lib/no_brainer/query_runner/logger.rb +29 -0
  53. data/lib/no_brainer/query_runner/run_options.rb +34 -0
  54. data/lib/no_brainer/query_runner/table_on_demand.rb +44 -0
  55. data/lib/no_brainer/query_runner/write_error.rb +28 -0
  56. data/lib/no_brainer/railtie.rb +36 -0
  57. data/lib/no_brainer/railtie/database.rake +34 -0
  58. data/lib/no_brainer/util.rb +23 -0
  59. data/lib/no_brainer/version.rb +3 -0
  60. data/lib/nobrainer.rb +59 -0
  61. metadata +152 -0
@@ -0,0 +1,71 @@
1
+ module NoBrainer::Criteria::Termination::Cache
2
+ extend ActiveSupport::Concern
3
+
4
+ included { attr_accessor :_with_cache }
5
+
6
+ def with_cache
7
+ chain { |criteria| criteria._with_cache = true }
8
+ end
9
+
10
+ def without_cache
11
+ chain { |criteria| criteria._with_cache = false }
12
+ end
13
+
14
+ def inspect
15
+ msg = super
16
+ msg = "#{msg} \e[1;37m# #{@cache.size} results cached\e[0m" if @cache && with_cache?
17
+ msg
18
+ end
19
+
20
+ def merge!(criteria)
21
+ super
22
+ self._with_cache = criteria._with_cache unless criteria._with_cache.nil?
23
+ self.reload
24
+ self
25
+ end
26
+
27
+ def with_cache?
28
+ @_with_cache.nil? ? NoBrainer::Config.cache_documents : @_with_cache
29
+ end
30
+
31
+ def reload
32
+ @cache = nil
33
+ end
34
+
35
+ def each(options={}, &block)
36
+ return super unless with_cache? && !options[:no_cache] && block
37
+ return @cache.each(&block) if @cache
38
+
39
+ cache = []
40
+ super(options.merge(:no_cache => true)) do |instance|
41
+ block.call(instance)
42
+ cache << instance
43
+ end
44
+ @cache = cache
45
+ self
46
+ end
47
+
48
+ def _override_cache(cache)
49
+ @cache = cache
50
+ end
51
+
52
+ def self.reload_on(*methods)
53
+ methods.each do |method|
54
+ define_method(method) do |*args, &block|
55
+ reload
56
+ super(*args, &block).tap { reload }
57
+ end
58
+ end
59
+ end
60
+
61
+ def self.use_cache_for(*methods)
62
+ methods.each do |method|
63
+ define_method(method) do |*args, &block|
64
+ @cache ? @cache.__send__(method, *args, &block) : super(*args, &block)
65
+ end
66
+ end
67
+ end
68
+
69
+ use_cache_for :first, :last, :count, :empty?, :any?
70
+ reload_on :update_all, :destroy_all, :delete_all
71
+ end
@@ -0,0 +1,19 @@
1
+ module NoBrainer::Criteria::Termination::Count
2
+ extend ActiveSupport::Concern
3
+
4
+ def count
5
+ run(to_rql.count)
6
+ end
7
+
8
+ def empty?
9
+ count == 0
10
+ end
11
+
12
+ def any?
13
+ if block_given?
14
+ to_a.any? { |*args| yield(*args) }
15
+ else
16
+ !empty?
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,11 @@
1
+ module NoBrainer::Criteria::Termination::Delete
2
+ extend ActiveSupport::Concern
3
+
4
+ def delete_all
5
+ run(to_rql.delete)['deleted']
6
+ end
7
+
8
+ def destroy_all
9
+ to_a.each { |doc| doc.destroy }
10
+ end
11
+ end
@@ -0,0 +1,64 @@
1
+ module NoBrainer::Criteria::Termination::EagerLoading
2
+ extend ActiveSupport::Concern
3
+
4
+ included { attr_accessor :_includes }
5
+
6
+ def initialize(options={})
7
+ super
8
+ self._includes = []
9
+ end
10
+
11
+ def includes(*values)
12
+ raise "Please enable caching with NoBrainer::Config.cache_documents = true" unless NoBrainer::Config.cache_documents
13
+ chain { |criteria| criteria._includes = values }
14
+ end
15
+
16
+ def merge!(criteria)
17
+ super
18
+ self._includes = self._includes + criteria._includes
19
+ end
20
+
21
+ def each(options={}, &block)
22
+ return super unless should_eager_load? && !options[:no_eager_loading] && block
23
+
24
+ docs = []
25
+ super(options.merge(:no_eager_loading => true)) { |doc| docs << doc }
26
+ eager_load(docs, self._includes)
27
+ docs.each(&block)
28
+ self
29
+ end
30
+
31
+ private
32
+
33
+ def should_eager_load?
34
+ self._includes.present? && !raw?
35
+ end
36
+
37
+ def get_one(criteria)
38
+ super.tap { |doc| eager_load([doc], self._includes) if should_eager_load? }
39
+ end
40
+
41
+ def eager_load_association(docs, association_name, criteria=nil)
42
+ docs = docs.compact
43
+ return if docs.empty?
44
+ association = docs.first.root_class.association_metadata[association_name.to_sym]
45
+ raise "Unknown association #{association_name}" unless association
46
+ association.eager_load(docs, criteria)
47
+ end
48
+
49
+ def eager_load(docs, includes)
50
+ case includes
51
+ when Hash then includes.each do |k,v|
52
+ if v.is_a?(NoBrainer::Criteria)
53
+ v = v.dup
54
+ nested_includes, v._includes = v._includes, []
55
+ eager_load(eager_load_association(docs, k, v), nested_includes)
56
+ else
57
+ eager_load(eager_load_association(docs, k), v)
58
+ end
59
+ end
60
+ when Array then includes.each { |v| eager_load(docs, v) }
61
+ else eager_load_association(docs, includes)
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,24 @@
1
+ module NoBrainer::Criteria::Termination::Enumerable
2
+ extend ActiveSupport::Concern
3
+
4
+ def each(options={}, &block)
5
+ return enum_for(:each, options) unless block
6
+ self.run.each { |attrs| block.call(instantiate_doc(attrs)) }
7
+ self
8
+ end
9
+
10
+ def to_a
11
+ each.to_a.freeze
12
+ end
13
+
14
+ # TODO test that
15
+ def respond_to?(name, include_private = false)
16
+ super || [].respond_to?(name)
17
+ end
18
+
19
+ # TODO Make something a bit more efficent ?
20
+ def method_missing(name, *args, &block)
21
+ return super unless [].respond_to?(name)
22
+ to_a.__send__(name, *args, &block)
23
+ end
24
+ end
@@ -0,0 +1,25 @@
1
+ module NoBrainer::Criteria::Termination::First
2
+ extend ActiveSupport::Concern
3
+
4
+ def first
5
+ get_one(self)
6
+ end
7
+
8
+ def last
9
+ get_one(self.reverse_order)
10
+ end
11
+
12
+ def first!
13
+ first.tap { |doc| raise NoBrainer::Error::DocumentNotFound unless doc }
14
+ end
15
+
16
+ def last!
17
+ last.tap { |doc| raise NoBrainer::Error::DocumentNotFound unless doc }
18
+ end
19
+
20
+ private
21
+
22
+ def get_one(criteria)
23
+ instantiate_doc(criteria.limit(1).run.first)
24
+ end
25
+ end
@@ -0,0 +1,14 @@
1
+ module NoBrainer::Criteria::Termination::Inc
2
+ extend ActiveSupport::Concern
3
+
4
+ def inc_all(field, value=1)
5
+ # TODO The useful inc() is on a model instance.
6
+ # But then do we want to postpone the inc() to the next save?
7
+ # It might make sense (because we don't have transactions).
8
+ update_all { |doc| { field => doc[field] + value } }
9
+ end
10
+
11
+ def dec_all(field, value=1)
12
+ inc_all(field, -value)
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ module NoBrainer::Criteria::Termination::Update
2
+ extend ActiveSupport::Concern
3
+
4
+ def update_all(attrs={}, &block)
5
+ block = proc { attrs } unless block_given?
6
+ run(to_rql.update(&block))['replaced']
7
+ end
8
+
9
+ def replace_all(attrs={}, &block)
10
+ block = proc { attrs } unless block_given?
11
+ run(to_rql.replace(&block))['replaced']
12
+ end
13
+ end
@@ -0,0 +1,41 @@
1
+ class NoBrainer::Database
2
+ attr_accessor :connection
3
+
4
+ # FIXME This class is a bit weird as we don't use it to represent the current
5
+ # database.
6
+
7
+ delegate :database_name, :to => :connection
8
+
9
+ def initialize(connection)
10
+ self.connection = connection
11
+ end
12
+
13
+ def raw
14
+ @raw ||= RethinkDB::RQL.new.db(database_name)
15
+ end
16
+
17
+ def drop!
18
+ # FIXME Sad hack.
19
+ db = (Thread.current[:nobrainer_options] || {})[:db] || database_name
20
+ connection.db_drop(db)['dropped'] == 1
21
+ end
22
+
23
+ # Note that truncating each table (purge) is much faster than dropping the
24
+ # database (drop)
25
+ def purge!(options={})
26
+ table_list.each do |table_name|
27
+ NoBrainer.run { |r| r.table(table_name).delete }
28
+ end
29
+ true
30
+ rescue RuntimeError => e
31
+ raise e unless e.message =~ /No entry with that name/
32
+ end
33
+
34
+ [:table_create, :table_drop, :table_list].each do |cmd|
35
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
36
+ def #{cmd}(*args)
37
+ NoBrainer.run { |r| r.#{cmd}(*args) }
38
+ end
39
+ RUBY
40
+ end
41
+ end
@@ -0,0 +1,15 @@
1
+ class NoBrainer::DecoratedSymbol < Struct.new(:symbol, :modifier, :args)
2
+ MODIFIERS = { :ne => :ne, :not => :ne, :in => :in, :eq => :eq,
3
+ :gt => :gt, :ge => :ge, :gte => :ge,
4
+ :lt => :lt, :le => :le, :lte => :le}
5
+
6
+ def self.hook
7
+ Symbol.class_eval do
8
+ MODIFIERS.each do |modifier_name, modifier|
9
+ define_method modifier_name do |*args|
10
+ NoBrainer::DecoratedSymbol.new(self, modifier, args)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,18 @@
1
+ require 'active_model'
2
+
3
+ module NoBrainer::Document
4
+ extend ActiveSupport::Concern
5
+ extend NoBrainer::Autoload
6
+
7
+ autoload_and_include :Core, :StoreIn, :InjectionLayer, :Attributes, :Persistance, :Dirty,
8
+ :Id, :Association, :Serialization, :Criteria, :Validation,
9
+ :Polymorphic, :Index
10
+
11
+ autoload :DynamicAttributes, :Timestamps
12
+
13
+ included do
14
+ include Timestamps if NoBrainer::Config.auto_include_timestamps
15
+ end
16
+
17
+ singleton_class.delegate :all, :to => Core
18
+ end
@@ -0,0 +1,41 @@
1
+ module NoBrainer::Document::Association
2
+ extend NoBrainer::Autoload
3
+ autoload :Core, :BelongsTo, :HasMany
4
+
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ class << self; attr_accessor :association_metadata; end
9
+ self.association_metadata = {}
10
+ end
11
+
12
+ def association(metadata)
13
+ @associations ||= {}
14
+ @associations[metadata] ||= metadata.new(self)
15
+ end
16
+
17
+ module ClassMethods
18
+ def inherited(subclass)
19
+ super
20
+ subclass.association_metadata = self.association_metadata.dup
21
+ end
22
+
23
+ [:belongs_to, :has_many].each do |association|
24
+ define_method(association) do |target, options={}|
25
+ target = target.to_sym
26
+
27
+ if r = self.association_metadata[target]
28
+ r.options.merge!(options)
29
+ else
30
+ metadata_klass = NoBrainer::Document::Association.const_get(association.to_s.camelize).const_get(:Metadata)
31
+ r = metadata_klass.new(self, target, options)
32
+ ([self] + descendants).each do |klass|
33
+ klass.association_metadata[target] = r
34
+ end
35
+ end
36
+ r.hook
37
+ r
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,64 @@
1
+ class NoBrainer::Document::Association::BelongsTo
2
+ include NoBrainer::Document::Association::Core
3
+
4
+ class Metadata
5
+ VALID_BELONGS_TO_OPTIONS = [:foreign_key, :class_name, :index]
6
+ include NoBrainer::Document::Association::Core::Metadata
7
+
8
+ def foreign_key
9
+ # TODO test :foreign_key
10
+ options[:foreign_key].try(:to_sym) || :"#{target_name}_id"
11
+ end
12
+
13
+ def target_klass
14
+ # TODO test :class_name
15
+ (options[:class_name] || target_name.to_s.camelize).constantize
16
+ end
17
+
18
+ def hook
19
+ super
20
+ options.assert_valid_keys(*VALID_BELONGS_TO_OPTIONS)
21
+
22
+ owner_klass.field foreign_key, :index => options[:index]
23
+ delegate("#{foreign_key}=", :assign_foreign_key, :call_super => true)
24
+ add_callback_for(:after_validation)
25
+ end
26
+
27
+ def eager_load(docs, criteria=nil)
28
+ target_criteria = target_klass.all
29
+ target_criteria = target_criteria.merge(criteria) if criteria
30
+ docs_fks = Hash[docs.map { |doc| [doc, doc.read_attribute(foreign_key)] }]
31
+ fks = docs_fks.values.compact.uniq
32
+ fk_targets = Hash[target_criteria.where(:id.in => fks).map { |doc| [doc.id, doc] }]
33
+ docs_fks.each { |doc, fk| doc.association(self)._write(fk_targets[fk]) if fk_targets[fk] }
34
+ fk_targets.values
35
+ end
36
+ end
37
+
38
+ def assign_foreign_key(value)
39
+ @target = nil
40
+ end
41
+
42
+ def read
43
+ return @target if @target && NoBrainer::Config.cache_documents
44
+ if fk = instance.read_attribute(foreign_key)
45
+ @target = target_klass.find(fk)
46
+ end
47
+ end
48
+
49
+ def _write(target)
50
+ @target = target
51
+ end
52
+
53
+ def write(target)
54
+ assert_target_type(target)
55
+ instance.write_attribute(foreign_key, target.try(:id))
56
+ _write(target)
57
+ end
58
+
59
+ def after_validation_callback
60
+ if @target && !@target.persisted?
61
+ raise NoBrainer::Error::AssociationNotSaved.new("#{target_name} must be saved first")
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,64 @@
1
+ module NoBrainer::Document::Association::Core
2
+ extend ActiveSupport::Concern
3
+
4
+ module Metadata
5
+ extend ActiveSupport::Concern
6
+
7
+ attr_accessor :owner_klass, :target_name, :options
8
+
9
+ def initialize(owner_klass, target_name, options={})
10
+ @owner_klass = owner_klass
11
+ @target_name = target_name
12
+ @options = options
13
+ end
14
+
15
+ def association_klass
16
+ @association_klass ||= self.class.name.deconstantize.constantize
17
+ end
18
+
19
+ def new(instance)
20
+ association_klass.new(self, instance)
21
+ end
22
+
23
+ def delegate(method_name, target, options={})
24
+ metadata = self
25
+ owner_klass.inject_in_layer :associations do
26
+ define_method(method_name) do |*args, &block|
27
+ super(*args, &block) if options[:call_super]
28
+ association(metadata).__send__(target, *args, &block)
29
+ end
30
+ end
31
+ end
32
+
33
+ def hook
34
+ delegate("#{target_name}=", :write)
35
+ delegate("#{target_name}", :read)
36
+ end
37
+
38
+ def add_callback_for(what)
39
+ instance_eval <<-RUBY, __FILE__, __LINE__+1
40
+ if !@added_#{what}
41
+ metadata = self
42
+ owner_klass.#{what} { association(metadata).#{what}_callback }
43
+ @added_#{what} = true
44
+ end
45
+ RUBY
46
+ end
47
+ end
48
+
49
+ included { attr_accessor :metadata, :instance }
50
+
51
+ delegate :foreign_key, :target_name, :target_klass, :to => :metadata
52
+
53
+ def initialize(metadata, instance)
54
+ @metadata = metadata
55
+ @instance = instance
56
+ end
57
+
58
+ def assert_target_type(value)
59
+ unless value.is_a?(target_klass) || value.nil?
60
+ msg = "Trying to use a #{value.class} as a #{target_name}"
61
+ raise NoBrainer::Error::InvalidType.new(msg)
62
+ end
63
+ end
64
+ end