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.
- data/CHANGELOG.md +28 -5
- data/Gemfile.lock +12 -12
- data/README.md +29 -1
- data/lib/massive_record/adapters/initialize.rb +18 -0
- data/lib/massive_record/adapters/thrift/adapter.rb +25 -0
- data/lib/massive_record/adapters/thrift/column_family.rb +24 -0
- data/lib/massive_record/adapters/thrift/connection.rb +73 -0
- data/lib/massive_record/{thrift → adapters/thrift/hbase}/hbase.rb +0 -0
- data/lib/massive_record/{thrift → adapters/thrift/hbase}/hbase_constants.rb +0 -0
- data/lib/massive_record/{thrift → adapters/thrift/hbase}/hbase_types.rb +0 -0
- data/lib/massive_record/adapters/thrift/row.rb +150 -0
- data/lib/massive_record/adapters/thrift/scanner.rb +59 -0
- data/lib/massive_record/adapters/thrift/table.rb +169 -0
- data/lib/massive_record/orm/attribute_methods/read.rb +2 -1
- data/lib/massive_record/orm/base.rb +61 -3
- data/lib/massive_record/orm/coders/chained.rb +71 -0
- data/lib/massive_record/orm/coders/json.rb +17 -0
- data/lib/massive_record/orm/coders/yaml.rb +15 -0
- data/lib/massive_record/orm/coders.rb +3 -0
- data/lib/massive_record/orm/errors.rb +15 -2
- data/lib/massive_record/orm/finders/scope.rb +166 -0
- data/lib/massive_record/orm/finders.rb +45 -24
- data/lib/massive_record/orm/persistence.rb +4 -4
- data/lib/massive_record/orm/relations/interface.rb +170 -0
- data/lib/massive_record/orm/relations/metadata.rb +150 -0
- data/lib/massive_record/orm/relations/proxy/references_many.rb +229 -0
- data/lib/massive_record/orm/relations/proxy/references_one.rb +40 -0
- data/lib/massive_record/orm/relations/proxy/references_one_polymorphic.rb +49 -0
- data/lib/massive_record/orm/relations/proxy.rb +174 -0
- data/lib/massive_record/orm/relations.rb +6 -0
- data/lib/massive_record/orm/schema/column_interface.rb +1 -1
- data/lib/massive_record/orm/schema/field.rb +62 -27
- data/lib/massive_record/orm/single_table_inheritance.rb +21 -0
- data/lib/massive_record/version.rb +1 -1
- data/lib/massive_record/wrapper/adapter.rb +6 -0
- data/lib/massive_record/wrapper/base.rb +6 -7
- data/lib/massive_record/wrapper/cell.rb +9 -32
- data/lib/massive_record/wrapper/column_families_collection.rb +2 -2
- data/lib/massive_record/wrapper/errors.rb +10 -0
- data/lib/massive_record/wrapper/tables_collection.rb +1 -1
- data/lib/massive_record.rb +5 -12
- data/spec/orm/cases/attribute_methods_spec.rb +5 -1
- data/spec/orm/cases/base_spec.rb +77 -4
- data/spec/orm/cases/column_spec.rb +1 -1
- data/spec/orm/cases/finder_default_scope.rb +53 -0
- data/spec/orm/cases/finder_scope_spec.rb +288 -0
- data/spec/orm/cases/finders_spec.rb +56 -13
- data/spec/orm/cases/persistence_spec.rb +20 -5
- data/spec/orm/cases/single_table_inheritance_spec.rb +26 -0
- data/spec/orm/cases/table_spec.rb +1 -1
- data/spec/orm/cases/timestamps_spec.rb +16 -16
- data/spec/orm/coders/chained_spec.rb +73 -0
- data/spec/orm/coders/json_spec.rb +6 -0
- data/spec/orm/coders/yaml_spec.rb +6 -0
- data/spec/orm/models/best_friend.rb +7 -0
- data/spec/orm/models/friend.rb +4 -0
- data/spec/orm/models/person.rb +20 -6
- data/spec/orm/models/{person_with_timestamps.rb → person_with_timestamp.rb} +1 -1
- data/spec/orm/models/test_class.rb +3 -0
- data/spec/orm/relations/interface_spec.rb +207 -0
- data/spec/orm/relations/metadata_spec.rb +202 -0
- data/spec/orm/relations/proxy/references_many_spec.rb +624 -0
- data/spec/orm/relations/proxy/references_one_polymorphic_spec.rb +106 -0
- data/spec/orm/relations/proxy/references_one_spec.rb +111 -0
- data/spec/orm/relations/proxy_spec.rb +13 -0
- data/spec/orm/schema/field_spec.rb +101 -2
- data/spec/shared/orm/coders/an_orm_coder.rb +14 -0
- data/spec/shared/orm/relations/proxy.rb +154 -0
- data/spec/shared/orm/relations/singular_proxy.rb +68 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/thrift/cases/encoding_spec.rb +28 -7
- data/spec/wrapper/cases/adapter_spec.rb +9 -0
- data/spec/wrapper/cases/connection_spec.rb +13 -10
- data/spec/wrapper/cases/table_spec.rb +85 -85
- metadata +74 -22
- data/TODO.md +0 -8
- data/lib/massive_record/exceptions.rb +0 -11
- data/lib/massive_record/wrapper/column_family.rb +0 -22
- data/lib/massive_record/wrapper/connection.rb +0 -71
- data/lib/massive_record/wrapper/row.rb +0 -173
- data/lib/massive_record/wrapper/scanner.rb +0 -61
- data/lib/massive_record/wrapper/table.rb +0 -149
- 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]
|
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
|
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
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
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,12 +1,11 @@
|
|
1
1
|
require 'yaml'
|
2
|
-
require '
|
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
|
-
|
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
|
-
|
21
|
-
|
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
|
29
|
-
|
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
|
33
|
-
|
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?(
|
8
|
+
if column_family.is_a?(ADAPTER::ColumnFamily)
|
9
9
|
self.push(column_family)
|
10
10
|
else
|
11
|
-
self.push(
|
11
|
+
self.push(ADAPTER::ColumnFamily.new(column_family, opts))
|
12
12
|
end
|
13
13
|
|
14
14
|
true
|