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.
Files changed (81) hide show
  1. data/.autotest +15 -0
  2. data/.gitignore +6 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +4 -0
  5. data/Gemfile.lock +38 -0
  6. data/Manifest +24 -0
  7. data/README.md +225 -0
  8. data/Rakefile +16 -0
  9. data/TODO.md +8 -0
  10. data/autotest/discover.rb +1 -0
  11. data/lib/massive_record.rb +18 -0
  12. data/lib/massive_record/exceptions.rb +11 -0
  13. data/lib/massive_record/orm/attribute_methods.rb +61 -0
  14. data/lib/massive_record/orm/attribute_methods/dirty.rb +80 -0
  15. data/lib/massive_record/orm/attribute_methods/read.rb +23 -0
  16. data/lib/massive_record/orm/attribute_methods/write.rb +24 -0
  17. data/lib/massive_record/orm/base.rb +176 -0
  18. data/lib/massive_record/orm/callbacks.rb +52 -0
  19. data/lib/massive_record/orm/column.rb +18 -0
  20. data/lib/massive_record/orm/config.rb +47 -0
  21. data/lib/massive_record/orm/errors.rb +47 -0
  22. data/lib/massive_record/orm/finders.rb +125 -0
  23. data/lib/massive_record/orm/id_factory.rb +133 -0
  24. data/lib/massive_record/orm/persistence.rb +199 -0
  25. data/lib/massive_record/orm/schema.rb +4 -0
  26. data/lib/massive_record/orm/schema/column_families.rb +48 -0
  27. data/lib/massive_record/orm/schema/column_family.rb +102 -0
  28. data/lib/massive_record/orm/schema/column_interface.rb +91 -0
  29. data/lib/massive_record/orm/schema/common_interface.rb +48 -0
  30. data/lib/massive_record/orm/schema/field.rb +128 -0
  31. data/lib/massive_record/orm/schema/fields.rb +37 -0
  32. data/lib/massive_record/orm/schema/table_interface.rb +96 -0
  33. data/lib/massive_record/orm/table.rb +9 -0
  34. data/lib/massive_record/orm/validations.rb +52 -0
  35. data/lib/massive_record/spec/support/simple_database_cleaner.rb +52 -0
  36. data/lib/massive_record/thrift/hbase.rb +2307 -0
  37. data/lib/massive_record/thrift/hbase_constants.rb +14 -0
  38. data/lib/massive_record/thrift/hbase_types.rb +225 -0
  39. data/lib/massive_record/version.rb +3 -0
  40. data/lib/massive_record/wrapper/base.rb +28 -0
  41. data/lib/massive_record/wrapper/cell.rb +45 -0
  42. data/lib/massive_record/wrapper/column_families_collection.rb +19 -0
  43. data/lib/massive_record/wrapper/column_family.rb +22 -0
  44. data/lib/massive_record/wrapper/connection.rb +71 -0
  45. data/lib/massive_record/wrapper/row.rb +170 -0
  46. data/lib/massive_record/wrapper/scanner.rb +50 -0
  47. data/lib/massive_record/wrapper/table.rb +148 -0
  48. data/lib/massive_record/wrapper/tables_collection.rb +13 -0
  49. data/massive_record.gemspec +28 -0
  50. data/spec/config.yml.example +4 -0
  51. data/spec/orm/cases/attribute_methods_spec.rb +47 -0
  52. data/spec/orm/cases/auto_generate_id_spec.rb +54 -0
  53. data/spec/orm/cases/base_spec.rb +176 -0
  54. data/spec/orm/cases/callbacks_spec.rb +309 -0
  55. data/spec/orm/cases/column_spec.rb +49 -0
  56. data/spec/orm/cases/config_spec.rb +103 -0
  57. data/spec/orm/cases/dirty_spec.rb +129 -0
  58. data/spec/orm/cases/encoding_spec.rb +49 -0
  59. data/spec/orm/cases/finders_spec.rb +208 -0
  60. data/spec/orm/cases/hbase/connection_spec.rb +13 -0
  61. data/spec/orm/cases/i18n_spec.rb +32 -0
  62. data/spec/orm/cases/id_factory_spec.rb +75 -0
  63. data/spec/orm/cases/persistence_spec.rb +479 -0
  64. data/spec/orm/cases/table_spec.rb +81 -0
  65. data/spec/orm/cases/validation_spec.rb +92 -0
  66. data/spec/orm/models/address.rb +7 -0
  67. data/spec/orm/models/person.rb +15 -0
  68. data/spec/orm/models/test_class.rb +5 -0
  69. data/spec/orm/schema/column_families_spec.rb +186 -0
  70. data/spec/orm/schema/column_family_spec.rb +131 -0
  71. data/spec/orm/schema/column_interface_spec.rb +115 -0
  72. data/spec/orm/schema/field_spec.rb +196 -0
  73. data/spec/orm/schema/fields_spec.rb +126 -0
  74. data/spec/orm/schema/table_interface_spec.rb +171 -0
  75. data/spec/spec_helper.rb +15 -0
  76. data/spec/support/connection_helpers.rb +76 -0
  77. data/spec/support/mock_massive_record_connection.rb +80 -0
  78. data/spec/thrift/cases/encoding_spec.rb +48 -0
  79. data/spec/wrapper/cases/connection_spec.rb +53 -0
  80. data/spec/wrapper/cases/table_spec.rb +231 -0
  81. metadata +228 -0
@@ -0,0 +1,4 @@
1
+ require 'massive_record/orm/schema/column_families'
2
+ require 'massive_record/orm/schema/column_family'
3
+ require 'massive_record/orm/schema/fields'
4
+ require 'massive_record/orm/schema/field'
@@ -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