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,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
|