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,68 @@
|
|
1
|
+
class NoBrainer::Document::Association::HasMany
|
2
|
+
include NoBrainer::Document::Association::Core
|
3
|
+
|
4
|
+
class Metadata
|
5
|
+
VALID_HAS_MANY_OPTIONS = [:foreign_key, :class_name, :dependent]
|
6
|
+
include NoBrainer::Document::Association::Core::Metadata
|
7
|
+
|
8
|
+
def foreign_key
|
9
|
+
# TODO test :foreign_key
|
10
|
+
options[:foreign_key].try(:to_sym) || :"#{owner_klass.name.underscore}_id"
|
11
|
+
end
|
12
|
+
|
13
|
+
def target_klass
|
14
|
+
# TODO test :class_name
|
15
|
+
(options[:class_name] || target_name.to_s.singularize.camelize).constantize
|
16
|
+
end
|
17
|
+
|
18
|
+
def hook
|
19
|
+
super
|
20
|
+
options.assert_valid_keys(*VALID_HAS_MANY_OPTIONS)
|
21
|
+
add_callback_for(:before_destroy)
|
22
|
+
end
|
23
|
+
|
24
|
+
def eager_load(docs, criteria=nil)
|
25
|
+
target_criteria = target_klass.all
|
26
|
+
target_criteria = target_criteria.merge(criteria) if criteria
|
27
|
+
docs_ids = Hash[docs.map { |doc| [doc, doc.id] }]
|
28
|
+
fk_targets = target_criteria
|
29
|
+
.where(foreign_key.in => docs_ids.values)
|
30
|
+
.reduce({}) do |hash, doc|
|
31
|
+
fk = doc.read_attribute(foreign_key)
|
32
|
+
hash[fk] ||= []
|
33
|
+
hash[fk] << doc
|
34
|
+
hash
|
35
|
+
end
|
36
|
+
docs_ids.each { |doc, id| doc.association(self)._write(fk_targets[id]) if fk_targets[id] }
|
37
|
+
fk_targets.values.flatten(1)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def children_criteria
|
42
|
+
@children_criteria ||= target_klass.where(foreign_key => instance.id)
|
43
|
+
end
|
44
|
+
|
45
|
+
def read
|
46
|
+
children_criteria
|
47
|
+
end
|
48
|
+
|
49
|
+
def write(new_children)
|
50
|
+
raise "You can't assign the array of #{target_name}. Instead, you must modify delete and create #{target_klass} manually."
|
51
|
+
end
|
52
|
+
|
53
|
+
def _write(new_children)
|
54
|
+
children_criteria._override_cache(new_children)
|
55
|
+
end
|
56
|
+
|
57
|
+
def before_destroy_callback
|
58
|
+
criteria = children_criteria.unscoped
|
59
|
+
case metadata.options[:dependent]
|
60
|
+
when nil then
|
61
|
+
when :destroy then criteria.destroy_all
|
62
|
+
when :delete then criteria.delete_all
|
63
|
+
when :nullify then criteria.update_all(foreign_key => nil)
|
64
|
+
when :restrict then raise NoBrainer::Error::ChildrenExist unless criteria.count.zero?
|
65
|
+
else raise "Unrecognized dependent option: #{metadata.options[:dependent]}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module NoBrainer::Document::Attributes
|
2
|
+
VALID_FIELD_OPTIONS = [:index, :default]
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
# Not using class_attribute because we want to
|
7
|
+
# use our custom logic
|
8
|
+
class << self; attr_accessor :fields; end
|
9
|
+
self.fields = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(attrs={}, options={})
|
13
|
+
super
|
14
|
+
@attributes = {}
|
15
|
+
assign_attributes(attrs, options.reverse_merge(:pristine => true))
|
16
|
+
end
|
17
|
+
|
18
|
+
def attributes
|
19
|
+
@attributes.dup.freeze
|
20
|
+
end
|
21
|
+
|
22
|
+
def read_attribute(name)
|
23
|
+
__send__("#{name}")
|
24
|
+
end
|
25
|
+
def [](*args); read_attribute(*args); end
|
26
|
+
|
27
|
+
def write_attribute(name, value)
|
28
|
+
__send__("#{name}=", value)
|
29
|
+
end
|
30
|
+
def []=(*args); write_attribute(*args); end
|
31
|
+
|
32
|
+
def assign_defaults
|
33
|
+
self.class.fields.each do |name, field_options|
|
34
|
+
if field_options.has_key?(:default) && !@attributes.has_key?(name.to_s)
|
35
|
+
default_value = field_options[:default]
|
36
|
+
default_value = default_value.call if default_value.is_a?(Proc)
|
37
|
+
self.write_attribute(name, default_value)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def assign_attributes(attrs, options={})
|
43
|
+
# XXX We don't save field that are not explicitly set. The row will
|
44
|
+
# therefore not contain nil for unset attributes.
|
45
|
+
@attributes.clear if options[:pristine]
|
46
|
+
|
47
|
+
if options[:from_db]
|
48
|
+
# TODO Should we reject undeclared fields ?
|
49
|
+
#
|
50
|
+
# TODO Not using the getter/setters, the dirty tracking won't notice it,
|
51
|
+
# also we should start thinking about custom serializer/deserializer.
|
52
|
+
@attributes.merge! attrs
|
53
|
+
else
|
54
|
+
attrs.each { |k,v| self.write_attribute(k, v) }
|
55
|
+
end
|
56
|
+
|
57
|
+
assign_defaults if options[:pristine] || options[:from_db]
|
58
|
+
self
|
59
|
+
end
|
60
|
+
def attributes=(*args); assign_attributes(*args); end
|
61
|
+
|
62
|
+
# TODO test that thing
|
63
|
+
def inspect
|
64
|
+
attrs = self.class.fields.keys.map { |f| "#{f}: #{@attributes[f.to_s].inspect}" }
|
65
|
+
"#<#{self.class} #{attrs.join(', ')}>"
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_s
|
69
|
+
inspect
|
70
|
+
end
|
71
|
+
|
72
|
+
module ClassMethods
|
73
|
+
def new_from_db(attrs, options={})
|
74
|
+
klass_from_attrs(attrs).new(attrs, options.reverse_merge(:from_db => true)) if attrs
|
75
|
+
end
|
76
|
+
|
77
|
+
def inherited(subclass)
|
78
|
+
super
|
79
|
+
subclass.fields = self.fields.dup
|
80
|
+
end
|
81
|
+
|
82
|
+
def field(name, options={})
|
83
|
+
name = name.to_sym
|
84
|
+
|
85
|
+
options.assert_valid_keys(*NoBrainer::Document::Attributes::VALID_FIELD_OPTIONS)
|
86
|
+
if name.in? NoBrainer::Criteria::Chainable::Where::RESERVED_FIELDS
|
87
|
+
raise "Cannot use a reserved field name: #{name}"
|
88
|
+
end
|
89
|
+
|
90
|
+
([self] + descendants).each do |klass|
|
91
|
+
klass.fields[name] ||= {}
|
92
|
+
klass.fields[name].merge!(options)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Using a layer so the user can use super when overriding these methods
|
96
|
+
inject_in_layer :attributes, <<-RUBY, __FILE__, __LINE__ + 1
|
97
|
+
def #{name}=(value)
|
98
|
+
@attributes['#{name}'] = value
|
99
|
+
end
|
100
|
+
|
101
|
+
def #{name}
|
102
|
+
@attributes['#{name}']
|
103
|
+
end
|
104
|
+
RUBY
|
105
|
+
end
|
106
|
+
|
107
|
+
def remove_field(name)
|
108
|
+
name = name.to_sym
|
109
|
+
|
110
|
+
([self] + descendants).each do |klass|
|
111
|
+
klass.fields.delete(name)
|
112
|
+
end
|
113
|
+
|
114
|
+
inject_in_layer :attributes, <<-RUBY, __FILE__, __LINE__ + 1
|
115
|
+
undef #{name}=
|
116
|
+
undef #{name}
|
117
|
+
RUBY
|
118
|
+
end
|
119
|
+
|
120
|
+
def has_field?(name)
|
121
|
+
!!fields[name.to_sym]
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module NoBrainer::Document::Core
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
class << self; attr_accessor :all; end
|
5
|
+
self.all = []
|
6
|
+
|
7
|
+
# TODO This assume the primary key is id.
|
8
|
+
# RethinkDB can have a custom primary key. careful.
|
9
|
+
include ActiveModel::Conversion
|
10
|
+
|
11
|
+
included do
|
12
|
+
# TODO test these includes
|
13
|
+
extend ActiveModel::Naming
|
14
|
+
extend ActiveModel::Translation
|
15
|
+
|
16
|
+
NoBrainer::Document::Core.all << self
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(attrs={}, options={}); end
|
20
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module NoBrainer::Document::Criteria
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
def selector
|
5
|
+
self.class.selector_for(id)
|
6
|
+
end
|
7
|
+
|
8
|
+
included do
|
9
|
+
class_attribute :default_scope_proc
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
delegate :to_rql, # Core
|
14
|
+
:limit, :offset, :skip, # Limit
|
15
|
+
:order_by, :reverse_order, # OrderBy
|
16
|
+
:scoped, :unscoped, # Scope
|
17
|
+
:where, :with_index, :without_index, :used_index, :indexed?, # Where
|
18
|
+
:with_cache, :without_cache, # Cache
|
19
|
+
:count, :empty?, :any?, # Count
|
20
|
+
:delete_all, :destroy_all, # Delete
|
21
|
+
:includes, # EagerLoading
|
22
|
+
:each, :to_a, # Enumerable
|
23
|
+
:first, :last, :first!, :last!, # First
|
24
|
+
:inc_all, :dec_all, # Inc
|
25
|
+
:update_all, :replace_all, # Update
|
26
|
+
:to => :all
|
27
|
+
|
28
|
+
def all
|
29
|
+
NoBrainer::Criteria.new(:klass => self)
|
30
|
+
end
|
31
|
+
|
32
|
+
def scope(name, criteria=nil, &block)
|
33
|
+
criteria ||= block
|
34
|
+
criteria_proc = criteria.is_a?(Proc) ? criteria : proc { criteria }
|
35
|
+
singleton_class.class_eval do
|
36
|
+
define_method(name) { |*args| criteria_proc.call(*args) }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def default_scope(criteria=nil, &block)
|
41
|
+
criteria ||= block
|
42
|
+
self.default_scope_proc = criteria.is_a?(Proc) ? criteria : proc { criteria }
|
43
|
+
end
|
44
|
+
|
45
|
+
def selector_for(id)
|
46
|
+
# TODO Pass primary key if not default
|
47
|
+
unscoped.where(:id => id)
|
48
|
+
end
|
49
|
+
|
50
|
+
# XXX this doesn't have the same semantics as
|
51
|
+
# other ORMs. the equivalent is find!.
|
52
|
+
def find(id)
|
53
|
+
selector_for(id).first
|
54
|
+
end
|
55
|
+
|
56
|
+
def find!(id)
|
57
|
+
find(id).tap do |doc|
|
58
|
+
raise NoBrainer::Error::DocumentNotFound, "#{self.class} id #{id} not found" unless doc
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module NoBrainer::Document::Dirty
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
# We are not using ActiveModel::Dirty because it's using
|
5
|
+
# ActiveModel::AttributeMethods which gives pretty violent method_missing()
|
6
|
+
# capabilities, such as giving a getter/setter method for any keys within the
|
7
|
+
# attributes keys. We don't want that.
|
8
|
+
|
9
|
+
included do
|
10
|
+
attr_accessor :previous_changes
|
11
|
+
after_save { clear_dirtiness }
|
12
|
+
end
|
13
|
+
|
14
|
+
def changed_attributes
|
15
|
+
@changed_attributes ||= {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def assign_attributes(attrs, options={})
|
19
|
+
clear_dirtiness if options[:pristine] || options[:from_db]
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
def clear_dirtiness
|
24
|
+
self.previous_changes = changes
|
25
|
+
self.changed_attributes.clear
|
26
|
+
end
|
27
|
+
|
28
|
+
def changed?
|
29
|
+
changed_attributes.present?
|
30
|
+
end
|
31
|
+
|
32
|
+
def changed
|
33
|
+
changed_attributes.keys
|
34
|
+
end
|
35
|
+
|
36
|
+
def changes
|
37
|
+
Hash[changed_attributes.map { |k,v| [k, [v, read_attribute(k)]] }]
|
38
|
+
end
|
39
|
+
|
40
|
+
def attribute_will_change!(attr, new_value)
|
41
|
+
return if changed_attributes.include?(attr)
|
42
|
+
|
43
|
+
# ActiveModel ignores TypeError and NoMethodError exception as if nothng
|
44
|
+
# happened. Why is that?
|
45
|
+
value = read_attribute(attr)
|
46
|
+
value = value.clone if value.duplicable?
|
47
|
+
|
48
|
+
return if value == new_value
|
49
|
+
|
50
|
+
changed_attributes[attr] = value
|
51
|
+
end
|
52
|
+
|
53
|
+
module ClassMethods
|
54
|
+
def field(name, options={})
|
55
|
+
super
|
56
|
+
|
57
|
+
inject_in_layer :dirty_tracking, <<-RUBY, __FILE__, __LINE__ + 1
|
58
|
+
def #{name}_changed?
|
59
|
+
changed_attributes.include?(:#{name})
|
60
|
+
end
|
61
|
+
|
62
|
+
def #{name}_change
|
63
|
+
[changed_attributes[:#{name}], #{name}] if #{name}_changed?
|
64
|
+
end
|
65
|
+
|
66
|
+
def #{name}_was
|
67
|
+
#{name}_changed? ? changed_attributes[:#{name}] : #{name}
|
68
|
+
end
|
69
|
+
|
70
|
+
def #{name}=(value)
|
71
|
+
attribute_will_change!(:#{name}, value)
|
72
|
+
super
|
73
|
+
end
|
74
|
+
RUBY
|
75
|
+
end
|
76
|
+
|
77
|
+
def remove_field(name)
|
78
|
+
super
|
79
|
+
|
80
|
+
inject_in_layer :dirty_tracking, <<-RUBY, __FILE__, __LINE__ + 1
|
81
|
+
undef #{name}_changed?
|
82
|
+
undef #{name}_change
|
83
|
+
undef #{name}_was
|
84
|
+
undef #{name}=
|
85
|
+
RUBY
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module NoBrainer::Document::DynamicAttributes
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
def read_attribute(name)
|
5
|
+
self.respond_to?("#{name}") ? super : @attributes[name.to_s]
|
6
|
+
end
|
7
|
+
|
8
|
+
def write_attribute(name, value)
|
9
|
+
attribute_will_change!(name, value)
|
10
|
+
self.respond_to?("#{name}=") ? super : @attributes[name.to_s] = value
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'socket'
|
3
|
+
require 'digest/md5'
|
4
|
+
|
5
|
+
module NoBrainer::Document::Id
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
self.field :id, :default => ->{ NoBrainer::Document::Id.generate }
|
10
|
+
end
|
11
|
+
|
12
|
+
def ==(other)
|
13
|
+
return super unless self.class == other.class
|
14
|
+
!id.nil? && id == other.id
|
15
|
+
end
|
16
|
+
alias_method :eql?, :==
|
17
|
+
|
18
|
+
delegate :hash, :to => :id
|
19
|
+
|
20
|
+
# The following code is inspired by the mongo-ruby-driver
|
21
|
+
|
22
|
+
@machine_id = Digest::MD5.digest(Socket.gethostname)[0, 3]
|
23
|
+
@lock = Mutex.new
|
24
|
+
@index = 0
|
25
|
+
|
26
|
+
def self.get_inc
|
27
|
+
@lock.synchronize do
|
28
|
+
@index = (@index + 1) % 0xFFFFFF
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# TODO Unit test that thing
|
33
|
+
def self.generate
|
34
|
+
oid = ''
|
35
|
+
# 4 bytes current time
|
36
|
+
oid += [Time.now.to_i].pack("N")
|
37
|
+
|
38
|
+
# 3 bytes machine
|
39
|
+
oid += @machine_id
|
40
|
+
|
41
|
+
# 2 bytes pid
|
42
|
+
oid += [Process.pid % 0xFFFF].pack("n")
|
43
|
+
|
44
|
+
# 3 bytes inc
|
45
|
+
oid += [get_inc].pack("N")[1, 3]
|
46
|
+
|
47
|
+
oid.unpack("C12").map {|e| v=e.to_s(16); v.size == 1 ? "0#{v}" : v }.join
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module NoBrainer::Document::Index
|
2
|
+
VALID_INDEX_OPTIONS = [:multi]
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
class_attribute :indexes
|
7
|
+
self.indexes = {}
|
8
|
+
self.index :id
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def index(name, *args)
|
13
|
+
name = name.to_sym
|
14
|
+
options = args.extract_options!
|
15
|
+
options.assert_valid_keys(*NoBrainer::Document::Index::VALID_INDEX_OPTIONS)
|
16
|
+
|
17
|
+
raise "Too many arguments: #{args}" if args.size > 1
|
18
|
+
|
19
|
+
kind, what = case args.first
|
20
|
+
when nil then [:single, name.to_sym]
|
21
|
+
when Array then [:compound, args.first.map(&:to_sym)]
|
22
|
+
when Proc then [:proc, args.first]
|
23
|
+
else raise "Index argument must be a lambda or a list of fields"
|
24
|
+
end
|
25
|
+
|
26
|
+
# FIXME Primary key may not always be :id
|
27
|
+
if name.in?(NoBrainer::Criteria::Chainable::Where::RESERVED_FIELDS)
|
28
|
+
raise "Cannot use a reserved field name: #{name}"
|
29
|
+
end
|
30
|
+
if has_field?(name) && kind != :single
|
31
|
+
raise "Cannot reuse field name #{name}"
|
32
|
+
end
|
33
|
+
|
34
|
+
indexes[name] = {:kind => kind, :what => what, :options => options}
|
35
|
+
end
|
36
|
+
|
37
|
+
def remove_index(name)
|
38
|
+
indexes.delete(name.to_sym)
|
39
|
+
end
|
40
|
+
|
41
|
+
def has_index?(name)
|
42
|
+
!!indexes[name.to_sym]
|
43
|
+
end
|
44
|
+
|
45
|
+
def field(name, options={})
|
46
|
+
name = name.to_sym
|
47
|
+
|
48
|
+
if has_index?(name) && indexes[name][:kind] != :single
|
49
|
+
raise "Cannot reuse index name #{name}"
|
50
|
+
end
|
51
|
+
|
52
|
+
super
|
53
|
+
|
54
|
+
case options[:index]
|
55
|
+
when nil then
|
56
|
+
when Hash then index(name, options[:index])
|
57
|
+
when Symbol then index(name, options[:index] => true)
|
58
|
+
when true then index(name)
|
59
|
+
when false then remove_index(name)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def remove_field(name)
|
64
|
+
remove_index(name) if fields[name.to_sym][:index]
|
65
|
+
super
|
66
|
+
end
|
67
|
+
|
68
|
+
def perform_create_index(index_name, options={})
|
69
|
+
index_name = index_name.to_sym
|
70
|
+
index_args = self.indexes[index_name]
|
71
|
+
|
72
|
+
index_proc = case index_args[:kind]
|
73
|
+
when :single then nil
|
74
|
+
when :compound then ->(doc) { index_args[:what].map { |field| doc[field] } }
|
75
|
+
when :proc then index_args[:what]
|
76
|
+
end
|
77
|
+
|
78
|
+
NoBrainer.run(self.rql_table.index_create(index_name, index_args[:options], &index_proc))
|
79
|
+
NoBrainer.run(self.rql_table.index_wait(index_name)) if options[:wait]
|
80
|
+
STDERR.puts "Created index #{self}.#{index_name}" if options[:verbose]
|
81
|
+
end
|
82
|
+
|
83
|
+
def perform_drop_index(index_name, options={})
|
84
|
+
NoBrainer.run(self.rql_table.index_drop(index_name))
|
85
|
+
STDERR.puts "Dropped index #{self}.#{index_name}" if options[:verbose]
|
86
|
+
end
|
87
|
+
|
88
|
+
def perform_update_indexes(options={})
|
89
|
+
current_indexes = NoBrainer.run(self.rql_table.index_list).map(&:to_sym)
|
90
|
+
wanted_indexes = self.indexes.keys - [:id] # XXX Primary key?
|
91
|
+
|
92
|
+
(current_indexes - wanted_indexes).each do |index_name|
|
93
|
+
perform_drop_index(index_name, options)
|
94
|
+
end
|
95
|
+
|
96
|
+
(wanted_indexes - current_indexes).each do |index_name|
|
97
|
+
perform_create_index(index_name, options)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
alias_method :update_indexes, :perform_update_indexes
|
101
|
+
end
|
102
|
+
end
|