simply_stored 0.1.4

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