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