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