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