massive_record 0.1.1 → 0.2.0.beta

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 (83) hide show
  1. data/CHANGELOG.md +28 -5
  2. data/Gemfile.lock +12 -12
  3. data/README.md +29 -1
  4. data/lib/massive_record/adapters/initialize.rb +18 -0
  5. data/lib/massive_record/adapters/thrift/adapter.rb +25 -0
  6. data/lib/massive_record/adapters/thrift/column_family.rb +24 -0
  7. data/lib/massive_record/adapters/thrift/connection.rb +73 -0
  8. data/lib/massive_record/{thrift → adapters/thrift/hbase}/hbase.rb +0 -0
  9. data/lib/massive_record/{thrift → adapters/thrift/hbase}/hbase_constants.rb +0 -0
  10. data/lib/massive_record/{thrift → adapters/thrift/hbase}/hbase_types.rb +0 -0
  11. data/lib/massive_record/adapters/thrift/row.rb +150 -0
  12. data/lib/massive_record/adapters/thrift/scanner.rb +59 -0
  13. data/lib/massive_record/adapters/thrift/table.rb +169 -0
  14. data/lib/massive_record/orm/attribute_methods/read.rb +2 -1
  15. data/lib/massive_record/orm/base.rb +61 -3
  16. data/lib/massive_record/orm/coders/chained.rb +71 -0
  17. data/lib/massive_record/orm/coders/json.rb +17 -0
  18. data/lib/massive_record/orm/coders/yaml.rb +15 -0
  19. data/lib/massive_record/orm/coders.rb +3 -0
  20. data/lib/massive_record/orm/errors.rb +15 -2
  21. data/lib/massive_record/orm/finders/scope.rb +166 -0
  22. data/lib/massive_record/orm/finders.rb +45 -24
  23. data/lib/massive_record/orm/persistence.rb +4 -4
  24. data/lib/massive_record/orm/relations/interface.rb +170 -0
  25. data/lib/massive_record/orm/relations/metadata.rb +150 -0
  26. data/lib/massive_record/orm/relations/proxy/references_many.rb +229 -0
  27. data/lib/massive_record/orm/relations/proxy/references_one.rb +40 -0
  28. data/lib/massive_record/orm/relations/proxy/references_one_polymorphic.rb +49 -0
  29. data/lib/massive_record/orm/relations/proxy.rb +174 -0
  30. data/lib/massive_record/orm/relations.rb +6 -0
  31. data/lib/massive_record/orm/schema/column_interface.rb +1 -1
  32. data/lib/massive_record/orm/schema/field.rb +62 -27
  33. data/lib/massive_record/orm/single_table_inheritance.rb +21 -0
  34. data/lib/massive_record/version.rb +1 -1
  35. data/lib/massive_record/wrapper/adapter.rb +6 -0
  36. data/lib/massive_record/wrapper/base.rb +6 -7
  37. data/lib/massive_record/wrapper/cell.rb +9 -32
  38. data/lib/massive_record/wrapper/column_families_collection.rb +2 -2
  39. data/lib/massive_record/wrapper/errors.rb +10 -0
  40. data/lib/massive_record/wrapper/tables_collection.rb +1 -1
  41. data/lib/massive_record.rb +5 -12
  42. data/spec/orm/cases/attribute_methods_spec.rb +5 -1
  43. data/spec/orm/cases/base_spec.rb +77 -4
  44. data/spec/orm/cases/column_spec.rb +1 -1
  45. data/spec/orm/cases/finder_default_scope.rb +53 -0
  46. data/spec/orm/cases/finder_scope_spec.rb +288 -0
  47. data/spec/orm/cases/finders_spec.rb +56 -13
  48. data/spec/orm/cases/persistence_spec.rb +20 -5
  49. data/spec/orm/cases/single_table_inheritance_spec.rb +26 -0
  50. data/spec/orm/cases/table_spec.rb +1 -1
  51. data/spec/orm/cases/timestamps_spec.rb +16 -16
  52. data/spec/orm/coders/chained_spec.rb +73 -0
  53. data/spec/orm/coders/json_spec.rb +6 -0
  54. data/spec/orm/coders/yaml_spec.rb +6 -0
  55. data/spec/orm/models/best_friend.rb +7 -0
  56. data/spec/orm/models/friend.rb +4 -0
  57. data/spec/orm/models/person.rb +20 -6
  58. data/spec/orm/models/{person_with_timestamps.rb → person_with_timestamp.rb} +1 -1
  59. data/spec/orm/models/test_class.rb +3 -0
  60. data/spec/orm/relations/interface_spec.rb +207 -0
  61. data/spec/orm/relations/metadata_spec.rb +202 -0
  62. data/spec/orm/relations/proxy/references_many_spec.rb +624 -0
  63. data/spec/orm/relations/proxy/references_one_polymorphic_spec.rb +106 -0
  64. data/spec/orm/relations/proxy/references_one_spec.rb +111 -0
  65. data/spec/orm/relations/proxy_spec.rb +13 -0
  66. data/spec/orm/schema/field_spec.rb +101 -2
  67. data/spec/shared/orm/coders/an_orm_coder.rb +14 -0
  68. data/spec/shared/orm/relations/proxy.rb +154 -0
  69. data/spec/shared/orm/relations/singular_proxy.rb +68 -0
  70. data/spec/spec_helper.rb +1 -0
  71. data/spec/thrift/cases/encoding_spec.rb +28 -7
  72. data/spec/wrapper/cases/adapter_spec.rb +9 -0
  73. data/spec/wrapper/cases/connection_spec.rb +13 -10
  74. data/spec/wrapper/cases/table_spec.rb +85 -85
  75. metadata +74 -22
  76. data/TODO.md +0 -8
  77. data/lib/massive_record/exceptions.rb +0 -11
  78. data/lib/massive_record/wrapper/column_family.rb +0 -22
  79. data/lib/massive_record/wrapper/connection.rb +0 -71
  80. data/lib/massive_record/wrapper/row.rb +0 -173
  81. data/lib/massive_record/wrapper/scanner.rb +0 -61
  82. data/lib/massive_record/wrapper/table.rb +0 -149
  83. data/spec/orm/cases/hbase/connection_spec.rb +0 -13
