massive_record 0.1.1 → 0.2.0.beta

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