simply_stored 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/simply_stored/class_methods_base.rb +31 -0
- data/lib/simply_stored/couch/belongs_to.rb +117 -0
- data/lib/simply_stored/couch/ext/couch_potato.rb +16 -0
- data/lib/simply_stored/couch/has_many.rb +148 -0
- data/lib/simply_stored/couch/has_one.rb +93 -0
- data/lib/simply_stored/couch/validations.rb +74 -0
- data/lib/simply_stored/couch/views/array_property_view_spec.rb +22 -0
- data/lib/simply_stored/couch/views.rb +1 -0
- data/lib/simply_stored/couch.rb +278 -0
- data/lib/simply_stored/instance_methods.rb +143 -0
- data/lib/simply_stored/simpledb/associations.rb +196 -0
- data/lib/simply_stored/simpledb/attributes.rb +173 -0
- data/lib/simply_stored/simpledb/storag.rb +85 -0
- data/lib/simply_stored/simpledb/validations.rb +88 -0
- data/lib/simply_stored/simpledb.rb +212 -0
- data/lib/simply_stored/storage.rb +93 -0
- data/lib/simply_stored.rb +9 -0
- data/test/custom_views_test.rb +33 -0
- data/test/fixtures/couch.rb +182 -0
- data/test/fixtures/simpledb/item.rb +11 -0
- data/test/fixtures/simpledb/item_daddy.rb +8 -0
- data/test/fixtures/simpledb/log_item.rb +3 -0
- data/test/fixtures/simpledb/namespace_bar.rb +5 -0
- data/test/fixtures/simpledb/namespace_foo.rb +7 -0
- data/test/fixtures/simpledb/protected_item.rb +3 -0
- data/test/simply_stored_couch_test.rb +1684 -0
- data/test/simply_stored_simpledb_test.rb +1341 -0
- data/test/test_helper.rb +22 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/dot/dot.rb +29 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/evaluator/evaluator.rb +133 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/closure_hash.rb +15 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/grammar.rb +240 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/grammar_symbol.rb +27 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/precedence.rb +19 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/production.rb +36 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/accept_actions.rb +36 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/alphabet.rb +21 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/compiled_lexer.rb +46 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/dfa.rb +121 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/lexeme.rb +32 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/lexer.rb +70 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/lexer_run.rb +78 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/regex_grammar.rb +392 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/regex_parser.rb +2010 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/regex_tokenizer.rb +14 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/specification.rb +96 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/state.rb +68 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/state_machine.rb +37 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/action.rb +55 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/channel.rb +58 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/compiled_parser.rb +51 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/conflict.rb +54 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/item.rb +42 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parse_result.rb +50 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parse_tree.rb +66 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parser.rb +165 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parser_methods.rb +11 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parser_run.rb +39 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parser_state.rb +74 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/token.rb +22 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/runtime.rb +51 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/tokenizer/tokenizer.rb +190 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka.rb +62 -0
- data/test/vendor/dhaka-2.2.1/test/all_tests.rb +5 -0
- data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_evaluator.rb +64 -0
- data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_evaluator_test.rb +43 -0
- data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_grammar.rb +41 -0
- data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_grammar_test.rb +9 -0
- data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_test_methods.rb +9 -0
- data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_tokenizer.rb +39 -0
- data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_tokenizer_test.rb +38 -0
- data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_evaluator.rb +43 -0
- data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_grammar.rb +24 -0
- data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_grammar_test.rb +30 -0
- data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_lexer_specification.rb +23 -0
- data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_parser_test.rb +33 -0
- data/test/vendor/dhaka-2.2.1/test/brackets/bracket_grammar.rb +23 -0
- data/test/vendor/dhaka-2.2.1/test/brackets/bracket_tokenizer.rb +22 -0
- data/test/vendor/dhaka-2.2.1/test/brackets/brackets_test.rb +28 -0
- data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_driver.rb +46 -0
- data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_driver_test.rb +276 -0
- data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_evaluator.rb +284 -0
- data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_evaluator_test.rb +38 -0
- data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_grammar.rb +104 -0
- data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_lexer.rb +109 -0
- data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_lexer_specification.rb +37 -0
- data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_lexer_test.rb +58 -0
- data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_parser.rb +879 -0
- data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_parser_test.rb +55 -0
- data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_test.rb +170 -0
- data/test/vendor/dhaka-2.2.1/test/core/another_lalr_but_not_slr_grammar.rb +20 -0
- data/test/vendor/dhaka-2.2.1/test/core/compiled_parser_test.rb +44 -0
- data/test/vendor/dhaka-2.2.1/test/core/dfa_test.rb +170 -0
- data/test/vendor/dhaka-2.2.1/test/core/evaluator_test.rb +22 -0
- data/test/vendor/dhaka-2.2.1/test/core/grammar_test.rb +83 -0
- data/test/vendor/dhaka-2.2.1/test/core/lalr_but_not_slr_grammar.rb +19 -0
- data/test/vendor/dhaka-2.2.1/test/core/lexer_test.rb +139 -0
- data/test/vendor/dhaka-2.2.1/test/core/malformed_grammar.rb +7 -0
- data/test/vendor/dhaka-2.2.1/test/core/malformed_grammar_test.rb +8 -0
- data/test/vendor/dhaka-2.2.1/test/core/nullable_grammar.rb +21 -0
- data/test/vendor/dhaka-2.2.1/test/core/parse_result_test.rb +44 -0
- data/test/vendor/dhaka-2.2.1/test/core/parser_state_test.rb +24 -0
- data/test/vendor/dhaka-2.2.1/test/core/parser_test.rb +131 -0
- data/test/vendor/dhaka-2.2.1/test/core/precedence_grammar.rb +17 -0
- data/test/vendor/dhaka-2.2.1/test/core/precedence_grammar_test.rb +9 -0
- data/test/vendor/dhaka-2.2.1/test/core/rr_conflict_grammar.rb +21 -0
- data/test/vendor/dhaka-2.2.1/test/core/simple_grammar.rb +22 -0
- data/test/vendor/dhaka-2.2.1/test/core/sr_conflict_grammar.rb +16 -0
- data/test/vendor/dhaka-2.2.1/test/dhaka_test_helper.rb +17 -0
- data/test/vendor/dhaka-2.2.1/test/fake_logger.rb +17 -0
- data/test/vendor/simplerdb-0.2/lib/simplerdb/client_exception.rb +10 -0
- data/test/vendor/simplerdb-0.2/lib/simplerdb/db.rb +146 -0
- data/test/vendor/simplerdb-0.2/lib/simplerdb/query_language.rb +266 -0
- data/test/vendor/simplerdb-0.2/lib/simplerdb/server.rb +33 -0
- data/test/vendor/simplerdb-0.2/lib/simplerdb/servlet.rb +191 -0
- data/test/vendor/simplerdb-0.2/lib/simplerdb.rb +3 -0
- data/test/vendor/simplerdb-0.2/test/functional_test.rb +81 -0
- data/test/vendor/simplerdb-0.2/test/query_evaluator_test.rb +73 -0
- data/test/vendor/simplerdb-0.2/test/query_parser_test.rb +64 -0
- data/test/vendor/simplerdb-0.2/test/simplerdb_test.rb +80 -0
- metadata +182 -0
@@ -0,0 +1,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
|