massive_record 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +15 -0
- data/.gitignore +6 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +38 -0
- data/Manifest +24 -0
- data/README.md +225 -0
- data/Rakefile +16 -0
- data/TODO.md +8 -0
- data/autotest/discover.rb +1 -0
- data/lib/massive_record.rb +18 -0
- data/lib/massive_record/exceptions.rb +11 -0
- data/lib/massive_record/orm/attribute_methods.rb +61 -0
- data/lib/massive_record/orm/attribute_methods/dirty.rb +80 -0
- data/lib/massive_record/orm/attribute_methods/read.rb +23 -0
- data/lib/massive_record/orm/attribute_methods/write.rb +24 -0
- data/lib/massive_record/orm/base.rb +176 -0
- data/lib/massive_record/orm/callbacks.rb +52 -0
- data/lib/massive_record/orm/column.rb +18 -0
- data/lib/massive_record/orm/config.rb +47 -0
- data/lib/massive_record/orm/errors.rb +47 -0
- data/lib/massive_record/orm/finders.rb +125 -0
- data/lib/massive_record/orm/id_factory.rb +133 -0
- data/lib/massive_record/orm/persistence.rb +199 -0
- data/lib/massive_record/orm/schema.rb +4 -0
- data/lib/massive_record/orm/schema/column_families.rb +48 -0
- data/lib/massive_record/orm/schema/column_family.rb +102 -0
- data/lib/massive_record/orm/schema/column_interface.rb +91 -0
- data/lib/massive_record/orm/schema/common_interface.rb +48 -0
- data/lib/massive_record/orm/schema/field.rb +128 -0
- data/lib/massive_record/orm/schema/fields.rb +37 -0
- data/lib/massive_record/orm/schema/table_interface.rb +96 -0
- data/lib/massive_record/orm/table.rb +9 -0
- data/lib/massive_record/orm/validations.rb +52 -0
- data/lib/massive_record/spec/support/simple_database_cleaner.rb +52 -0
- data/lib/massive_record/thrift/hbase.rb +2307 -0
- data/lib/massive_record/thrift/hbase_constants.rb +14 -0
- data/lib/massive_record/thrift/hbase_types.rb +225 -0
- data/lib/massive_record/version.rb +3 -0
- data/lib/massive_record/wrapper/base.rb +28 -0
- data/lib/massive_record/wrapper/cell.rb +45 -0
- data/lib/massive_record/wrapper/column_families_collection.rb +19 -0
- data/lib/massive_record/wrapper/column_family.rb +22 -0
- data/lib/massive_record/wrapper/connection.rb +71 -0
- data/lib/massive_record/wrapper/row.rb +170 -0
- data/lib/massive_record/wrapper/scanner.rb +50 -0
- data/lib/massive_record/wrapper/table.rb +148 -0
- data/lib/massive_record/wrapper/tables_collection.rb +13 -0
- data/massive_record.gemspec +28 -0
- data/spec/config.yml.example +4 -0
- data/spec/orm/cases/attribute_methods_spec.rb +47 -0
- data/spec/orm/cases/auto_generate_id_spec.rb +54 -0
- data/spec/orm/cases/base_spec.rb +176 -0
- data/spec/orm/cases/callbacks_spec.rb +309 -0
- data/spec/orm/cases/column_spec.rb +49 -0
- data/spec/orm/cases/config_spec.rb +103 -0
- data/spec/orm/cases/dirty_spec.rb +129 -0
- data/spec/orm/cases/encoding_spec.rb +49 -0
- data/spec/orm/cases/finders_spec.rb +208 -0
- data/spec/orm/cases/hbase/connection_spec.rb +13 -0
- data/spec/orm/cases/i18n_spec.rb +32 -0
- data/spec/orm/cases/id_factory_spec.rb +75 -0
- data/spec/orm/cases/persistence_spec.rb +479 -0
- data/spec/orm/cases/table_spec.rb +81 -0
- data/spec/orm/cases/validation_spec.rb +92 -0
- data/spec/orm/models/address.rb +7 -0
- data/spec/orm/models/person.rb +15 -0
- data/spec/orm/models/test_class.rb +5 -0
- data/spec/orm/schema/column_families_spec.rb +186 -0
- data/spec/orm/schema/column_family_spec.rb +131 -0
- data/spec/orm/schema/column_interface_spec.rb +115 -0
- data/spec/orm/schema/field_spec.rb +196 -0
- data/spec/orm/schema/fields_spec.rb +126 -0
- data/spec/orm/schema/table_interface_spec.rb +171 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/support/connection_helpers.rb +76 -0
- data/spec/support/mock_massive_record_connection.rb +80 -0
- data/spec/thrift/cases/encoding_spec.rb +48 -0
- data/spec/wrapper/cases/connection_spec.rb +53 -0
- data/spec/wrapper/cases/table_spec.rb +231 -0
- metadata +228 -0
@@ -0,0 +1,80 @@
|
|
1
|
+
module MassiveRecord
|
2
|
+
module ORM
|
3
|
+
module AttributeMethods
|
4
|
+
module Dirty
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
include ActiveModel::Dirty
|
7
|
+
|
8
|
+
|
9
|
+
def save(*)
|
10
|
+
if status = super
|
11
|
+
changes_before_save = changes
|
12
|
+
clear_dirty_states!
|
13
|
+
@previously_changed = changes_before_save
|
14
|
+
end
|
15
|
+
status
|
16
|
+
end
|
17
|
+
|
18
|
+
def save!(*)
|
19
|
+
super.tap do
|
20
|
+
changes_before_save = changes
|
21
|
+
clear_dirty_states!
|
22
|
+
@previously_changed = changes_before_save
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def reload(*)
|
27
|
+
super.tap do
|
28
|
+
clear_dirty_states!
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def write_attribute(attr_name, value)
|
33
|
+
attr_name = attr_name.to_s
|
34
|
+
|
35
|
+
if will_change_attribute?(attr_name, value)
|
36
|
+
if will_change_back_to_original_value?(attr_name, value)
|
37
|
+
changed_attributes.delete(attr_name)
|
38
|
+
else
|
39
|
+
super(attr_name, original_attribute_value(attr_name))
|
40
|
+
send("#{attr_name}_will_change!")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
super
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def update(*)
|
51
|
+
changes.empty? ? true : super(changes.keys)
|
52
|
+
end
|
53
|
+
|
54
|
+
def original_attribute_value(attr_name)
|
55
|
+
@original_attribute_values ||= {}
|
56
|
+
|
57
|
+
unless @original_attribute_values.has_key? attr_name
|
58
|
+
@original_attribute_values[attr_name] = send(attr_name)
|
59
|
+
end
|
60
|
+
|
61
|
+
@original_attribute_values[attr_name]
|
62
|
+
end
|
63
|
+
|
64
|
+
def will_change_attribute?(attr_name, value)
|
65
|
+
read_attribute(attr_name) != value
|
66
|
+
end
|
67
|
+
|
68
|
+
def will_change_back_to_original_value?(attr_name, value)
|
69
|
+
original_attribute_value(attr_name) == value
|
70
|
+
end
|
71
|
+
|
72
|
+
def clear_dirty_states!
|
73
|
+
changed_attributes.clear
|
74
|
+
@original_attribute_values.clear if @original_attribute_values
|
75
|
+
@previously_changed.clear if @previously_changed
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module MassiveRecord
|
2
|
+
module ORM
|
3
|
+
module AttributeMethods
|
4
|
+
module Read
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
attribute_method_suffix ""
|
9
|
+
end
|
10
|
+
|
11
|
+
def read_attribute(attr_name)
|
12
|
+
attributes_schema[attr_name].nil? ? @attributes[attr_name.to_s] : attributes_schema[attr_name].decode(@attributes[attr_name.to_s])
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def attribute(attr_name)
|
18
|
+
read_attribute(attr_name)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module MassiveRecord
|
2
|
+
module ORM
|
3
|
+
module AttributeMethods
|
4
|
+
module Write
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
attribute_method_suffix "="
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
def write_attribute(attr_name, value)
|
13
|
+
@attributes[attr_name.to_s] = value
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def attribute=(attr_name, value)
|
19
|
+
write_attribute(attr_name, value)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
require 'active_support/core_ext/class/attribute_accessors'
|
3
|
+
require 'active_support/core_ext/class/attribute'
|
4
|
+
require 'active_support/core_ext/class/subclasses'
|
5
|
+
require 'active_support/core_ext/module'
|
6
|
+
require 'active_support/core_ext/string'
|
7
|
+
require 'active_support/memoizable'
|
8
|
+
|
9
|
+
require 'massive_record/orm/schema'
|
10
|
+
require 'massive_record/orm/errors'
|
11
|
+
require 'massive_record/orm/config'
|
12
|
+
require 'massive_record/orm/finders'
|
13
|
+
require 'massive_record/orm/attribute_methods'
|
14
|
+
require 'massive_record/orm/attribute_methods/write'
|
15
|
+
require 'massive_record/orm/attribute_methods/read'
|
16
|
+
require 'massive_record/orm/attribute_methods/dirty'
|
17
|
+
require 'massive_record/orm/validations'
|
18
|
+
require 'massive_record/orm/callbacks'
|
19
|
+
require 'massive_record/orm/persistence'
|
20
|
+
|
21
|
+
module MassiveRecord
|
22
|
+
module ORM
|
23
|
+
class Base
|
24
|
+
include ActiveModel::Conversion
|
25
|
+
|
26
|
+
# Add a prefix or a suffix to the table name
|
27
|
+
# example:
|
28
|
+
#
|
29
|
+
# MassiveRecord::ORM::Base.table_name_prefix = "_production"
|
30
|
+
class_attribute :table_name_overriden, :instance_writer => false
|
31
|
+
self.table_name_overriden = nil
|
32
|
+
|
33
|
+
class_attribute :table_name_prefix, :instance_writer => false
|
34
|
+
self.table_name_prefix = ""
|
35
|
+
|
36
|
+
class_attribute :table_name_suffix, :instance_writer => false
|
37
|
+
self.table_name_suffix = ""
|
38
|
+
|
39
|
+
class << self
|
40
|
+
def table_name
|
41
|
+
@table_name ||= table_name_prefix + (table_name_overriden.blank? ? self.to_s.demodulize.underscore.pluralize : table_name_overriden) + table_name_suffix
|
42
|
+
end
|
43
|
+
|
44
|
+
def table_name=(name)
|
45
|
+
self.table_name_overriden = name
|
46
|
+
end
|
47
|
+
alias :set_table_name :table_name=
|
48
|
+
|
49
|
+
def reset_table_name_configuration!
|
50
|
+
@table_name = self.table_name_overriden = nil
|
51
|
+
self.table_name_prefix = self.table_name_suffix = ""
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
#
|
56
|
+
# Initialize a new object. Send in attributes which
|
57
|
+
# we'll dynamically set up read- and write methods for
|
58
|
+
# and assign to instance variables. How read- and write
|
59
|
+
# methods are defined might change over time when the DSL
|
60
|
+
# for describing column families and fields are in place
|
61
|
+
#
|
62
|
+
def initialize(attributes = {})
|
63
|
+
self.attributes_raw = attributes_from_field_definition.merge(attributes)
|
64
|
+
self.attributes = attributes
|
65
|
+
@new_record = true
|
66
|
+
@destroyed = false
|
67
|
+
|
68
|
+
_run_initialize_callbacks
|
69
|
+
end
|
70
|
+
|
71
|
+
# Initialize an empty model object from +coder+. +coder+ must contain
|
72
|
+
# the attributes necessary for initializing an empty model object. For
|
73
|
+
# example:
|
74
|
+
#
|
75
|
+
# This should be used after finding a record from the database, as it will
|
76
|
+
# trust the coder's attributes and not load it with default values.
|
77
|
+
#
|
78
|
+
# class Person < MassiveRecord::ORM::Table
|
79
|
+
# end
|
80
|
+
#
|
81
|
+
# person = Person.allocate
|
82
|
+
# person.init_with('attributes' => { 'name' => 'Alice' })
|
83
|
+
# person.name # => 'Alice'
|
84
|
+
def init_with(coder)
|
85
|
+
@new_record = false
|
86
|
+
@destroyed = false
|
87
|
+
|
88
|
+
self.attributes_raw = coder['attributes']
|
89
|
+
|
90
|
+
_run_find_callbacks
|
91
|
+
_run_initialize_callbacks
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
def ==(other)
|
96
|
+
other.equal?(self) || other.instance_of?(self.class) && id == other.id
|
97
|
+
end
|
98
|
+
alias_method :eql?, :==
|
99
|
+
|
100
|
+
|
101
|
+
def freeze
|
102
|
+
@attributes.freeze
|
103
|
+
end
|
104
|
+
|
105
|
+
def frozen?
|
106
|
+
@attributes.frozen?
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
|
111
|
+
def inspect
|
112
|
+
attributes_as_string = self.class.known_attribute_names.collect do |attr_name|
|
113
|
+
"#{attr_name}: #{attribute_for_inspect(attr_name)}"
|
114
|
+
end.join(', ')
|
115
|
+
|
116
|
+
"#<#{self.class} id: #{id.inspect}, #{attributes_as_string}>"
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
def attribute_for_inspect(attr_name)
|
121
|
+
value = read_attribute(attr_name)
|
122
|
+
|
123
|
+
if value.is_a?(String) && value.length > 50
|
124
|
+
"#{value[0..50]}...".inspect
|
125
|
+
elsif value.is_a?(Date) || value.is_a?(Time)
|
126
|
+
%("#{value.to_s}")
|
127
|
+
else
|
128
|
+
value.inspect
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def id
|
133
|
+
if read_attribute(:id).blank? && respond_to?(:default_id, true)
|
134
|
+
@attributes["id"] = default_id
|
135
|
+
end
|
136
|
+
|
137
|
+
read_attribute(:id)
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
|
142
|
+
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
def next_id
|
147
|
+
IdFactory.next_for(self.class).to_s
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
|
152
|
+
Base.class_eval do
|
153
|
+
include Config
|
154
|
+
include Persistence
|
155
|
+
include Finders
|
156
|
+
include ActiveModel::Translation
|
157
|
+
include AttributeMethods
|
158
|
+
include AttributeMethods::Write, AttributeMethods::Read
|
159
|
+
include AttributeMethods::Dirty
|
160
|
+
include Validations
|
161
|
+
include Callbacks
|
162
|
+
|
163
|
+
|
164
|
+
alias [] read_attribute
|
165
|
+
alias []= write_attribute
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
|
171
|
+
|
172
|
+
|
173
|
+
|
174
|
+
require 'massive_record/orm/table'
|
175
|
+
require 'massive_record/orm/column'
|
176
|
+
require 'massive_record/orm/id_factory'
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module MassiveRecord
|
2
|
+
module ORM
|
3
|
+
module Callbacks
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
CALLBACKS = [
|
7
|
+
:after_initialize, :after_find, :after_touch,
|
8
|
+
:before_validation, :after_validation,
|
9
|
+
:before_save, :around_save, :after_save,
|
10
|
+
:before_create, :around_create, :after_create,
|
11
|
+
:before_update, :around_update, :after_update,
|
12
|
+
:before_destroy, :around_destroy, :after_destroy
|
13
|
+
]
|
14
|
+
|
15
|
+
included do
|
16
|
+
extend ActiveModel::Callbacks
|
17
|
+
include ActiveModel::Validations::Callbacks
|
18
|
+
|
19
|
+
define_model_callbacks :initialize, :find, :touch, :only => :after
|
20
|
+
define_model_callbacks :save, :create, :update, :destroy
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
def destroy
|
26
|
+
_run_destroy_callbacks { super }
|
27
|
+
end
|
28
|
+
|
29
|
+
def touch(*)
|
30
|
+
_run_touch_callbacks { super }
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
|
39
|
+
def create_or_update
|
40
|
+
_run_save_callbacks { super }
|
41
|
+
end
|
42
|
+
|
43
|
+
def create
|
44
|
+
_run_create_callbacks { super }
|
45
|
+
end
|
46
|
+
|
47
|
+
def update
|
48
|
+
_run_update_callbacks { super }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'massive_record/orm/schema/column_interface'
|
2
|
+
|
3
|
+
module MassiveRecord
|
4
|
+
module ORM
|
5
|
+
class Column < Base
|
6
|
+
include Schema::ColumnInterface
|
7
|
+
|
8
|
+
# TODO Column does not support these kind of methods
|
9
|
+
class << self
|
10
|
+
undef_method :first, :last, :all, :exists?, :destroy_all
|
11
|
+
end
|
12
|
+
|
13
|
+
undef_method :create, :reload, :save, :save!, :update_attribute, :update_attributes,
|
14
|
+
:update_attributes!, :touch, :destroy, :increment!, :atomic_increment!,
|
15
|
+
:decrement!, :delete
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module MassiveRecord
|
2
|
+
module ORM
|
3
|
+
module Config
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
cattr_accessor :connection_configuration, :instance_writer => false
|
8
|
+
@@connection_configuration = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
extend ActiveSupport::Memoizable
|
13
|
+
@@connection = nil
|
14
|
+
|
15
|
+
def connection
|
16
|
+
if @@connection.blank?
|
17
|
+
@@connection = if !connection_configuration.empty?
|
18
|
+
MassiveRecord::Wrapper::Connection.new(connection_configuration)
|
19
|
+
elsif defined? Rails
|
20
|
+
MassiveRecord::Wrapper::Base.connection
|
21
|
+
else
|
22
|
+
raise ConnectionConfigurationMissing
|
23
|
+
end
|
24
|
+
|
25
|
+
@@connection.open
|
26
|
+
end
|
27
|
+
@@connection
|
28
|
+
end
|
29
|
+
|
30
|
+
def reset_connection!
|
31
|
+
@@connection = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
def table
|
36
|
+
MassiveRecord::Wrapper::Table.new(connection, table_name)
|
37
|
+
end
|
38
|
+
memoize :table
|
39
|
+
|
40
|
+
|
41
|
+
def table_exists?
|
42
|
+
connection.tables.include?(table_name)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module MassiveRecord
|
2
|
+
module ORM
|
3
|
+
|
4
|
+
# Generic error / exception class
|
5
|
+
class MassiveRecordError < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
|
9
|
+
# Railsed by save! and create! if the record passes
|
10
|
+
# validation, but for some reason was not saved.
|
11
|
+
class RecordNotSaved < MassiveRecordError
|
12
|
+
end
|
13
|
+
|
14
|
+
# Raised if a conncetion was being accessed, but no
|
15
|
+
# configuration was set to tell us how to connect.
|
16
|
+
class ConnectionConfigurationMissing < MassiveRecordError
|
17
|
+
end
|
18
|
+
|
19
|
+
# Raised on find(id) when id does not exist.
|
20
|
+
class RecordNotFound < MassiveRecordError
|
21
|
+
end
|
22
|
+
|
23
|
+
# Raised if an attribute is unkown
|
24
|
+
class UnkownAttributeError < MassiveRecordError
|
25
|
+
end
|
26
|
+
|
27
|
+
# Raised if id is missing when you try a save
|
28
|
+
# TODO It might be that we some time later will offer a kind of
|
29
|
+
# auto increment key functionality, and then this should only
|
30
|
+
# be raised if that is disabled.
|
31
|
+
class IdMissing < MassiveRecordError
|
32
|
+
end
|
33
|
+
|
34
|
+
class ColumnFamiliesMissingError < MassiveRecordError
|
35
|
+
attr_reader :missing_column_families
|
36
|
+
def initialize(missing_column_families)
|
37
|
+
@missing_column_families = missing_column_families
|
38
|
+
super("hbase are missing some column families: #{@missing_column_families.join(' ')}. Please migrate the database.")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Raised when trying to manipulate non-numeric fields by operations
|
43
|
+
# requiring a number to work on.
|
44
|
+
class NotNumericalFieldError < MassiveRecordError
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|