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