simply_stored 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. data/lib/simply_stored/class_methods_base.rb +31 -0
  2. data/lib/simply_stored/couch/belongs_to.rb +117 -0
  3. data/lib/simply_stored/couch/ext/couch_potato.rb +16 -0
  4. data/lib/simply_stored/couch/has_many.rb +148 -0
  5. data/lib/simply_stored/couch/has_one.rb +93 -0
  6. data/lib/simply_stored/couch/validations.rb +74 -0
  7. data/lib/simply_stored/couch/views/array_property_view_spec.rb +22 -0
  8. data/lib/simply_stored/couch/views.rb +1 -0
  9. data/lib/simply_stored/couch.rb +278 -0
  10. data/lib/simply_stored/instance_methods.rb +143 -0
  11. data/lib/simply_stored/simpledb/associations.rb +196 -0
  12. data/lib/simply_stored/simpledb/attributes.rb +173 -0
  13. data/lib/simply_stored/simpledb/storag.rb +85 -0
  14. data/lib/simply_stored/simpledb/validations.rb +88 -0
  15. data/lib/simply_stored/simpledb.rb +212 -0
  16. data/lib/simply_stored/storage.rb +93 -0
  17. data/lib/simply_stored.rb +9 -0
  18. data/test/custom_views_test.rb +33 -0
  19. data/test/fixtures/couch.rb +182 -0
  20. data/test/fixtures/simpledb/item.rb +11 -0
  21. data/test/fixtures/simpledb/item_daddy.rb +8 -0
  22. data/test/fixtures/simpledb/log_item.rb +3 -0
  23. data/test/fixtures/simpledb/namespace_bar.rb +5 -0
  24. data/test/fixtures/simpledb/namespace_foo.rb +7 -0
  25. data/test/fixtures/simpledb/protected_item.rb +3 -0
  26. data/test/simply_stored_couch_test.rb +1684 -0
  27. data/test/simply_stored_simpledb_test.rb +1341 -0
  28. data/test/test_helper.rb +22 -0
  29. data/test/vendor/dhaka-2.2.1/lib/dhaka/dot/dot.rb +29 -0
  30. data/test/vendor/dhaka-2.2.1/lib/dhaka/evaluator/evaluator.rb +133 -0
  31. data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/closure_hash.rb +15 -0
  32. data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/grammar.rb +240 -0
  33. data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/grammar_symbol.rb +27 -0
  34. data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/precedence.rb +19 -0
  35. data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/production.rb +36 -0
  36. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/accept_actions.rb +36 -0
  37. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/alphabet.rb +21 -0
  38. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/compiled_lexer.rb +46 -0
  39. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/dfa.rb +121 -0
  40. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/lexeme.rb +32 -0
  41. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/lexer.rb +70 -0
  42. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/lexer_run.rb +78 -0
  43. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/regex_grammar.rb +392 -0
  44. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/regex_parser.rb +2010 -0
  45. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/regex_tokenizer.rb +14 -0
  46. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/specification.rb +96 -0
  47. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/state.rb +68 -0
  48. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/state_machine.rb +37 -0
  49. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/action.rb +55 -0
  50. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/channel.rb +58 -0
  51. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/compiled_parser.rb +51 -0
  52. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/conflict.rb +54 -0
  53. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/item.rb +42 -0
  54. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parse_result.rb +50 -0
  55. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parse_tree.rb +66 -0
  56. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parser.rb +165 -0
  57. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parser_methods.rb +11 -0
  58. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parser_run.rb +39 -0
  59. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parser_state.rb +74 -0
  60. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/token.rb +22 -0
  61. data/test/vendor/dhaka-2.2.1/lib/dhaka/runtime.rb +51 -0
  62. data/test/vendor/dhaka-2.2.1/lib/dhaka/tokenizer/tokenizer.rb +190 -0
  63. data/test/vendor/dhaka-2.2.1/lib/dhaka.rb +62 -0
  64. data/test/vendor/dhaka-2.2.1/test/all_tests.rb +5 -0
  65. data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_evaluator.rb +64 -0
  66. data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_evaluator_test.rb +43 -0
  67. data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_grammar.rb +41 -0
  68. data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_grammar_test.rb +9 -0
  69. data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_test_methods.rb +9 -0
  70. data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_tokenizer.rb +39 -0
  71. data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_tokenizer_test.rb +38 -0
  72. data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_evaluator.rb +43 -0
  73. data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_grammar.rb +24 -0
  74. data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_grammar_test.rb +30 -0
  75. data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_lexer_specification.rb +23 -0
  76. data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_parser_test.rb +33 -0
  77. data/test/vendor/dhaka-2.2.1/test/brackets/bracket_grammar.rb +23 -0
  78. data/test/vendor/dhaka-2.2.1/test/brackets/bracket_tokenizer.rb +22 -0
  79. data/test/vendor/dhaka-2.2.1/test/brackets/brackets_test.rb +28 -0
  80. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_driver.rb +46 -0
  81. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_driver_test.rb +276 -0
  82. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_evaluator.rb +284 -0
  83. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_evaluator_test.rb +38 -0
  84. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_grammar.rb +104 -0
  85. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_lexer.rb +109 -0
  86. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_lexer_specification.rb +37 -0
  87. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_lexer_test.rb +58 -0
  88. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_parser.rb +879 -0
  89. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_parser_test.rb +55 -0
  90. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_test.rb +170 -0
  91. data/test/vendor/dhaka-2.2.1/test/core/another_lalr_but_not_slr_grammar.rb +20 -0
  92. data/test/vendor/dhaka-2.2.1/test/core/compiled_parser_test.rb +44 -0
  93. data/test/vendor/dhaka-2.2.1/test/core/dfa_test.rb +170 -0
  94. data/test/vendor/dhaka-2.2.1/test/core/evaluator_test.rb +22 -0
  95. data/test/vendor/dhaka-2.2.1/test/core/grammar_test.rb +83 -0
  96. data/test/vendor/dhaka-2.2.1/test/core/lalr_but_not_slr_grammar.rb +19 -0
  97. data/test/vendor/dhaka-2.2.1/test/core/lexer_test.rb +139 -0
  98. data/test/vendor/dhaka-2.2.1/test/core/malformed_grammar.rb +7 -0
  99. data/test/vendor/dhaka-2.2.1/test/core/malformed_grammar_test.rb +8 -0
  100. data/test/vendor/dhaka-2.2.1/test/core/nullable_grammar.rb +21 -0
  101. data/test/vendor/dhaka-2.2.1/test/core/parse_result_test.rb +44 -0
  102. data/test/vendor/dhaka-2.2.1/test/core/parser_state_test.rb +24 -0
  103. data/test/vendor/dhaka-2.2.1/test/core/parser_test.rb +131 -0
  104. data/test/vendor/dhaka-2.2.1/test/core/precedence_grammar.rb +17 -0
  105. data/test/vendor/dhaka-2.2.1/test/core/precedence_grammar_test.rb +9 -0
  106. data/test/vendor/dhaka-2.2.1/test/core/rr_conflict_grammar.rb +21 -0
  107. data/test/vendor/dhaka-2.2.1/test/core/simple_grammar.rb +22 -0
  108. data/test/vendor/dhaka-2.2.1/test/core/sr_conflict_grammar.rb +16 -0
  109. data/test/vendor/dhaka-2.2.1/test/dhaka_test_helper.rb +17 -0
  110. data/test/vendor/dhaka-2.2.1/test/fake_logger.rb +17 -0
  111. data/test/vendor/simplerdb-0.2/lib/simplerdb/client_exception.rb +10 -0
  112. data/test/vendor/simplerdb-0.2/lib/simplerdb/db.rb +146 -0
  113. data/test/vendor/simplerdb-0.2/lib/simplerdb/query_language.rb +266 -0
  114. data/test/vendor/simplerdb-0.2/lib/simplerdb/server.rb +33 -0
  115. data/test/vendor/simplerdb-0.2/lib/simplerdb/servlet.rb +191 -0
  116. data/test/vendor/simplerdb-0.2/lib/simplerdb.rb +3 -0
  117. data/test/vendor/simplerdb-0.2/test/functional_test.rb +81 -0
  118. data/test/vendor/simplerdb-0.2/test/query_evaluator_test.rb +73 -0
  119. data/test/vendor/simplerdb-0.2/test/query_parser_test.rb +64 -0
  120. data/test/vendor/simplerdb-0.2/test/simplerdb_test.rb +80 -0
  121. 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