nobrainer 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|