massive_record 0.1.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.
- 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
|