massive_record 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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