@@ -0,0 +1,174 @@
1
+ module MassiveRecord
2
+ module ORM
3
+ module Relations
4
+ #
5
+ # Parent class for all proxies sitting between records.
6
+ # It's responsibility is to transparently load and forward
7
+ # method calls to it's proxy_target. Iy may also do some small maintenance
8
+ # work like setting foreign key in proxy_owner object etc. That kind of
9
+ # functionality is likely to be implemented in one of it's more
10
+ # specific sub class proxies.
11
+ #
12
+ class Proxy
13
+ instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id|to_a|to_s|extend|equal\?)$|^__|^respond_to|^should|^instance_variable_/ }
14
+
15
+ attr_reader :proxy_target
16
+ attr_accessor :proxy_owner, :metadata
17
+
18
+ delegate :class_name, :proxy_target_class, :represents_a_collection?, :to => :metadata
19
+
20
+ def initialize(options = {})
21
+ options.to_options!
22
+ self.metadata = options[:metadata]
23
+ self.proxy_owner = options[:proxy_owner]
24
+ self.proxy_target = options[:proxy_target]
25
+
26
+ reset if proxy_target.nil?
27
+ end
28
+
29
+
30
+ #
31
+ # The proxy_target of a relation is the record
32
+ # the proxy_owner references. For instance,
33
+ #
34
+ # class Person
35
+ # references_one :car
36
+ # end
37
+ #
38
+ # The proxy_owner is a record of class person, the proxy_target will be the car.
39
+ #
40
+ def proxy_target=(proxy_target)
41
+ @proxy_target = proxy_target
42
+ loaded! unless @proxy_target.nil?
43
+ end
44
+
45
+ #
46
+ # Returns the proxy_target. Loads it, if it's not there.
47
+ # Returns nil if for some reason proxy_target could not be found.
48
+ #
49
+ def load_proxy_target
50
+ self.proxy_target = find_proxy_target_or_find_with_proc if find_proxy_target?
51
+ proxy_target
52
+ rescue RecordNotFound
53
+ reset
54
+ end
55
+
56
+ def reload
57
+ reset
58
+ load_proxy_target
59
+ end
60
+
61
+ def reset
62
+ @loaded = @proxy_target = nil
63
+ end
64
+
65
+ def replace(proxy_target)
66
+ if proxy_target.nil?
67
+ reset
68
+ else
69
+ raise_if_type_mismatch(proxy_target)
70
+ self.proxy_target = proxy_target
71
+ end
72
+ end
73
+
74
+ def inspect
75
+ load_proxy_target.inspect
76
+ end
77
+
78
+
79
+
80
+ #
81
+ # If the proxy is loaded it has a proxy_target
82
+ #
83
+ def loaded?
84
+ !!@loaded
85
+ end
86
+
87
+ def loaded!
88
+ @loaded = true
89
+ end
90
+
91
+
92
+
93
+
94
+ def respond_to?(*args)
95
+ super || (load_proxy_target && proxy_target.respond_to?(*args))
96
+ end
97
+
98
+ def method_missing(method, *args, &block)
99
+ return proxy_target.send(method, *args, &block) if load_proxy_target && proxy_target.respond_to?(method)
100
+ super
101
+ rescue NoMethodError => e
102
+ raise e, e.message.sub(/ for #<.*$/, " via proxy for #{proxy_target}")
103
+ end
104
+
105
+
106
+ # Strange.. Without Rails, to_param goes through method_missing,
107
+ # With Rails it seems like the proxy answered to to_param, which
108
+ # kinda was not what I wanted.
109
+ def to_param # :nodoc:
110
+ proxy_target.try :to_param
111
+ end
112
+
113
+
114
+ protected
115
+
116
+ def find_proxy_target_or_find_with_proc
117
+ find_with_proc? ? find_proxy_target_with_proc : find_proxy_target
118
+ end
119
+
120
+ #
121
+ # "Abstract" method used to find proxy_target for the proxy.
122
+ # Implement in subclasses. It is not called when the meta
123
+ # data contains a find_with proc; in that case find_proxy_target_with_proc
124
+ # is used instead
125
+ #
126
+ def find_proxy_target
127
+ end
128
+
129
+ #
130
+ # Gives sub classes a place to hook into when we are
131
+ # gonna find proxy_target(s) by the proc. For instance, the
132
+ # references_many proxy ensures that the result of proc
133
+ # is put inside of an array.
134
+ #
135
+ def find_proxy_target_with_proc(options = {})
136
+ metadata.find_with.call(proxy_owner, options)
137
+ end
138
+
139
+ #
140
+ # When are we supposed to find a proxy_target? Find a proxy_target is done
141
+ # through load_proxy_target.
142
+ #
143
+ def find_proxy_target?
144
+ !loaded? && can_find_proxy_target?
145
+ end
146
+
147
+ #
148
+ # Override this to controll when a proxy_target may be found.
149
+ #
150
+ def can_find_proxy_target?
151
+ find_with_proc?
152
+ end
153
+
154
+ def update_foreign_key_fields_in_proxy_owner?
155
+ !proxy_owner.destroyed?
156
+ end
157
+
158
+ #
159
+ # Are we supposed to find proxy_target with a proc?
160
+ #
161
+ def find_with_proc?
162
+ !metadata.find_with.nil? && metadata.find_with.respond_to?(:call)
163
+ end
164
+
165
+ def raise_if_type_mismatch(record)
166
+ unless record.is_a? proxy_target_class
167
+ message = "#{class_name}(##{proxy_target_class.object_id}) expected, got #{record.class}(##{record.class.object_id})"
168
+ raise RelationTypeMismatch.new(message)
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,6 @@
1
+ require 'massive_record/orm/relations/proxy'
2
+ require 'massive_record/orm/relations/proxy/references_one'
3
+ require 'massive_record/orm/relations/proxy/references_one_polymorphic'
4
+ require 'massive_record/orm/relations/proxy/references_many'
5
+ require 'massive_record/orm/relations/metadata'
6
+ require 'massive_record/orm/relations/interface'
@@ -85,7 +85,7 @@ module MassiveRecord
85
85
 
86
86
  attributes_schema.each do |attr_name, orm_field|
87
87
  next unless only_attr_names.empty? || only_attr_names.include?(attr_name)
88
- values[orm_field.column] = send(attr_name)
88
+ values[orm_field.column] = orm_field.encode(send(attr_name))
89
89
  end
90
90
 
91
91
  values
@@ -7,7 +7,7 @@ module MassiveRecord
7
7
  TYPES = [:string, :integer, :float, :boolean, :array, :hash, :date, :time]
8
8
 
9
9
  attr_writer :default
10
- attr_accessor :name, :column, :type, :fields
10
+ attr_accessor :name, :column, :type, :fields, :coder
11
11
 
12
12
 
13
13
  validates_presence_of :name
@@ -25,7 +25,7 @@ module MassiveRecord
25
25
  def self.new_with_arguments_from_dsl(*args)
26
26
  field_options = args.extract_options!
27
27
  field_options[:name] = args[0]
28
- field_options[:type] = args[1]
28
+ field_options[:type] ||= args[1]
29
29
 
30
30
  new(field_options)
31
31
  end
@@ -40,6 +40,11 @@ module MassiveRecord
40
40
  self.column = options[:column]
41
41
  self.type = options[:type] || :string
42
42
  self.default = options[:default]
43
+
44
+ self.coder = options[:coder] || Base.coder
45
+
46
+ @@encoded_nil_value = coder.dump(nil)
47
+ @@encoded_null_string = coder.dump("null")
43
48
  end
44
49
 
45
50
 
@@ -84,34 +89,36 @@ module MassiveRecord
84
89
 
85
90
 
86
91
  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
92
+ return value if value.nil? || value_is_already_decoded?(value)
94
93
 
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
94
+ value = case type
95
+ when :boolean
96
+ value.blank? ? nil : !value.to_s.match(/^(true|1)$/i).nil?
97
+ when :date
98
+ value.blank? || value.to_s == "0" ? nil : (Date.parse(value) rescue nil)
99
+ when :time
100
+ value.blank? ? nil : (Time.parse(value) rescue nil)
101
+ when :string
102
+ if value.present?
103
+ value = value.to_s if value.is_a? Symbol
104
+ coder.load(value)
105
+ end
106
+ when :integer, :float, :array, :hash
107
+ coder.load(value) if value.present?
108
+ else
109
+ raise "Unable to decode #{value}, class: #{value}"
110
+ end
111
+ ensure
112
+ unless loaded_value_is_of_valid_class?(value)
113
+ raise SerializationTypeMismatch.new("Expected #{value} (class: #{value.class}) to be any of: #{classes.join(', ')}.")
114
+ end
115
+ end
116
+
117
+ def encode(value)
118
+ if type == :string && !(value.nil? || value == @@encoded_nil_value)
112
119
  value
113
120
  else
114
- value
121
+ coder.dump(value)
115
122
  end
116
123
  end
117
124
 
@@ -122,6 +129,34 @@ module MassiveRecord
122
129
  def name=(name)
123
130
  @name = name.to_s
124
131
  end
132
+
133
+ def classes
134
+ classes = case type
135
+ when :boolean
136
+ [TrueClass, FalseClass]
137
+ when :integer
138
+ [Fixnum]
139
+ else
140
+ klass = type.to_s.classify
141
+ if ::Object.const_defined?(klass)
142
+ [klass.constantize]
143
+ end
144
+ end
145
+
146
+ classes || []
147
+ end
148
+
149
+ def value_is_already_decoded?(value)
150
+ if type == :string
151
+ value.is_a?(String) && !(value == @@encoded_null_string || value == @@encoded_nil_value)
152
+ else
153
+ classes.include?(value.class)
154
+ end
155
+ end
156
+
157
+ def loaded_value_is_of_valid_class?(value)
158
+ value.nil? || value.is_a?(String) && value == @@encoded_nil_value || value_is_already_decoded?(value)
159
+ end
125
160
  end
126
161
  end
127
162
  end
@@ -0,0 +1,21 @@
1
+ module MassiveRecord
2
+ module ORM
3
+ module SingleTableInheritance
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ after_initialize :ensure_proper_type
8
+
9
+
10
+ end
11
+
12
+ def ensure_proper_type
13
+ attr = self.class.inheritance_attribute
14
+
15
+ if respond_to?(attr) && self[attr].blank? && self.class.base_class != self.class
16
+ self[attr] = self.class.to_s
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,3 +1,3 @@
1
1
  module MassiveRecord
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0.beta"
3
3
  end
@@ -0,0 +1,6 @@
1
+ # Include the specific Adapters classes into the Wrapper
2
+ module MassiveRecord
3
+ module Wrapper
4
+ include ADAPTER
5
+ end
6
+ end
@@ -1,12 +1,11 @@
1
1
  require 'yaml'
2
- require 'massive_record/wrapper/connection'
2
+ require 'json'
3
+
4
+ require 'massive_record/wrapper/adapter'
5
+ require 'massive_record/wrapper/errors'
3
6
  require 'massive_record/wrapper/tables_collection'
4
- require 'massive_record/wrapper/table'
5
- require 'massive_record/wrapper/row'
6
7
  require 'massive_record/wrapper/column_families_collection'
7
- require 'massive_record/wrapper/column_family'
8
8
  require 'massive_record/wrapper/cell'
9
- require 'massive_record/wrapper/scanner'
10
9
 
11
10
  module MassiveRecord
12
11
  module Wrapper
@@ -18,11 +17,11 @@ module MassiveRecord
18
17
  end
19
18
 
20
19
  def self.connection(opts = {})
21
- conn = Connection.new(opts.empty? ? config : opts)
20
+ conn = ADAPTER::Connection.new(opts.empty? ? config : opts)
22
21
  conn.open
23
22
  conn
24
23
  end
25
24
 
26
25
  end
27
26
  end
28
- end
27
+ end
@@ -1,44 +1,21 @@
1
1
  module MassiveRecord
2
2
  module Wrapper
3
3
  class Cell
4
- attr_writer :value
4
+ attr_reader :value
5
5
  attr_accessor :created_at
6
6
 
7
- class << self
8
- def serialize_value(v)
9
- serialize?(v) ? v.to_yaml : v.to_s.force_encoding(Encoding::BINARY)
10
- end
11
-
12
- private
13
-
14
- def serialize?(v)
15
- [Hash, Array, NilClass].include?(v.class)
16
- end
17
- end
18
-
19
7
  def initialize(opts = {})
20
- @value = opts[:value]
21
- @created_at = opts[:created_at]
22
- end
23
-
24
- def value
25
- @value.is_a?(String) ? @value.to_s.force_encoding(Encoding::UTF_8) : @value
8
+ self.value = opts[:value]
9
+ self.created_at = opts[:created_at]
26
10
  end
27
11
 
28
- def deserialize_value
29
- is_yaml? ? YAML.load(@value) : value
12
+ def value=(v)
13
+ raise "#{v} was a #{v.class}, but it must be a String!" unless v.is_a? String
14
+ @value = v.dup.force_encoding(Encoding::UTF_8)
30
15
  end
31
-
32
- def serialize_value(v)
33
- @value = self.class.serialize_value(v)
34
- end
35
-
36
- def serialized_value
37
- self.class.serialize_value(@value)
38
- end
39
-
40
- def is_yaml?
41
- @value =~ /^--- \n/ || @value =~ /^--- {}/ || @value =~ /^--- \[\]/
16
+
17
+ def value_to_thrift
18
+ value.force_encoding(Encoding::BINARY)
42
19
  end
43
20
  end
44
21
  end
@@ -5,10 +5,10 @@ module MassiveRecord
5
5
  attr_accessor :table
6
6
 
7
7
  def create(column_family, opts = {})
8
- if column_family.is_a?(MassiveRecord::Wrapper::ColumnFamily)
8
+ if column_family.is_a?(ADAPTER::ColumnFamily)
9
9
  self.push(column_family)
10
10
  else
11
- self.push(MassiveRecord::Wrapper::ColumnFamily.new(column_family, opts))
11
+ self.push(ADAPTER::ColumnFamily.new(column_family, opts))
12
12
  end
13
13
 
14
14
  true