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,278 @@
1
+ require 'couch_potato'
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/../simply_stored')
4
+ require 'simply_stored/couch/validations'
5
+ require 'simply_stored/couch/belongs_to'
6
+ require 'simply_stored/couch/has_many'
7
+ require 'simply_stored/couch/has_one'
8
+ require 'simply_stored/couch/ext/couch_potato'
9
+ require 'simply_stored/couch/views'
10
+
11
+ module SimplyStored
12
+ module Couch
13
+ def self.included(clazz)
14
+ clazz.class_eval do
15
+ include CouchPotato::Persistence
16
+ include InstanceMethods
17
+ extend ClassMethods
18
+ end
19
+
20
+ clazz.instance_eval do
21
+ attr_accessor :_accessible_attributes, :_protected_attributes
22
+ alias :simpledb_array :simpledb_string
23
+ alias :simpledb_integer :simpledb_string
24
+
25
+ view :all_documents, :key => :created_at
26
+ end
27
+ end
28
+
29
+ module ClassMethods
30
+ include SimplyStored::ClassMethods::Base
31
+ include SimplyStored::Couch::Validations
32
+ include SimplyStored::Couch::BelongsTo
33
+ include SimplyStored::Couch::HasMany
34
+ include SimplyStored::Couch::HasOne
35
+ include SimplyStored::Storage::ClassMethods
36
+
37
+ def create(attributes = {}, &blk)
38
+ instance = new(attributes, &blk)
39
+ instance.save
40
+ instance
41
+ end
42
+
43
+ def create!(attributes = {}, &blk)
44
+ instance = new(attributes, &blk)
45
+ instance.save!
46
+ instance
47
+ end
48
+
49
+ def find(*args)
50
+ what = args.shift
51
+ options = args.last.is_a?(Hash) ? args.last : {}
52
+
53
+ options.assert_valid_keys(:with_deleted)
54
+ with_deleted = options.delete(:with_deleted)
55
+
56
+ case what
57
+ when :all
58
+ if with_deleted || !soft_deleting_enabled?
59
+ CouchPotato.database.view(all_documents(*args))
60
+ else
61
+ CouchPotato.database.view(all_documents_without_deleted(:key => nil))
62
+ end
63
+ when :first
64
+ if with_deleted || !soft_deleting_enabled?
65
+ CouchPotato.database.view(all_documents(:limit => 1)).first
66
+ else
67
+ CouchPotato.database.view(all_documents_without_deleted(:key => nil, :limit => 1)).first
68
+ end
69
+ else
70
+ raise SimplyStored::Error, "Can't load record without an id" if what.nil?
71
+ document = CouchPotato.database.load_document(what)
72
+ if document.nil? or !document.is_a?(self) or (document.deleted? && !with_deleted)
73
+ raise(SimplyStored::RecordNotFound)
74
+ end
75
+ document
76
+ end
77
+ end
78
+
79
+ def all
80
+ find(:all)
81
+ end
82
+
83
+ def first
84
+ find(:first)
85
+ end
86
+
87
+ def count(options = {})
88
+ options.assert_valid_keys(:with_deleted)
89
+ with_deleted = options[:with_deleted]
90
+
91
+ if with_deleted || !soft_deleting_enabled?
92
+ CouchPotato.database.view(all_documents(:reduce => true))
93
+ else
94
+ CouchPotato.database.view(all_documents_without_deleted(:reduce => true, :key => nil))
95
+ end
96
+ end
97
+
98
+ def enable_soft_delete(property_name = :deleted_at)
99
+ @_soft_delete_attribute = property_name.to_sym
100
+ property property_name, :type => Time
101
+ _define_hard_delete_methods
102
+ _define_soft_delete_views
103
+ end
104
+
105
+ def soft_delete_attribute
106
+ @_soft_delete_attribute
107
+ end
108
+
109
+ def soft_deleting_enabled?
110
+ !soft_delete_attribute.nil?
111
+ end
112
+
113
+ def simpledb_string(*names)
114
+ names.each do |name|
115
+ property name
116
+ end
117
+ end
118
+
119
+ def simpledb_timestamp(*names)
120
+ names.each do |name|
121
+ property name, :type => Time
122
+ end
123
+ end
124
+
125
+ def require_attributes(*names)
126
+ names.each do |name|
127
+ validates_presence_of name
128
+ end
129
+ end
130
+
131
+ def require_inclusion_of(name, valid_set, options = {})
132
+ options.update(:in => valid_set)
133
+ validates_inclusion_of(name, options)
134
+ end
135
+
136
+ def require_format_of(attr, valid_regex, options = {})
137
+ options.update(:with => valid_regex)
138
+ validates_format_of(attr, options)
139
+ end
140
+
141
+ def _define_find_by(name, *args)
142
+ keys = name.to_s.gsub(/^find_by_/, "").split("_and_")
143
+ view_name = name.to_s.gsub(/^find_/, "").to_sym
144
+ view_keys = keys.length == 1 ? keys.first : keys
145
+ without_deleted_view_name = "#{view_name}_withoutdeleted"
146
+ without_deleted_view_keys = keys + [:deleted_at]
147
+
148
+ unless respond_to?(view_name)
149
+ puts "Warning: Defining view #{self.name}##{view_name} with keys #{view_keys.inspect} at call time, please add it to the class body. (Called from #{caller[0]})"
150
+ view(view_name, :key => view_keys)
151
+ end
152
+
153
+ if !respond_to?(without_deleted_view_name) && soft_deleting_enabled?
154
+ puts "Warning: Defining view #{self.name}##{without_deleted_view_name} with keys #{without_deleted_view_keys.inspect} at call time, please add it to the class body. (Called from #{caller[0]})"
155
+ view(without_deleted_view_name, :key => without_deleted_view_keys)
156
+ end
157
+
158
+ (class << self; self end).instance_eval do
159
+ define_method(name) do |*key_args|
160
+ options = key_args.last.is_a?(Hash) ? key_args.pop : {}
161
+ options.assert_valid_keys(:with_deleted)
162
+ with_deleted = options.delete(:with_deleted)
163
+
164
+ raise ArgumentError, "Too many or too few arguments, require #{keys.inspect}" unless keys.size == key_args.size
165
+
166
+ if soft_deleting_enabled? && !with_deleted
167
+ key_args = key_args + [nil] # deleted_at
168
+ CouchPotato.database.view(send(without_deleted_view_name, :key => (key_args.size == 1 ? key_args.first : key_args), :limit => 1, :include_docs => true)).first
169
+ else
170
+ CouchPotato.database.view(send(view_name, :key => (key_args.size == 1 ? key_args.first : key_args), :limit => 1, :include_docs => true)).first
171
+ end
172
+ end
173
+ end
174
+
175
+ send(name, *args)
176
+ end
177
+
178
+ def _define_find_all_by(name, *args)
179
+ keys = name.to_s.gsub(/^find_all_by_/, "").split("_and_")
180
+ view_name = name.to_s.gsub(/^find_all_/, "").to_sym
181
+ view_keys = keys.length == 1 ? keys.first : keys
182
+ without_deleted_view_name = "#{view_name}_withoutdeleted"
183
+ without_deleted_view_keys = keys + [:deleted_at]
184
+
185
+ unless respond_to?(view_name)
186
+ puts "Warning: Defining view #{self.name}##{view_name} with keys #{view_keys.inspect} at call time, please add it to the class body. (Called from #{caller[0]})"
187
+ view(view_name, :key => view_keys)
188
+ end
189
+
190
+ if !respond_to?(without_deleted_view_name) && soft_deleting_enabled?
191
+ puts "Warning: Defining view #{self.name}##{without_deleted_view_name} with keys #{without_deleted_view_keys.inspect} at call time, please add it to the class body. (Called from #{caller[0]})"
192
+ view(without_deleted_view_name, :key => without_deleted_view_keys)
193
+ end
194
+
195
+ (class << self; self end).instance_eval do
196
+ define_method(name) do |*key_args|
197
+ options = key_args.last.is_a?(Hash) ? key_args.pop : {}
198
+ options.assert_valid_keys(:with_deleted)
199
+ with_deleted = options.delete(:with_deleted)
200
+
201
+ raise ArgumentError, "Too many or too few arguments, require #{keys.inspect}" unless keys.size == key_args.size
202
+
203
+ if soft_deleting_enabled? && !with_deleted
204
+ key_args = key_args + [nil] # deleted_at
205
+ CouchPotato.database.view(send(without_deleted_view_name, :key => (key_args.size == 1 ? key_args.first : key_args), :include_docs => true))
206
+ else
207
+ CouchPotato.database.view(send(view_name, :key => (key_args.size == 1 ? key_args.first : key_args), :include_docs => true))
208
+ end
209
+ end
210
+ end
211
+ send(name, *args)
212
+ end
213
+
214
+ def _define_count_by(name, *args)
215
+ keys = name.to_s.gsub(/^count_by_/, "").split("_and_")
216
+ view_name = name.to_s.gsub(/^count_/, "").to_sym
217
+ view_keys = keys.length == 1 ? keys.first : keys
218
+ without_deleted_view_name = "#{view_name}_withoutdeleted"
219
+ without_deleted_view_keys = keys + [:deleted_at]
220
+
221
+ unless respond_to?(view_name)
222
+ puts "Warning: Defining view #{self.name}##{view_name} with keys #{view_keys.inspect} at call time, please add it to the class body. (Called from #{caller[0]})"
223
+ view(view_name, :key => view_keys)
224
+ end
225
+
226
+ if !respond_to?(without_deleted_view_name) && soft_deleting_enabled?
227
+ puts "Warning: Defining view #{self.name}##{without_deleted_view_name} with keys #{without_deleted_view_keys.inspect} at call time, please add it to the class body. (Called from #{caller[0]})"
228
+ view(without_deleted_view_name, :key => without_deleted_view_keys)
229
+ end
230
+
231
+ (class << self; self end).instance_eval do
232
+ define_method("#{name}") do |*key_args|
233
+ options = key_args.last.is_a?(Hash) ? key_args.pop : {}
234
+ options.assert_valid_keys(:with_deleted)
235
+ with_deleted = options.delete(:with_deleted)
236
+
237
+ if soft_deleting_enabled? && !with_deleted
238
+ key_args = key_args + [nil] # deleted_at
239
+ CouchPotato.database.view(send(without_deleted_view_name, :key => (key_args.size == 1 ? key_args.first : key_args), :reduce => true))
240
+ else
241
+ CouchPotato.database.view(send(view_name, :key => (key_args.size == 1 ? key_args.first : key_args), :reduce => true))
242
+ end
243
+
244
+ end
245
+ end
246
+
247
+ send(name, *args)
248
+ end
249
+
250
+ def method_missing(name, *args)
251
+ if name.to_s =~ /^find_by/
252
+ _define_find_by(name, *args)
253
+ elsif name.to_s =~ /^find_all_by/
254
+ _define_find_all_by(name, *args)
255
+ elsif name.to_s =~ /^count_by/
256
+ _define_count_by(name, *args)
257
+ else
258
+ super
259
+ end
260
+ end
261
+
262
+ def _define_hard_delete_methods
263
+ define_method("destroy!") do
264
+ destroy(true)
265
+ end
266
+
267
+ define_method("delete!") do
268
+ destroy(true)
269
+ end
270
+ end
271
+
272
+ def _define_soft_delete_views
273
+ view :all_documents_without_deleted, :key => soft_delete_attribute
274
+ end
275
+
276
+ end
277
+ end
278
+ end
@@ -0,0 +1,143 @@
1
+ module SimplyStored
2
+ module InstanceMethods
3
+
4
+ def initialize(attributes = {}, &blk)
5
+ super(_remove_protected_attributes(attributes))
6
+ blk.call(self) if blk
7
+ end
8
+
9
+ def ==(other)
10
+ other._id == _id && other._rev == _rev
11
+ end
12
+
13
+ def eql?(other)
14
+ self.==(other)
15
+ end
16
+
17
+ def save(validate = true)
18
+ CouchPotato.database.save_document(self, validate)
19
+ end
20
+
21
+ def save!
22
+ CouchPotato.database.save_document!(self)
23
+ end
24
+
25
+ def destroy(override_soft_delete=false)
26
+ check_and_destroy_dependents
27
+ if self.class.soft_deleting_enabled? && !override_soft_delete
28
+ _mark_as_deleted
29
+ else
30
+ self.skip_callbacks = true if self.class.soft_deleting_enabled? && deleted?
31
+ CouchPotato.database.destroy_document(self)
32
+ freeze
33
+ end
34
+ end
35
+ alias :delete :destroy
36
+
37
+ def update_attributes(attributes = {})
38
+ self.attributes = attributes
39
+ save
40
+ end
41
+
42
+ def attributes=(attr)
43
+ super(_remove_protected_attributes(attr))
44
+ end
45
+
46
+ def reload
47
+ instance = self.class.find(_id, :with_deleted => true)
48
+ instance.attributes.each do |attribute, value|
49
+ send "#{attribute}=", value
50
+ end
51
+ self._rev = instance._rev
52
+ reset_dirty_attributes
53
+ reset_association_caches
54
+ self
55
+ end
56
+
57
+ def deleted?
58
+ if self.class.soft_deleting_enabled?
59
+ !send(self.class.soft_delete_attribute).nil?
60
+ else
61
+ false
62
+ end
63
+ end
64
+
65
+ protected
66
+
67
+ def reset_association_caches
68
+ self.class.properties.each do |property|
69
+ if property.respond_to?(:association?) && property.association?
70
+ instance_variable_set("@#{property.name}", nil)
71
+ end
72
+ end
73
+ end
74
+
75
+ def _remove_protected_attributes(attrs)
76
+ return {} if attrs.blank?
77
+ attrs = attrs.dup.stringify_keys
78
+ (self.class.instance_variable_get(:@_protected_attributes) || []).map(&:to_s).each do |protected_attribute|
79
+ attrs.delete(protected_attribute)
80
+ end
81
+
82
+ accessible_attributes = (self.class.instance_variable_get(:@_accessible_attributes) || []).map(&:to_s)
83
+
84
+ if accessible_attributes.present?
85
+ attrs.each do |attr_key, attr_value|
86
+ attrs.delete(attr_key) unless accessible_attributes.include?(attr_key)
87
+ end
88
+ end
89
+
90
+ attrs
91
+ end
92
+
93
+ def check_and_destroy_dependents
94
+ self.class.properties.each do |property|
95
+ if property.respond_to?(:association?) and property.association?
96
+ next unless property.options[:dependent]
97
+ next if property.options[:through]
98
+ dependents = send(property.name, :force_reload => true)
99
+ dependents = [dependents] unless dependents.is_a?(Array)
100
+ dependents.reject{|d| d.nil?}.each do |dependent|
101
+ case property.options[:dependent]
102
+ when :destroy
103
+ dependent.destroy
104
+ else
105
+ unless dependent.class.soft_deleting_enabled?
106
+ dependent.send("#{self.class.foreign_property}=", nil)
107
+ dependent.save(false)
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ def find_one_associated(from, to, options = {})
116
+ options = {
117
+ :limit => 1,
118
+ :descending => true
119
+ }.update(options)
120
+ find_associated(from, to, options).first
121
+ end
122
+
123
+ def find_associated(from, to, options = {})
124
+ if options[:with_deleted]
125
+ CouchPotato.database.view(
126
+ self.class.get_class_from_name(from).send(
127
+ "association_#{from.to_s.singularize.underscore}_belongs_to_#{to.name.singularize.underscore}_with_deleted", :key => id))
128
+ else
129
+ CouchPotato.database.view(
130
+ self.class.get_class_from_name(from).send(
131
+ "association_#{from.to_s.singularize.underscore}_belongs_to_#{to.name.singularize.underscore}", :key => id))
132
+ end
133
+ end
134
+
135
+ def _mark_as_deleted
136
+ run_callbacks(:before_destroy)
137
+ send("#{self.class.soft_delete_attribute}=", Time.now)
138
+ save(false)
139
+ run_callbacks(:after_destroy)
140
+ end
141
+
142
+ end
143
+ end
@@ -0,0 +1,196 @@
1
+ module SimplyStored
2
+ module SimpleDB
3
+ module Associations
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ module ClassMethods
9
+ def belongs_to(klass_name)
10
+ define_belongs_to_getter(klass_name)
11
+ define_belongs_to_setter(klass_name)
12
+ end
13
+
14
+ def has_one(klass_name, options = {})
15
+ options = {
16
+ :clear => :nullify, # or :destroy
17
+ :dependent => :nullify # or :destroy
18
+ }.update(options)
19
+
20
+ define_has_one_getter(klass_name, options)
21
+ define_has_one_setter(klass_name, options)
22
+ define_has_one_dependent_clearing(klass_name, options)
23
+ end
24
+
25
+ def has_many(klass_name, options = {})
26
+ options = {
27
+ :clear => :nullify, # or :destroy
28
+ :dependent => :nullify # or :destroy
29
+ }.update(options)
30
+
31
+ define_has_many_getter(klass_name, options)
32
+ define_has_many_setter_add(klass_name, options)
33
+ define_has_many_setter_remove(klass_name, options)
34
+ define_has_many_setter_remove_all(klass_name, options)
35
+ define_has_many_dependent_clearing(klass_name, options)
36
+ end
37
+
38
+ def define_belongs_to_getter(klass_name)
39
+ define_method klass_name.to_s do
40
+ klass = self.class.get_class_from_name(klass_name)
41
+ cached_version = instance_variable_get("@_cached_belongs_to_#{klass_name}")
42
+ if cached_version.nil? and self["#{klass_name}_id"].present?
43
+ cached_version = klass.find(self.send("#{klass_name}_id"), :auto_load => true)
44
+ instance_variable_set("@_cached_belongs_to_#{klass_name}", cached_version)
45
+ end
46
+ cached_version
47
+ end
48
+ end
49
+
50
+ def define_belongs_to_setter(klass_name)
51
+ define_method "#{klass_name}=" do |val|
52
+ klass = self.class.get_class_from_name(klass_name)
53
+ raise ArgumentError, "expected #{klass} got #{val.class}" unless val.is_a?(klass)
54
+ self.send("#{klass_name}_id=", val.id)
55
+ instance_variable_set("@_cached_belongs_to_#{klass_name}", val)
56
+ end
57
+ end
58
+
59
+ def define_has_one_getter(klass_name, options)
60
+ define_method klass_name.to_s do
61
+ klass = self.class.get_class_from_name(klass_name)
62
+ cached_version = instance_variable_get("@_cached_has_one_#{klass_name}")
63
+ if cached_version
64
+ return cached_version
65
+ else
66
+ cached_version = klass.send("find_by_#{self.class.foreign_key}".to_sym, self.id, {:auto_load => true})
67
+ instance_variable_set("@_cached_has_one_#{klass_name}", cached_version)
68
+ return cached_version
69
+ end
70
+ end
71
+ end
72
+
73
+ def define_has_one_setter(klass_name, options)
74
+ define_method "#{klass_name}=" do |val|
75
+ klass = self.class.get_class_from_name(klass_name)
76
+ raise ArgumentError, "expected #{klass} got #{val.class}" unless val.is_a?(klass)
77
+
78
+ # clear old
79
+ old = self.send("#{klass_name}")
80
+ old.send("#{self.class.foreign_key}=", nil) if old && options[:clear] == :nullify
81
+ old.delete if old && options[:clear] == :destroy
82
+
83
+ # store new
84
+ val.send("#{self.class.foreign_key}=", self.id)
85
+ instance_variable_set("@_cached_has_one_#{klass_name}", val)
86
+ end
87
+ end
88
+
89
+ def define_has_one_dependent_clearing(klass_name, options)
90
+ # add method to list of methods to run when deleted
91
+ @_clear_dependents_after_delete_methods ||= []
92
+ @_clear_dependents_after_delete_methods << "has_one_clear_#{klass_name}_after_destroy"
93
+
94
+ # define actual clearing/deleting
95
+ define_method "has_one_clear_#{klass_name}_after_destroy" do
96
+ klass = self.class.get_class_from_name(klass_name)
97
+ dependent = klass.send("find_by_#{self.class.foreign_key}".to_sym, self.id)
98
+ if options[:dependent] == :nullify
99
+ dependent.send("#{self.class.foreign_key}=", nil) if dependent
100
+ elsif options[:dependent] == :destroy
101
+ dependent.delete if dependent
102
+ else
103
+ raise ArgumentError, "unknown dependent method: #{options[:dependent].inspect}"
104
+ end
105
+ end
106
+ end
107
+
108
+ def define_has_many_getter(klass_name, options)
109
+ define_method klass_name.to_s do
110
+ klass = self.class.get_class_from_name(klass_name)
111
+ cached_version = instance_variable_get("@_cached_has_many_#{klass_name}")
112
+ if cached_version
113
+ return cached_version
114
+ else
115
+ cached_version = klass.send("find_all_by_#{self.class.foreign_key}".to_sym, self.id, {:auto_load => true})
116
+ instance_variable_set("@_cached_has_many_#{klass_name}", cached_version)
117
+ return cached_version
118
+ end
119
+ end
120
+ end
121
+
122
+ def define_has_many_setter_add(klass_name, options)
123
+ define_method "add_#{klass_name.to_s.singularize}" do |val|
124
+ klass = self.class.get_class_from_name(klass_name)
125
+ raise ArgumentError, "expected #{klass} got #{val.class}" unless val.is_a?(klass)
126
+ val.send("#{self.class.foreign_key}=", self.id)
127
+ val.save(false)
128
+ cached_version = instance_variable_get("@_cached_has_many_#{klass_name}") || []
129
+ instance_variable_set("@_cached_has_many_#{klass_name}", cached_version << val)
130
+ end
131
+ end
132
+
133
+ def define_has_many_setter_remove(klass_name, options)
134
+ define_method "remove_#{klass_name.to_s.singularize}" do |val|
135
+ klass = self.class.get_class_from_name(klass_name)
136
+ raise ArgumentError, "expected #{klass} got #{val.class}" unless val.is_a?(klass)
137
+ raise ArgumentError, "cannot remove not mine" unless val.send(self.class.foreign_key.to_sym) == self.id
138
+ if options[:clear] == :nullify
139
+ val.send("#{self.class.foreign_key}=", nil)
140
+ val.save(false)
141
+ elsif options[:clear] == :destroy
142
+ val.delete
143
+ else
144
+ raise "Unknown option for clear: #{option[:clear]}"
145
+ end
146
+ cached_version = instance_variable_get("@_cached_has_many_#{klass_name}") || []
147
+ instance_variable_set("@_cached_has_many_#{klass_name}", cached_version.delete_if{|x| x.id == val.id})
148
+ end
149
+ end
150
+
151
+ def define_has_many_setter_remove_all(klass_name, options)
152
+ define_method "remove_all_#{klass_name}" do
153
+ klass = self.class.get_class_from_name(klass_name)
154
+
155
+ all = klass.send("find_all_by_#{self.class.foreign_key}".to_sym, self.id)
156
+
157
+ all.each do |item|
158
+ self.send("remove_#{klass_name.to_s.singularize}", item)
159
+ end
160
+ instance_variable_set("@_cached_has_many_#{klass_name}", [])
161
+ end
162
+ end
163
+
164
+ def define_has_many_dependent_clearing(klass_name, options)
165
+ # add method to list of methods to run when deleted
166
+ @_clear_dependents_after_delete_methods ||= []
167
+ @_clear_dependents_after_delete_methods << "has_many_clear_#{klass_name}_after_destroy"
168
+
169
+ # define actual clearing/deleting
170
+ define_method "has_many_clear_#{klass_name}_after_destroy" do
171
+ klass = self.class.get_class_from_name(klass_name)
172
+ dependents = klass.send("find_all_by_#{self.class.foreign_key}".to_sym, self.id)
173
+ if options[:dependent] == :nullify
174
+ dependents.each do |dependent|
175
+ dependent.send("#{self.class.foreign_key}=", nil)
176
+ end
177
+ elsif options[:dependent] == :destroy
178
+ dependents.each do |dependent|
179
+ dependent.delete
180
+ end
181
+ else
182
+ raise ArgumentError, "unknown dependent method: #{options[:dependent].inspect}"
183
+ end
184
+ end
185
+ end
186
+ end
187
+
188
+ def clear_dependents_after_delete
189
+ clear_methods = self.class.instance_variable_get("@_clear_dependents_after_delete_methods") || []
190
+ clear_methods.uniq.each do |clear_method|
191
+ self.send(clear_method)
192
+ end
193
+ end
194
+ end
195
+ end
196
+ end