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,48 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module MassiveRecord
|
4
|
+
module ORM
|
5
|
+
module Schema
|
6
|
+
class InvalidColumnFamily < ArgumentError; end
|
7
|
+
|
8
|
+
class ColumnFamilies < Set
|
9
|
+
def add(family)
|
10
|
+
family.column_families = self
|
11
|
+
raise InvalidColumnFamily.new(family.errors.full_messages.join(". ")) unless family.valid?
|
12
|
+
super
|
13
|
+
end
|
14
|
+
alias_method :<<, :add
|
15
|
+
|
16
|
+
|
17
|
+
def family_by_name(name)
|
18
|
+
detect { |family| family.name == name.to_s }
|
19
|
+
end
|
20
|
+
|
21
|
+
def family_by_name_or_new(name)
|
22
|
+
unless known_family = family_by_name(name)
|
23
|
+
known_family = ColumnFamily.new(:name => name)
|
24
|
+
add(known_family)
|
25
|
+
end
|
26
|
+
known_family
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
def to_hash
|
31
|
+
inject({}) do |hash, column_family|
|
32
|
+
hash.update(column_family.to_hash)
|
33
|
+
hash
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def attribute_names
|
38
|
+
to_hash.keys
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
def attribute_name_taken?(name)
|
43
|
+
attribute_names.include? name.to_s
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module MassiveRecord
|
2
|
+
module ORM
|
3
|
+
module Schema
|
4
|
+
class ColumnFamily
|
5
|
+
include ActiveModel::Validations
|
6
|
+
|
7
|
+
attr_accessor :column_families, :autoload_fields
|
8
|
+
attr_reader :name, :fields
|
9
|
+
|
10
|
+
|
11
|
+
validates_presence_of :name
|
12
|
+
validate do
|
13
|
+
errors.add(:column_families, :blank) if column_families.nil?
|
14
|
+
errors.add(:base, :invalid_fields) unless fields.all? { |field| field.valid? }
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
delegate :add, :add?, :<<, :to_hash, :attribute_names, :field_by_name, :to => :fields
|
19
|
+
|
20
|
+
|
21
|
+
def initialize(*args)
|
22
|
+
options = args.extract_options!
|
23
|
+
options.symbolize_keys!
|
24
|
+
|
25
|
+
@fields = Fields.new
|
26
|
+
@fields.contained_in = self
|
27
|
+
|
28
|
+
if options.has_key? :autoload
|
29
|
+
# TODO remove this for next version
|
30
|
+
ActiveSupport::Deprecation.warn("autoload is deprecated as an intitializer option. Please use autoload_fields instead!")
|
31
|
+
options[:autoload_fields] = options.delete :autoload
|
32
|
+
end
|
33
|
+
|
34
|
+
self.name = options[:name]
|
35
|
+
self.column_families = options[:column_families]
|
36
|
+
self.autoload_fields = options[:autoload_fields]
|
37
|
+
end
|
38
|
+
|
39
|
+
def ==(other)
|
40
|
+
other.instance_of?(self.class) && other.hash == hash
|
41
|
+
end
|
42
|
+
alias_method :eql?, :==
|
43
|
+
|
44
|
+
def hash
|
45
|
+
name.hash
|
46
|
+
end
|
47
|
+
|
48
|
+
def contained_in=(column_families)
|
49
|
+
self.column_families = column_families
|
50
|
+
end
|
51
|
+
|
52
|
+
def contained_in
|
53
|
+
column_families
|
54
|
+
end
|
55
|
+
|
56
|
+
def attribute_name_taken?(name, check_only_self = false)
|
57
|
+
name = name.to_s
|
58
|
+
check_only_self || contained_in.nil? ? fields.attribute_name_taken?(name, true) : contained_in.attribute_name_taken?(name)
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
|
63
|
+
def autoload_fields?
|
64
|
+
@autoload_fields == true
|
65
|
+
end
|
66
|
+
|
67
|
+
def autoload?
|
68
|
+
# TODO remove this method for next version
|
69
|
+
ActiveSupport::Deprecation.warn("ColumnFamily#autoload? is deprecated. Please use autoload_fields? instead")
|
70
|
+
autoload_fields?
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def name=(name)
|
77
|
+
@name = name.to_s
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
|
82
|
+
|
83
|
+
# Internal DSL method
|
84
|
+
def field(*args)
|
85
|
+
self << Field.new_with_arguments_from_dsl(*args)
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
# Internal DSL method
|
90
|
+
def autoload_fields
|
91
|
+
@autoload_fields = true
|
92
|
+
end
|
93
|
+
|
94
|
+
def autoload
|
95
|
+
# TODO remove this method for next version
|
96
|
+
ActiveSupport::Deprecation.warn("ColumnFamily#autoload DSL call is deprecated. Please use autoload_fields instead")
|
97
|
+
autoload
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'massive_record/orm/schema/common_interface'
|
2
|
+
|
3
|
+
module MassiveRecord
|
4
|
+
module ORM
|
5
|
+
module Schema
|
6
|
+
module ColumnInterface
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
include CommonInterface
|
11
|
+
|
12
|
+
class_attribute :fields, :instance_writer => false
|
13
|
+
self.fields = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
#
|
18
|
+
# DSL method exposed into class. Makes it possible to do:
|
19
|
+
#
|
20
|
+
# class Person < MassiveRecord::ORM::Column
|
21
|
+
# field :name
|
22
|
+
# field :age, :integer, :default => 0
|
23
|
+
# field :points, :integer, :column => :number_of_points
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
#
|
27
|
+
def field(*args)
|
28
|
+
ensure_fields_exists
|
29
|
+
fields << Field.new_with_arguments_from_dsl(*args)
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# If you need to add fields dynamically, use this method.
|
34
|
+
# It wraps functionality needed to keep the class in a consistent state.
|
35
|
+
# There is also an instance method defined which will inject default value
|
36
|
+
# to the object itself after defining the field.
|
37
|
+
#
|
38
|
+
def add_field(*args)
|
39
|
+
ensure_fields_exists
|
40
|
+
|
41
|
+
field = Field.new_with_arguments_from_dsl(*args)
|
42
|
+
fields << field
|
43
|
+
|
44
|
+
undefine_attribute_methods if respond_to? :undefine_attribute_methods
|
45
|
+
|
46
|
+
field
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
|
51
|
+
|
52
|
+
private
|
53
|
+
#
|
54
|
+
# Entrypoint for the CommonInterface
|
55
|
+
#
|
56
|
+
def schema_source
|
57
|
+
fields
|
58
|
+
end
|
59
|
+
|
60
|
+
def ensure_fields_exists
|
61
|
+
self.fields = Fields.new if fields.nil?
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
#
|
66
|
+
# Same as defined in class method, but also sets the default value
|
67
|
+
# in current object it was called from.
|
68
|
+
#
|
69
|
+
def add_field(*args)
|
70
|
+
new_field = self.class.add_field(*args)
|
71
|
+
method = "#{new_field.name}="
|
72
|
+
send(method, new_field.default) if respond_to? method
|
73
|
+
end
|
74
|
+
|
75
|
+
#
|
76
|
+
# TODO : Need to be cleaned up after we implement the has_many method
|
77
|
+
#
|
78
|
+
def attributes_to_row_values_hash(only_attr_names = [])
|
79
|
+
values = Hash.new
|
80
|
+
|
81
|
+
attributes_schema.each do |attr_name, orm_field|
|
82
|
+
next unless only_attr_names.empty? || only_attr_names.include?(attr_name)
|
83
|
+
values[orm_field.column] = send(attr_name)
|
84
|
+
end
|
85
|
+
|
86
|
+
values
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module MassiveRecord
|
2
|
+
module ORM
|
3
|
+
module Schema
|
4
|
+
#
|
5
|
+
# Common methods both shared with table interface and field interface.
|
6
|
+
# Methods are to be included as ClassMethods, and where they are to be
|
7
|
+
# included must provide a schema_source(). Currently it is expected to
|
8
|
+
# be a set of Schema::ColumnFamilies or a set of Schema::Fields, but
|
9
|
+
# I guess as long as it responds to to_hash and attribute_names you are fine.
|
10
|
+
#
|
11
|
+
module CommonInterface
|
12
|
+
extend ActiveSupport::Concern
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
#
|
16
|
+
# Returns a hash where attribute names are keys and it's field
|
17
|
+
# is the value.
|
18
|
+
#
|
19
|
+
def attributes_schema
|
20
|
+
schema_source.present? ? schema_source.to_hash : {}
|
21
|
+
end
|
22
|
+
|
23
|
+
#
|
24
|
+
# Returns an array of known attributes based on all fields found
|
25
|
+
# in schema source
|
26
|
+
#
|
27
|
+
def known_attribute_names
|
28
|
+
schema_source.present? ? schema_source.attribute_names : []
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
#
|
33
|
+
# Returns a hash with attribute name as keys, default values read from field as value.
|
34
|
+
#
|
35
|
+
def default_attributes_from_schema
|
36
|
+
Hash[attributes_schema.collect { |attribute_name, field|
|
37
|
+
[attribute_name, field.default]
|
38
|
+
}]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def attributes_schema
|
43
|
+
self.class.attributes_schema
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module MassiveRecord
|
2
|
+
module ORM
|
3
|
+
module Schema
|
4
|
+
class Field
|
5
|
+
include ActiveModel::Validations
|
6
|
+
|
7
|
+
TYPES = [:string, :integer, :float, :boolean, :array, :hash, :date, :time]
|
8
|
+
|
9
|
+
attr_writer :default
|
10
|
+
attr_accessor :name, :column, :type, :fields
|
11
|
+
|
12
|
+
|
13
|
+
validates_presence_of :name
|
14
|
+
validates_inclusion_of :type, :in => TYPES
|
15
|
+
validate do
|
16
|
+
errors.add(:fields, :blank) if fields.nil?
|
17
|
+
errors.add(:name, :taken) if fields.try(:attribute_name_taken?, name)
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
#
|
22
|
+
# Creates a new field based on arguments from DSL
|
23
|
+
# args: name, type, options
|
24
|
+
#
|
25
|
+
def self.new_with_arguments_from_dsl(*args)
|
26
|
+
field_options = args.extract_options!
|
27
|
+
field_options[:name] = args[0]
|
28
|
+
field_options[:type] = args[1]
|
29
|
+
|
30
|
+
new(field_options)
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
|
35
|
+
def initialize(*args)
|
36
|
+
options = args.extract_options!.to_options
|
37
|
+
|
38
|
+
self.fields = options[:fields]
|
39
|
+
self.name = options[:name]
|
40
|
+
self.column = options[:column]
|
41
|
+
self.type = options[:type] || :string
|
42
|
+
self.default = options[:default]
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
def ==(other)
|
47
|
+
other.instance_of?(self.class) && other.hash == hash
|
48
|
+
end
|
49
|
+
alias_method :eql?, :==
|
50
|
+
|
51
|
+
def hash
|
52
|
+
name.hash
|
53
|
+
end
|
54
|
+
|
55
|
+
def type=(type)
|
56
|
+
@type = type.to_sym
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
def column
|
61
|
+
@column || name
|
62
|
+
end
|
63
|
+
|
64
|
+
def default
|
65
|
+
@default.duplicable? ? @default.dup : @default
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
def unique_name
|
70
|
+
raise "Can't generate a unique name as I don't have a column family!" if column_family.nil?
|
71
|
+
[column_family.name, column].join(":")
|
72
|
+
end
|
73
|
+
|
74
|
+
def column_family
|
75
|
+
fields.try :contained_in
|
76
|
+
end
|
77
|
+
|
78
|
+
def column=(column)
|
79
|
+
column = column.to_s unless column.nil?
|
80
|
+
@column = column
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
|
85
|
+
|
86
|
+
def decode(value)
|
87
|
+
return nil if value.nil?
|
88
|
+
|
89
|
+
if type == :boolean
|
90
|
+
return value if value === TrueClass || value === FalseClass
|
91
|
+
else
|
92
|
+
return value if value.class == type.to_s.classify.constantize
|
93
|
+
end
|
94
|
+
|
95
|
+
case type
|
96
|
+
when :string
|
97
|
+
value
|
98
|
+
when :boolean
|
99
|
+
value.to_s.empty? ? nil : !value.to_s.match(/^(true|1)$/i).nil?
|
100
|
+
when :integer
|
101
|
+
value.to_s.empty? ? nil : value.to_i
|
102
|
+
when :float
|
103
|
+
value.to_s.empty? ? nil : value.to_f
|
104
|
+
when :date
|
105
|
+
# TODO : find a nicer way to do that
|
106
|
+
value.empty? || value.to_s == "0" ? nil : Date.parse(value)
|
107
|
+
when :time
|
108
|
+
value.empty? ? nil : Time.parse(value)
|
109
|
+
when :array
|
110
|
+
value
|
111
|
+
when :hash
|
112
|
+
value
|
113
|
+
else
|
114
|
+
value
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
def name=(name)
|
123
|
+
@name = name.to_s
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module MassiveRecord
|
2
|
+
module ORM
|
3
|
+
module Schema
|
4
|
+
class InvalidField < ArgumentError; end
|
5
|
+
|
6
|
+
class Fields < Set
|
7
|
+
attr_accessor :contained_in
|
8
|
+
|
9
|
+
def add(field)
|
10
|
+
field.fields = self
|
11
|
+
raise InvalidField.new(field.errors.full_messages.join(". ")) unless field.valid?
|
12
|
+
super
|
13
|
+
end
|
14
|
+
alias_method :<<, :add
|
15
|
+
|
16
|
+
def field_by_name(name)
|
17
|
+
name = name.to_s
|
18
|
+
detect { |field| field.name == name }
|
19
|
+
end
|
20
|
+
|
21
|
+
def attribute_name_taken?(name, check_only_self = false)
|
22
|
+
name = name.to_s
|
23
|
+
check_only_self || contained_in.nil? ? attribute_names.include?(name) : contained_in.attribute_name_taken?(name)
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
def to_hash
|
28
|
+
Hash[collect { |field| [field.name, field] }]
|
29
|
+
end
|
30
|
+
|
31
|
+
def attribute_names
|
32
|
+
to_hash.keys
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|