simply_stored 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/simply_stored/class_methods_base.rb +31 -0
- data/lib/simply_stored/couch/belongs_to.rb +117 -0
- data/lib/simply_stored/couch/ext/couch_potato.rb +16 -0
- data/lib/simply_stored/couch/has_many.rb +148 -0
- data/lib/simply_stored/couch/has_one.rb +93 -0
- data/lib/simply_stored/couch/validations.rb +74 -0
- data/lib/simply_stored/couch/views/array_property_view_spec.rb +22 -0
- data/lib/simply_stored/couch/views.rb +1 -0
- data/lib/simply_stored/couch.rb +278 -0
- data/lib/simply_stored/instance_methods.rb +143 -0
- data/lib/simply_stored/simpledb/associations.rb +196 -0
- data/lib/simply_stored/simpledb/attributes.rb +173 -0
- data/lib/simply_stored/simpledb/storag.rb +85 -0
- data/lib/simply_stored/simpledb/validations.rb +88 -0
- data/lib/simply_stored/simpledb.rb +212 -0
- data/lib/simply_stored/storage.rb +93 -0
- data/lib/simply_stored.rb +9 -0
- data/test/custom_views_test.rb +33 -0
- data/test/fixtures/couch.rb +182 -0
- data/test/fixtures/simpledb/item.rb +11 -0
- data/test/fixtures/simpledb/item_daddy.rb +8 -0
- data/test/fixtures/simpledb/log_item.rb +3 -0
- data/test/fixtures/simpledb/namespace_bar.rb +5 -0
- data/test/fixtures/simpledb/namespace_foo.rb +7 -0
- data/test/fixtures/simpledb/protected_item.rb +3 -0
- data/test/simply_stored_couch_test.rb +1684 -0
- data/test/simply_stored_simpledb_test.rb +1341 -0
- data/test/test_helper.rb +22 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/dot/dot.rb +29 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/evaluator/evaluator.rb +133 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/closure_hash.rb +15 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/grammar.rb +240 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/grammar_symbol.rb +27 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/precedence.rb +19 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/production.rb +36 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/accept_actions.rb +36 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/alphabet.rb +21 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/compiled_lexer.rb +46 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/dfa.rb +121 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/lexeme.rb +32 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/lexer.rb +70 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/lexer_run.rb +78 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/regex_grammar.rb +392 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/regex_parser.rb +2010 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/regex_tokenizer.rb +14 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/specification.rb +96 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/state.rb +68 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/state_machine.rb +37 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/action.rb +55 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/channel.rb +58 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/compiled_parser.rb +51 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/conflict.rb +54 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/item.rb +42 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parse_result.rb +50 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parse_tree.rb +66 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parser.rb +165 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parser_methods.rb +11 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parser_run.rb +39 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parser_state.rb +74 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/token.rb +22 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/runtime.rb +51 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/tokenizer/tokenizer.rb +190 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka.rb +62 -0
- data/test/vendor/dhaka-2.2.1/test/all_tests.rb +5 -0
- data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_evaluator.rb +64 -0
- data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_evaluator_test.rb +43 -0
- data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_grammar.rb +41 -0
- data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_grammar_test.rb +9 -0
- data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_test_methods.rb +9 -0
- data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_tokenizer.rb +39 -0
- data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_tokenizer_test.rb +38 -0
- data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_evaluator.rb +43 -0
- data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_grammar.rb +24 -0
- data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_grammar_test.rb +30 -0
- data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_lexer_specification.rb +23 -0
- data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_parser_test.rb +33 -0
- data/test/vendor/dhaka-2.2.1/test/brackets/bracket_grammar.rb +23 -0
- data/test/vendor/dhaka-2.2.1/test/brackets/bracket_tokenizer.rb +22 -0
- data/test/vendor/dhaka-2.2.1/test/brackets/brackets_test.rb +28 -0
- data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_driver.rb +46 -0
- data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_driver_test.rb +276 -0
- data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_evaluator.rb +284 -0
- data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_evaluator_test.rb +38 -0
- data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_grammar.rb +104 -0
- data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_lexer.rb +109 -0
- data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_lexer_specification.rb +37 -0
- data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_lexer_test.rb +58 -0
- data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_parser.rb +879 -0
- data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_parser_test.rb +55 -0
- data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_test.rb +170 -0
- data/test/vendor/dhaka-2.2.1/test/core/another_lalr_but_not_slr_grammar.rb +20 -0
- data/test/vendor/dhaka-2.2.1/test/core/compiled_parser_test.rb +44 -0
- data/test/vendor/dhaka-2.2.1/test/core/dfa_test.rb +170 -0
- data/test/vendor/dhaka-2.2.1/test/core/evaluator_test.rb +22 -0
- data/test/vendor/dhaka-2.2.1/test/core/grammar_test.rb +83 -0
- data/test/vendor/dhaka-2.2.1/test/core/lalr_but_not_slr_grammar.rb +19 -0
- data/test/vendor/dhaka-2.2.1/test/core/lexer_test.rb +139 -0
- data/test/vendor/dhaka-2.2.1/test/core/malformed_grammar.rb +7 -0
- data/test/vendor/dhaka-2.2.1/test/core/malformed_grammar_test.rb +8 -0
- data/test/vendor/dhaka-2.2.1/test/core/nullable_grammar.rb +21 -0
- data/test/vendor/dhaka-2.2.1/test/core/parse_result_test.rb +44 -0
- data/test/vendor/dhaka-2.2.1/test/core/parser_state_test.rb +24 -0
- data/test/vendor/dhaka-2.2.1/test/core/parser_test.rb +131 -0
- data/test/vendor/dhaka-2.2.1/test/core/precedence_grammar.rb +17 -0
- data/test/vendor/dhaka-2.2.1/test/core/precedence_grammar_test.rb +9 -0
- data/test/vendor/dhaka-2.2.1/test/core/rr_conflict_grammar.rb +21 -0
- data/test/vendor/dhaka-2.2.1/test/core/simple_grammar.rb +22 -0
- data/test/vendor/dhaka-2.2.1/test/core/sr_conflict_grammar.rb +16 -0
- data/test/vendor/dhaka-2.2.1/test/dhaka_test_helper.rb +17 -0
- data/test/vendor/dhaka-2.2.1/test/fake_logger.rb +17 -0
- data/test/vendor/simplerdb-0.2/lib/simplerdb/client_exception.rb +10 -0
- data/test/vendor/simplerdb-0.2/lib/simplerdb/db.rb +146 -0
- data/test/vendor/simplerdb-0.2/lib/simplerdb/query_language.rb +266 -0
- data/test/vendor/simplerdb-0.2/lib/simplerdb/server.rb +33 -0
- data/test/vendor/simplerdb-0.2/lib/simplerdb/servlet.rb +191 -0
- data/test/vendor/simplerdb-0.2/lib/simplerdb.rb +3 -0
- data/test/vendor/simplerdb-0.2/test/functional_test.rb +81 -0
- data/test/vendor/simplerdb-0.2/test/query_evaluator_test.rb +73 -0
- data/test/vendor/simplerdb-0.2/test/query_parser_test.rb +64 -0
- data/test/vendor/simplerdb-0.2/test/simplerdb_test.rb +80 -0
- metadata +182 -0
@@ -0,0 +1,173 @@
|
|
1
|
+
module SimplyStored
|
2
|
+
module SimpleDB
|
3
|
+
module Attributes
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(ClassMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
# generates an accessor for a simple attribute
|
10
|
+
def simpledb_string(*attributes)
|
11
|
+
attributes.each do |attr|
|
12
|
+
# getter
|
13
|
+
define_method attr.to_s do
|
14
|
+
self[attr.to_s].try(:first) || @_partitioned_attributes.try(:[], attr.to_s)
|
15
|
+
end
|
16
|
+
|
17
|
+
# setter
|
18
|
+
define_method "#{attr}=" do |val|
|
19
|
+
self[attr.to_s] = val
|
20
|
+
end
|
21
|
+
|
22
|
+
add_attribute_definition_to_attribute_list(attr)
|
23
|
+
add_attribute_definition_to_partition_list(attr)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def simpledb_array(*attributes)
|
28
|
+
attributes.each do |attr|
|
29
|
+
# getter
|
30
|
+
define_method attr.to_s do
|
31
|
+
self[attr.to_s] = [] unless self[attr.to_s]
|
32
|
+
self[attr.to_s]
|
33
|
+
end
|
34
|
+
|
35
|
+
# setter
|
36
|
+
define_method "#{attr}=" do |val|
|
37
|
+
self[attr.to_s] = [val].flatten
|
38
|
+
end
|
39
|
+
|
40
|
+
add_attribute_definition_to_attribute_list(attr)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def simpledb_timestamp(*attributes)
|
45
|
+
attributes.each do |attr|
|
46
|
+
# getter
|
47
|
+
define_method attr.to_s do
|
48
|
+
if self[attr.to_s]
|
49
|
+
cached = instance_variable_get("@#{attr}")
|
50
|
+
if cached
|
51
|
+
return cached
|
52
|
+
else
|
53
|
+
val = Time.parse(self[attr.to_s].first + "+0000").utc # UTC
|
54
|
+
instance_variable_set("@#{attr}", val)
|
55
|
+
return val
|
56
|
+
end
|
57
|
+
else
|
58
|
+
nil
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# setter
|
63
|
+
define_method "#{attr}=" do |val|
|
64
|
+
instance_variable_set("@#{attr}", val)
|
65
|
+
self[attr.to_s] = val.strftime("%Y%m%d%H%M%S")
|
66
|
+
end
|
67
|
+
|
68
|
+
add_attribute_definition_to_attribute_list(attr)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def simpledb_integer(*attributes)
|
73
|
+
attributes.each do |attr|
|
74
|
+
# getter
|
75
|
+
define_method attr.to_s do
|
76
|
+
if self[attr.to_s]
|
77
|
+
cached = instance_variable_get("@#{attr}")
|
78
|
+
if cached
|
79
|
+
return cached
|
80
|
+
else
|
81
|
+
val = self[attr.to_s].first.sub(/\A0+/, '').to_i
|
82
|
+
instance_variable_set("@#{attr}", val)
|
83
|
+
return val
|
84
|
+
end
|
85
|
+
else
|
86
|
+
nil
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# setter
|
91
|
+
define_method "#{attr}=" do |val|
|
92
|
+
instance_variable_set("@#{attr}", val)
|
93
|
+
self[attr.to_s] = sprintf("%016d", val.to_i)
|
94
|
+
end
|
95
|
+
|
96
|
+
add_attribute_definition_to_attribute_list(attr)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def add_attribute_definition_to_attribute_list(attr)
|
101
|
+
@_defined_attributes ||= []
|
102
|
+
@_defined_attributes << attr.to_s
|
103
|
+
end
|
104
|
+
|
105
|
+
def add_attribute_definition_to_partition_list(attr)
|
106
|
+
@_partitioned_attributes ||= []
|
107
|
+
@_partitioned_attributes << attr.to_s
|
108
|
+
end
|
109
|
+
|
110
|
+
def attr_protected(*args)
|
111
|
+
@_protected_attributes ||= []
|
112
|
+
@_protected_attributes += args.to_a
|
113
|
+
end
|
114
|
+
|
115
|
+
def attr_accessible(*args)
|
116
|
+
@_accessible_attributes ||= []
|
117
|
+
@_accessible_attributes += args.to_a
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
def attributes=(attr)
|
123
|
+
attr = _remove_protected_attributes(attr)
|
124
|
+
super(attr)
|
125
|
+
end
|
126
|
+
|
127
|
+
def _remove_protected_attributes(attrs)
|
128
|
+
return {} if attrs.blank?
|
129
|
+
attrs = attrs.dup.stringify_keys
|
130
|
+
(self.class.instance_variable_get(:@_protected_attributes) || []).map(&:to_s).each do |protected_attribute|
|
131
|
+
attrs.delete(protected_attribute)
|
132
|
+
end
|
133
|
+
|
134
|
+
accessible_attributes = (self.class.instance_variable_get(:@_accessible_attributes) || []).map(&:to_s)
|
135
|
+
|
136
|
+
if accessible_attributes.present?
|
137
|
+
attrs.each do |attr_key, attr_value|
|
138
|
+
attrs.delete(attr_key) unless accessible_attributes.include?(attr_key)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
attrs
|
143
|
+
end
|
144
|
+
|
145
|
+
def partition_string_attributes
|
146
|
+
(self.class.instance_variable_get(:@_partitioned_attributes) || []).each do |attr|
|
147
|
+
next if self[attr].nil?
|
148
|
+
value = self[attr].first
|
149
|
+
if value.present? && value.size > 1024
|
150
|
+
0.upto(value.size / 1024) do |i|
|
151
|
+
self["#{attr.to_s}_#{i}"] = value[(1024 * i)..(1024 * (i + 1) - 1)]
|
152
|
+
end
|
153
|
+
@_partitioned_attributes ||= {}
|
154
|
+
@_partitioned_attributes[attr] = self[attr]
|
155
|
+
send("#{attr}=", [])
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def restore_partitioned_attributes
|
161
|
+
(self.class.instance_variable_get(:@_partitioned_attributes) || []).each do |attr|
|
162
|
+
if (partitions = attributes.select{|key, value| key.match(/^#{attr}_([0-9]+)$/)}).any?
|
163
|
+
unpartitioned_value = ""
|
164
|
+
partitions.sort_by{|key, value| key.match(/^#{attr}_([0-9]+)$/); $1}.each do |partition|
|
165
|
+
unpartitioned_value << partition.last.first
|
166
|
+
end
|
167
|
+
self[attr] = unpartitioned_value
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module SimplyStore
|
2
|
+
module Storage
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
base.class_eval do
|
6
|
+
cattr_accessor :_s3_options
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def s3_connection
|
11
|
+
@_s3_connection ||= RightAws::S3.new(AWS_CONFIG[:aws_access_key_id], AWS_CONFIG[:aws_secret_access_key], :multi_thread => true)
|
12
|
+
end
|
13
|
+
|
14
|
+
def s3_bucket(name)
|
15
|
+
if !@_s3_bucket
|
16
|
+
@_s3_bucket = s3_connection.bucket(_s3_options[name][:bucket])
|
17
|
+
@_s3_bucket = s3_connection.bucket(_s3_options[name][:bucket], true, 'private') if @_s3_bucket.nil? # create it if it didn't exist, do not do it the first time as the ACL get modified
|
18
|
+
end
|
19
|
+
@_s3_bucket
|
20
|
+
rescue Exception => e
|
21
|
+
raise ArgumentError, "Could not access/create S3 bucket '#{name}': #{e} #{e.backtrace.join("\n")}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def save
|
25
|
+
if ret = super
|
26
|
+
save_attachments
|
27
|
+
end
|
28
|
+
ret
|
29
|
+
end
|
30
|
+
|
31
|
+
def save_attachments
|
32
|
+
if @_attachments
|
33
|
+
@_attachments.each do |name, attachment|
|
34
|
+
if attachment[:dirty]
|
35
|
+
value = attachment[:value].is_a?(String) ? attachment[:value] : attachment[:value].to_json
|
36
|
+
s3_bucket(name).put(s3_attachment_key(name), value, {}, _s3_options[name][:permissions])
|
37
|
+
attachment[:dirty] = false
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def s3_attachment_key(name)
|
44
|
+
"#{self.class.name.tableize}/#{name}/#{id}"
|
45
|
+
end
|
46
|
+
|
47
|
+
module ClassMethods
|
48
|
+
def has_s3_attachment(name, options = {})
|
49
|
+
name = name.to_sym
|
50
|
+
raise ArgumentError, "No bucket name specified for attachment #{name}" if options[:bucket].blank?
|
51
|
+
options.update(:permissions => 'private', :ssl => true)
|
52
|
+
self._s3_options ||= {}
|
53
|
+
self._s3_options[name] = options
|
54
|
+
|
55
|
+
define_attachment_accessors(name)
|
56
|
+
attr_reader :_attachments
|
57
|
+
end
|
58
|
+
|
59
|
+
def define_attachment_accessors(name)
|
60
|
+
define_method(name) do
|
61
|
+
unless @_attachments and @_attachments[name]
|
62
|
+
@_attachments = {name => {}}
|
63
|
+
@_attachments[name][:value] = s3_bucket(name).get(s3_attachment_key(name))
|
64
|
+
end
|
65
|
+
@_attachments[name][:value]
|
66
|
+
end
|
67
|
+
|
68
|
+
define_method("#{name}=") do |value|
|
69
|
+
@_attachments ||= {}
|
70
|
+
@_attachments[name] ||= {}
|
71
|
+
@_attachments[name].update(:value => value, :dirty => true)
|
72
|
+
value
|
73
|
+
end
|
74
|
+
|
75
|
+
define_method("#{name}_url") do
|
76
|
+
if _s3_options[name][:permissions] == 'private'
|
77
|
+
RightAws::S3Generator.new(AWS_CONFIG[:aws_access_key_id], AWS_CONFIG[:aws_secret_access_key], :multi_thread => true).bucket(_s3_options[name][:bucket]).get(s3_attachment_key(name), 5.minutes)
|
78
|
+
else
|
79
|
+
"http://#{_s3_options[name][:bucket].to_s}.s3.amazonaws.com/#{s3_attachment_key(name)}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module SimplyStored
|
2
|
+
module SimpleDB
|
3
|
+
module Validations
|
4
|
+
def validate
|
5
|
+
# abstract - implement in subclass and add checks that add to errors with add_error
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.included(base)
|
9
|
+
base.extend(ClassMethods)
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def require_attributes(*attributes)
|
14
|
+
@_required_attributes ||= []
|
15
|
+
attributes.each do |attr|
|
16
|
+
@_required_attributes << attr.to_s
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def require_inclusion_of(attr, valid_set, options = {})
|
21
|
+
options = {
|
22
|
+
:allow_blank => false
|
23
|
+
}.update(options)
|
24
|
+
@_required_inclusions ||= {}
|
25
|
+
@_required_inclusions[attr.to_s] = [valid_set, options]
|
26
|
+
end
|
27
|
+
|
28
|
+
def require_format_of(attr, valid_regex, options = {})
|
29
|
+
options = {
|
30
|
+
:allow_blank => false
|
31
|
+
}.update(options)
|
32
|
+
@_required_formats ||= {}
|
33
|
+
@_required_formats[attr.to_s] = [valid_regex, options]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def ensure_formats
|
38
|
+
list_of_format_checks = self.class.instance_variable_get("@_required_formats") || {}
|
39
|
+
list_of_format_checks.each do |attr, check_options|
|
40
|
+
valid_regex, options = check_options
|
41
|
+
add_error(attr, 'is of invalid format') unless valid_regex.match(self.send(attr)) || (self.send(attr).blank? && options[:allow_blank])
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def ensure_inclusions
|
46
|
+
list_of_inclusion_checks = self.class.instance_variable_get("@_required_inclusions") || {}
|
47
|
+
list_of_inclusion_checks.each do |attr, check_options|
|
48
|
+
valid_set, options = check_options
|
49
|
+
if self.send(attr) && self.send(attr).is_a?(Array)
|
50
|
+
self.send(attr).all? do |item|
|
51
|
+
add_error(attr, 'is not included in the valid set') unless (valid_set.include?(item) rescue false) || (self.send(attr).blank? && options[:allow_blank])
|
52
|
+
end
|
53
|
+
else
|
54
|
+
add_error(attr, 'is not included in the valid set') unless (valid_set.include?(self.send(attr)) rescue false) || (self.send(attr).blank? && options[:allow_blank])
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def add_error(attr, desc)
|
60
|
+
errors << [attr, desc]
|
61
|
+
end
|
62
|
+
|
63
|
+
def clear_errors
|
64
|
+
@errors = []
|
65
|
+
end
|
66
|
+
|
67
|
+
def ensure_no_foreign_attributes
|
68
|
+
not_allowed_attributes = self.attributes.keys.sort - (self.class.instance_variable_get("@_defined_attributes") || [])
|
69
|
+
not_allowed_attributes = not_allowed_attributes.delete_if{|attr| attr.to_s == 'id'} # the ID is an implicit attribute
|
70
|
+
not_allowed_attributes.each do |attr|
|
71
|
+
add_error(attr, 'is unknown and should not be set')
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def ensure_required_attributes_present
|
76
|
+
list_of_required_attributes = self.class.instance_variable_get("@_required_attributes") || []
|
77
|
+
list_of_required_attributes.all? do |attr|
|
78
|
+
if self.send(attr).blank?
|
79
|
+
add_error(attr, 'is missing')
|
80
|
+
false
|
81
|
+
else
|
82
|
+
true
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,212 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../simply_stored')
|
2
|
+
|
3
|
+
require 'sdb/right_sdb_interface'
|
4
|
+
require 'sdb/active_sdb'
|
5
|
+
require "simply_stored/simpledb/attributes"
|
6
|
+
require "simply_stored/simpledb/associations"
|
7
|
+
require "simply_stored/simpledb/validations"
|
8
|
+
|
9
|
+
module SimplyStored
|
10
|
+
class Simple < RightAws::ActiveSdb::Base
|
11
|
+
def self.aws_access_key
|
12
|
+
@_aws_access_key
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.aws_secret_access_key
|
16
|
+
@_aws_secret_access_key
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.aws_access_key=(aws_access_key)
|
20
|
+
@_aws_access_key = aws_access_key
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.aws_secret_access_key=(aws_secret_access_key)
|
24
|
+
@_aws_secret_access_key = aws_secret_access_key
|
25
|
+
end
|
26
|
+
|
27
|
+
include SimplyStored::SimpleDB::Associations
|
28
|
+
include SimplyStored::SimpleDB::Attributes
|
29
|
+
include SimplyStored::SimpleDB::Validations
|
30
|
+
extend SimplyStored::Storage::ClassMethods
|
31
|
+
|
32
|
+
alias :active_sdb_save :save
|
33
|
+
alias :active_sdb_delete :delete
|
34
|
+
alias :active_sdb_save_attributes :save_attributes
|
35
|
+
|
36
|
+
def initialize(attrs={})
|
37
|
+
attrs = _remove_protected_attributes(attrs) || {}
|
38
|
+
super(attrs)
|
39
|
+
end
|
40
|
+
|
41
|
+
def save(run_validations = true)
|
42
|
+
run_validations ? save_with_validations : save_without_validations
|
43
|
+
end
|
44
|
+
|
45
|
+
def reload
|
46
|
+
super
|
47
|
+
restore_partitioned_attributes
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
def save_with_validations
|
52
|
+
new_record = new_record? # save status as it changes during saving
|
53
|
+
before_create if new_record && respond_to?(:before_create)
|
54
|
+
before_save if respond_to?(:before_save)
|
55
|
+
if valid?
|
56
|
+
set_timestamps
|
57
|
+
partition_string_attributes
|
58
|
+
sdb_operation(:save)
|
59
|
+
clear_errors
|
60
|
+
after_create if new_record && respond_to?(:after_create)
|
61
|
+
after_save if respond_to?(:after_save)
|
62
|
+
true
|
63
|
+
else
|
64
|
+
false
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def save_without_validations
|
69
|
+
new_record = new_record? # save status as it changes during saving
|
70
|
+
before_create if new_record && respond_to?(:before_create)
|
71
|
+
before_save if respond_to?(:before_save)
|
72
|
+
set_timestamps
|
73
|
+
sdb_operation(:save)
|
74
|
+
clear_errors
|
75
|
+
after_create if new_record && respond_to?(:after_create)
|
76
|
+
after_save if respond_to?(:after_save)
|
77
|
+
true
|
78
|
+
end
|
79
|
+
|
80
|
+
def delete
|
81
|
+
before_delete if respond_to?(:before_delete)
|
82
|
+
sdb_operation(:delete)
|
83
|
+
after_delete if respond_to?(:after_delete)
|
84
|
+
clear_dependents_after_delete
|
85
|
+
nil
|
86
|
+
end
|
87
|
+
|
88
|
+
def valid?
|
89
|
+
clear_errors
|
90
|
+
before_validation if respond_to?(:before_validation)
|
91
|
+
ensure_no_foreign_attributes
|
92
|
+
ensure_required_attributes_present
|
93
|
+
ensure_inclusions
|
94
|
+
ensure_formats
|
95
|
+
validate
|
96
|
+
errors.empty?
|
97
|
+
end
|
98
|
+
|
99
|
+
def errors
|
100
|
+
@errors ||= []
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.domain
|
104
|
+
unless @domain
|
105
|
+
if defined? ActiveSupport::CoreExtensions::String::Inflections
|
106
|
+
@domain = name.tableize.gsub('/','__').gsub('::','__')
|
107
|
+
else
|
108
|
+
@domain = name.downcase.gsub('/','__').gsub('::','__')
|
109
|
+
end
|
110
|
+
end
|
111
|
+
@domain
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.foreign_key
|
115
|
+
self.name.underscore.gsub('/','__').gsub('::','__') + "_id"
|
116
|
+
end
|
117
|
+
|
118
|
+
def self.find(*args)
|
119
|
+
retries = 0
|
120
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
121
|
+
options.update(:auto_load => true)
|
122
|
+
args << options
|
123
|
+
begin
|
124
|
+
super(*args)
|
125
|
+
rescue Rightscale::AwsError => e
|
126
|
+
if (Rightscale::AwsError.system_error?(e) or e.message.match(/^RequestThrottled/)) and retries < 2
|
127
|
+
retries += 1
|
128
|
+
sleep retries * 0.5
|
129
|
+
retry
|
130
|
+
else
|
131
|
+
raise Error.new("Couldn't find item with #{args.inspect}: #{e}")
|
132
|
+
end
|
133
|
+
rescue RightAws::ActiveSdb::ActiveSdbError => e
|
134
|
+
if e.message.match(/Couldn't find #{name} with ID/)
|
135
|
+
if retries < 2
|
136
|
+
retries += 1
|
137
|
+
sleep retries * 0.5
|
138
|
+
retry
|
139
|
+
else
|
140
|
+
raise RecordNotFound, e.message
|
141
|
+
end
|
142
|
+
else
|
143
|
+
raise
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def self.all(options = {})
|
149
|
+
find(:all, options)
|
150
|
+
end
|
151
|
+
|
152
|
+
def self.first(options = {})
|
153
|
+
find(:first, options)
|
154
|
+
end
|
155
|
+
|
156
|
+
def update_attributes(*args)
|
157
|
+
# HACK in order to update attributes to nil
|
158
|
+
# thank you ActiveSDB
|
159
|
+
if (attrs = args.first).is_a?(Hash)
|
160
|
+
attrs.each do |k,v|
|
161
|
+
attrs[k] = [nil] if v.blank?
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
sdb_operation(:save_attributes, *args)
|
166
|
+
end
|
167
|
+
|
168
|
+
def to_param
|
169
|
+
id
|
170
|
+
end
|
171
|
+
|
172
|
+
# set several attrbiutes at once with a given hash
|
173
|
+
# in contrast to ActiveSDB#attributes= it doesn't nil the other attributes
|
174
|
+
def set_attributes(attrs)
|
175
|
+
attrs = _remove_protected_attributes(attrs)
|
176
|
+
|
177
|
+
attrs.each do |k,v|
|
178
|
+
self[k] = v
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
protected
|
183
|
+
|
184
|
+
def self.generate_id
|
185
|
+
UUIDTools::UUID.random_create().to_s
|
186
|
+
end
|
187
|
+
|
188
|
+
def sdb_operation(operation, *args)
|
189
|
+
retries = 0
|
190
|
+
begin
|
191
|
+
send("active_sdb_#{operation}", *args)
|
192
|
+
rescue Rightscale::AwsError => e
|
193
|
+
if (Rightscale::AwsError.system_error?(e) or e.message.match(/^RequestThrottled/)) and retries < 2
|
194
|
+
retries += 1
|
195
|
+
sleep retries * 0.5
|
196
|
+
retry
|
197
|
+
else
|
198
|
+
raise Error.new("Couldn't #{operation} item of type #{self.class.name}#{id.nil? ? "" : " (with id: #{id})"}: #{e} #{e.backtrace.join("\n")}")
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def set_timestamps
|
204
|
+
self.updated_at = Time.now.utc if self.respond_to?(:updated_at=)
|
205
|
+
self.created_at = Time.now.utc if self.respond_to?(:created_at=) && self.new_record?
|
206
|
+
end
|
207
|
+
|
208
|
+
def self.get_class_from_name(klass_name)
|
209
|
+
klass_name.to_s.gsub('__','/').gsub('__','::').classify.constantize
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module SimplyStored
|
2
|
+
module Storage
|
3
|
+
module InstanceMethods
|
4
|
+
def _s3_options
|
5
|
+
self.class._s3_options
|
6
|
+
end
|
7
|
+
|
8
|
+
def s3_connection(name)
|
9
|
+
@_s3_connection ||= RightAws::S3.new(_s3_options[name][:access_key], _s3_options[name][:secret_access_key], :multi_thread => true)
|
10
|
+
end
|
11
|
+
|
12
|
+
def s3_bucket(name)
|
13
|
+
if !@_s3_bucket
|
14
|
+
@_s3_bucket = s3_connection(name).bucket(_s3_options[name][:bucket])
|
15
|
+
@_s3_bucket = s3_connection(name).bucket(_s3_options[name][:bucket], true, 'private') if @_s3_bucket.nil?
|
16
|
+
end
|
17
|
+
@_s3_bucket
|
18
|
+
rescue Exception => e
|
19
|
+
raise ArgumentError, "Could not access/create S3 bucket '#{name}': #{e} #{e.backtrace.join("\n")}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def save(validate = true)
|
23
|
+
if ret = super(validate)
|
24
|
+
save_attachments
|
25
|
+
end
|
26
|
+
ret
|
27
|
+
end
|
28
|
+
|
29
|
+
def save_attachments
|
30
|
+
if @_s3_attachments
|
31
|
+
@_s3_attachments.each do |name, attachment|
|
32
|
+
if attachment[:dirty]
|
33
|
+
value = attachment[:value].is_a?(String) ? attachment[:value] : attachment[:value].to_json
|
34
|
+
s3_bucket(name).put(s3_attachment_key(name), value, {}, _s3_options[name][:permissions])
|
35
|
+
attachment[:dirty] = false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def s3_attachment_key(name)
|
42
|
+
"#{self.class.name.tableize}/#{name}/#{id}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
module ClassMethods
|
47
|
+
def has_s3_attachment(name, options = {})
|
48
|
+
require 'awsbase/right_awsbase'
|
49
|
+
require 's3/right_s3'
|
50
|
+
require 's3/right_s3_interface'
|
51
|
+
|
52
|
+
self.class.instance_eval do
|
53
|
+
attr_accessor :_s3_options
|
54
|
+
end
|
55
|
+
|
56
|
+
name = name.to_sym
|
57
|
+
raise ArgumentError, "No bucket name specified for attachment #{name}" if options[:bucket].blank?
|
58
|
+
options.update(:permissions => 'private', :ssl => true)
|
59
|
+
self._s3_options ||= {}
|
60
|
+
self._s3_options[name] = options
|
61
|
+
|
62
|
+
define_attachment_accessors(name)
|
63
|
+
attr_reader :_s3_attachments
|
64
|
+
include InstanceMethods
|
65
|
+
end
|
66
|
+
|
67
|
+
def define_attachment_accessors(name)
|
68
|
+
define_method(name) do
|
69
|
+
unless @_s3_attachments and @_s3_attachments[name]
|
70
|
+
@_s3_attachments = {name => {}}
|
71
|
+
@_s3_attachments[name][:value] = s3_bucket(name).get(s3_attachment_key(name))
|
72
|
+
end
|
73
|
+
@_s3_attachments[name][:value]
|
74
|
+
end
|
75
|
+
|
76
|
+
define_method("#{name}=") do |value|
|
77
|
+
@_s3_attachments ||= {}
|
78
|
+
@_s3_attachments[name] ||= {}
|
79
|
+
@_s3_attachments[name].update(:value => value, :dirty => true)
|
80
|
+
value
|
81
|
+
end
|
82
|
+
|
83
|
+
define_method("#{name}_url") do
|
84
|
+
if _s3_options[name][:permissions] == 'private'
|
85
|
+
RightAws::S3Generator.new(_s3_options[name][:access_key], _s3_options[name][:secret_access_key], :multi_thread => true).bucket(_s3_options[name][:bucket]).get(s3_attachment_key(name), 5.minutes)
|
86
|
+
else
|
87
|
+
"http://#{_s3_options[name][:bucket].to_s}.s3.amazonaws.com/#{s3_attachment_key(name)}"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
$:<<(File.expand_path(File.dirname(__FILE__) + "/lib"))
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/simply_stored/instance_methods')
|
3
|
+
require File.expand_path(File.dirname(__FILE__) + '/simply_stored/storage')
|
4
|
+
require File.expand_path(File.dirname(__FILE__) + '/simply_stored/class_methods_base')
|
5
|
+
|
6
|
+
module SimplyStored
|
7
|
+
class Error < RuntimeError; end
|
8
|
+
class RecordNotFound < RuntimeError; end
|
9
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/test_helper')
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/fixtures/couch')
|
3
|
+
|
4
|
+
class CustomViewUser
|
5
|
+
include SimplyStored::Couch
|
6
|
+
|
7
|
+
property :tags
|
8
|
+
view :by_tags, :type => SimplyStored::Couch::Views::ArrayPropertyViewSpec, :key => :tags
|
9
|
+
end
|
10
|
+
|
11
|
+
class CustomViewsTest < Test::Unit::TestCase
|
12
|
+
|
13
|
+
context "Custom couch views" do
|
14
|
+
setup do
|
15
|
+
CouchPotato::Config.database_name = 'simply_stored_test'
|
16
|
+
recreate_db
|
17
|
+
end
|
18
|
+
|
19
|
+
context "With array views" do
|
20
|
+
should "find objects with one match of the array" do
|
21
|
+
CustomViewUser.create(:tags => ["agile", "cool", "extreme"])
|
22
|
+
CustomViewUser.create(:tags => ["agile"])
|
23
|
+
assert_equal 2, CustomViewUser.find_all_by_tags("agile").size
|
24
|
+
end
|
25
|
+
|
26
|
+
should "find the object when the property is not an array" do
|
27
|
+
CustomViewUser.create(:tags => "agile")
|
28
|
+
assert_equal 1, CustomViewUser.find_all_by_tags("agile").size
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|