paranoid 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -38,10 +38,10 @@ Automobile.count # => 1
38
38
 
39
39
  that_large_automobile.destroy
40
40
  Automobile.count # => 0
41
- Automobile.count_with_destroyed # => 1
41
+ Automobile.with_destroyed.count # => 1
42
42
 
43
43
  # where is that large automobile?
44
- that_large_automobile = Automobile.find_with_destroyed(:all).first
44
+ that_large_automobile = Automobile.with_destroyed.first
45
45
  that_large_automobile.restore
46
46
  Automobile.count # => 1
47
47
  </pre>
@@ -51,14 +51,24 @@ One thing to note, destroying is always undo-able, but deleting is not. This is
51
51
  <pre>
52
52
  Automobile.destroy_all
53
53
  Automobile.count # => 0
54
- Automobile.count_with_destroyed # => 1
54
+ Automobile.with_destroyed.count # => 1
55
55
 
56
56
  Automobile.delete_all
57
- Automobile.count_with_destroyed # => 0
57
+ Automobile.with_destroyed.count # => 0
58
58
  # And you may say to yourself, "My god! What have I done?"
59
59
  </pre>
60
60
 
61
- Any find/count/sum/etc. _with_destroyed calls should work and you can also do find/count/sum/etc._destroyed_only.
61
+ You can also lookup only destroyed record with with_destroyed_only.
62
+
63
+ <pre>
64
+ auto1 = Automobile.create()
65
+ auto2 = Automobile.create()
66
+ auto2.destroy
67
+ Automobile.count # => 1
68
+ Automobile.with_destroyed.count # => 2
69
+ Automobile.with_destroyed_only.count # => 1
70
+ Automobile.with_destroyed_only.first # => auto2
71
+ </pre>
62
72
 
63
73
  h3. Specifying alternate rules for what should be considered destroyed
64
74
 
data/Rakefile CHANGED
@@ -1,3 +1,4 @@
1
+ require 'rake/rdoctask'
1
2
  require "spec"
2
3
  require "spec/rake/spectask"
3
4
 
@@ -22,4 +23,14 @@ rescue LoadError
22
23
  puts "Jeweler not available. Install it with: sudo gem install jeweler"
23
24
  end
24
25
 
26
+ Rake::RDocTask.new { |rdoc|
27
+ rdoc.rdoc_dir = 'doc'
28
+ rdoc.title = "Paranoid"
29
+ rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
30
+ rdoc.options << '--charset' << 'utf-8'
31
+ rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : './rdoc/template.rb'
32
+ rdoc.rdoc_files.include('README.textile')
33
+ rdoc.rdoc_files.include('lib/**/*.rb')
34
+ }
35
+
25
36
  task :default => :spec
@@ -1,4 +1,5 @@
1
1
  ---
2
- :major: 0
3
2
  :minor: 0
4
- :patch: 1
3
+ :patch: 2
4
+ :major: 0
5
+ :build:
@@ -1,3 +1,4 @@
1
1
  require 'active_record'
2
2
  require 'paranoid/base'
3
+ require 'paranoid/paranoid_methods'
3
4
  require 'paranoid/relation'
@@ -1,5 +1,23 @@
1
1
  module Paranoid
2
2
  module Base
3
+ # Call this in your model to enable paranoid.
4
+ #
5
+ # === Examples
6
+ #
7
+ # Post < ActiveRecord::Base
8
+ # paranoid
9
+ # end
10
+ #
11
+ # Item < ActiveRecord::Base
12
+ # paranoid :field => [:available, fales, true]
13
+ # end
14
+ #
15
+ # === Options
16
+ #
17
+ # [:field]
18
+ # Must be a 3 element array in the form
19
+ # [:field_name, 'destroyed value', 'not destroyed value']
20
+ # Default: [:deleted_at, Proc.new{Time.now.utc}, nil]
3
21
  def paranoid(opts = {})
4
22
  return if paranoid?
5
23
  @paranoid = true
@@ -8,82 +26,20 @@ module Paranoid
8
26
  class_inheritable_accessor :destroyed_field, :field_destroyed, :field_not_destroyed
9
27
  self.destroyed_field, self.field_destroyed, self.field_not_destroyed = opts[:field]
10
28
 
11
- extend ClassMethods
12
- include InstanceMethods
29
+ include Paranoid::ParanoidMethods
13
30
 
14
31
  class_eval do
15
32
  class << self
16
- delegate :with_destroyed, :to => :scoped
33
+ delegate :with_destroyed, :with_destroyed_only, :to => :scoped
17
34
  end
18
35
  end
19
36
  end
20
37
 
38
+ # Returns true if the model is paranoid and paranoid is enabled
21
39
  def paranoid?
22
40
  @paranoid = false unless defined?(@paranoid)
23
41
  @paranoid
24
42
  end
25
-
26
- module ClassMethods
27
- def paranoid_condition
28
- {destroyed_field => field_not_destroyed}
29
- end
30
-
31
- def paranoid_only_condition
32
- ["#{table_name}.#{destroyed_field} IS NOT ?", field_not_destroyed]
33
- end
34
-
35
- def disable_paranoid
36
- if block_given?
37
- @paranoid = false
38
- yield
39
- end
40
- ensure
41
- @paranoid = true
42
- end
43
- end
44
-
45
- module InstanceMethods
46
- extend ActiveSupport::Concern
47
-
48
- included do
49
- alias_method_chain :create_or_update, :paranoid
50
- end
51
-
52
- def restore
53
- set_destroyed(field_not_destroyed.respond_to?(:call) ? field_not_destroyed.call : field_not_destroyed)
54
- @destroyed = false
55
-
56
- self
57
- end
58
-
59
- # Override the default destroy to allow us to soft delete records.
60
- # This preserves the before_destroy and after_destroy callbacks.
61
- # Because this is also called internally by Model.destroy_all and
62
- # the Model.destroy(id), we don't need to specify those methods
63
- # separately.
64
- def destroy
65
- _run_destroy_callbacks do
66
- set_destroyed(field_destroyed.respond_to?(:call) ? field_destroyed.call : field_destroyed)
67
- @destroyed = true
68
- end
69
-
70
- self
71
- end
72
-
73
- protected
74
-
75
- def create_or_update_with_paranoid
76
- self.class.disable_paranoid { create_or_update_without_paranoid }
77
- end
78
-
79
- # Set the value for the destroyed field.
80
- def set_destroyed(val)
81
- self[destroyed_field] = val
82
- updates = self.class.send(:sanitize_sql_for_assignment, {destroyed_field => val})
83
- self.class.unscoped.with_destroyed.where(self.class.arel_table[self.class.primary_key].eq(id)).arel.update(updates)
84
- @destroyed = true
85
- end
86
- end
87
43
  end
88
44
  end
89
45
 
@@ -0,0 +1,79 @@
1
+ module Paranoid
2
+ module ParanoidMethods
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ extend ClassMethods
7
+ alias_method_chain :create_or_update, :paranoid
8
+ end
9
+
10
+ module ClassMethods
11
+ # Returns the condition used to scope the return to exclude
12
+ # soft deleted records
13
+ def paranoid_condition
14
+ {destroyed_field => field_not_destroyed}
15
+ end
16
+
17
+ # Returns the condition used to scope the return to contain
18
+ # only soft deleted records
19
+ def paranoid_only_condition
20
+ val = field_not_destroyed.respond_to?(:call) ? field_not_destroyed.call : field_not_destroyed
21
+ column_sql = arel_table[destroyed_field].to_sql
22
+ case val
23
+ when nil then "#{column_sql} IS NOT NULL"
24
+ else ["#{column_sql} != ?", val]
25
+ end
26
+ end
27
+
28
+ # Temporarily disables paranoid on the model
29
+ def disable_paranoid
30
+ if block_given?
31
+ @paranoid = false
32
+ yield
33
+ else
34
+ raise 'Only block form is supported'
35
+ end
36
+ ensure
37
+ @paranoid = true
38
+ end
39
+ end
40
+
41
+ # Restores the record
42
+ def restore
43
+ set_destroyed(field_not_destroyed.respond_to?(:call) ? field_not_destroyed.call : field_not_destroyed)
44
+ @destroyed = false
45
+
46
+ self
47
+ end
48
+
49
+ # Override the default destroy to allow us to soft delete records.
50
+ # This preserves the before_destroy and after_destroy callbacks.
51
+ # Because this is also called internally by Model.destroy_all and
52
+ # the Model.destroy(id), we don't need to specify those methods
53
+ # separately.
54
+ def destroy
55
+ _run_destroy_callbacks do
56
+ set_destroyed(field_destroyed.respond_to?(:call) ? field_destroyed.call : field_destroyed)
57
+ @destroyed = true
58
+ end
59
+
60
+ self
61
+ end
62
+
63
+ protected
64
+
65
+ # Overrides ActiveRecord::Base#create_or_update
66
+ # to disable paranoid during the create and update operations
67
+ def create_or_update_with_paranoid
68
+ self.class.disable_paranoid { create_or_update_without_paranoid }
69
+ end
70
+
71
+ # Set the value for the destroyed field.
72
+ def set_destroyed(val)
73
+ self[destroyed_field] = val
74
+ updates = self.class.send(:sanitize_sql_for_assignment, {destroyed_field => val})
75
+ self.class.unscoped.with_destroyed.where(self.class.arel_table[self.class.primary_key].eq(id)).arel.update(updates)
76
+ @destroyed = true
77
+ end
78
+ end
79
+ end
@@ -5,13 +5,18 @@ module Paranoid
5
5
  included do
6
6
  alias_method_chain :arel, :paranoid
7
7
  alias_method_chain :delete_all, :paranoid
8
+ alias_method_chain :except, :paranoid
9
+ alias_method_chain :only, :paranoid
8
10
  end
9
11
 
12
+ # Returns true if the relation should be scoped to
13
+ # exclude soft deleted records
10
14
  def add_paranoid_condition?
11
15
  @add_paranoid = true unless defined?(@add_paranoid)
12
16
  @klass.paranoid? && @add_paranoid
13
17
  end
14
18
 
19
+ # Overrides ActiveRecord::Relation#arel
15
20
  def arel_with_paranoid
16
21
  if add_paranoid_condition?
17
22
  @arel ||= without_destroyed.arel_without_paranoid
@@ -20,6 +25,8 @@ module Paranoid
20
25
  end
21
26
  end
22
27
 
28
+ # Overrides ActiveRecord::Relation#delete_all
29
+ # forcing delete_all to ignore deleted flag
23
30
  def delete_all_with_paranoid(*args)
24
31
  if add_paranoid_condition?
25
32
  with_destroyed.delete_all_without_paranoid(*args)
@@ -28,21 +35,46 @@ module Paranoid
28
35
  end
29
36
  end
30
37
 
31
- def skip_paranoid_condition
32
- @add_paranoid = false
38
+ # Overrides ActiveRecord::Relation#except
39
+ def except_with_paranoid(*args)
40
+ result = except_without_paranoid(*args)
41
+ result.instance_variable_set(:@add_paranoid, @add_paranoid) if defined?(@add_paranoid)
42
+ result
33
43
  end
34
44
 
45
+ # Overrides ActiveRecord::Relation#only
46
+ def only_with_paranoid(*args)
47
+ result = only_without_paranoid(*args)
48
+ result.instance_variable_set(:@add_paranoid, @add_paranoid) if defined?(@add_paranoid)
49
+ result
50
+ end
51
+
52
+ # Returns a new relation scoped to include soft deleted records
35
53
  def with_destroyed
36
54
  spawn.tap {|relation| relation.skip_paranoid_condition }
37
55
  end
38
56
 
57
+ # Returns a new relation scoped to include only deleted records
39
58
  def with_destroyed_only
40
59
  where(@klass.paranoid_only_condition).tap {|relation| relation.skip_paranoid_condition }
41
60
  end
42
61
 
62
+ # Can be used to force the exclusion of soft deleted records down
63
+ # the chain from a with_destroyed call. *WARNING*: with_destroyed
64
+ # will do nothing after this has been called! So
65
+ # Model.without_destroyed.with_destroyed.all will *NOT* return
66
+ # soft deleted records
43
67
  def without_destroyed
44
68
  where(@klass.paranoid_condition).tap {|relation| relation.skip_paranoid_condition }
45
69
  end
70
+
71
+ protected
72
+
73
+ # Tell the relation to skip adding the paranoid conditions. DO NOT
74
+ # call directly. Call with_destroyed.
75
+ def skip_paranoid_condition
76
+ @add_paranoid = false
77
+ end
46
78
  end
47
79
  end
48
80
 
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{paranoid}
8
- s.version = "0.0.1"
8
+ s.version = "0.0.2"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["David Genord II"]
12
- s.date = %q{2010-02-18}
12
+ s.date = %q{2010-02-19}
13
13
  s.description = %q{}
14
14
  s.email = %q{github@xspond.com}
15
15
  s.extra_rdoc_files = [
@@ -23,8 +23,10 @@ Gem::Specification.new do |s|
23
23
  "init.rb",
24
24
  "lib/paranoid.rb",
25
25
  "lib/paranoid/base.rb",
26
+ "lib/paranoid/paranoid_methods.rb",
26
27
  "lib/paranoid/relation.rb",
27
28
  "paranoid.gemspec",
29
+ "rdoc/template.rb",
28
30
  "spec/database.yml",
29
31
  "spec/models.rb",
30
32
  "spec/paranoid_spec.rb",
@@ -0,0 +1,613 @@
1
+ # Horo RDoc template
2
+ # Author: Hongli Lai - http://izumi.plan99.net/blog/
3
+ #
4
+ # Based on the Jamis template:
5
+ # http://weblog.jamisbuck.org/2005/4/8/rdoc-template
6
+
7
+ if defined?(RDoc::Diagram)
8
+ RDoc::Diagram.class_eval do
9
+ remove_const(:FONT)
10
+ const_set(:FONT, "\"Bitstream Vera Sans\"")
11
+ end
12
+ end
13
+
14
+ module RDoc
15
+ module Page
16
+
17
+ FONTS = "\"Bitstream Vera Sans\", Verdana, Arial, Helvetica, sans-serif"
18
+
19
+ STYLE = <<CSS
20
+ a {
21
+ color: #00F;
22
+ text-decoration: none;
23
+ }
24
+
25
+ a:hover {
26
+ color: #77F;
27
+ text-decoration: underline;
28
+ }
29
+
30
+ body, td, p {
31
+ font-family: %fonts%;
32
+ background: #FFF;
33
+ color: #000;
34
+ margin: 0px;
35
+ font-size: small;
36
+ }
37
+
38
+ p {
39
+ margin-top: 0.5em;
40
+ margin-bottom: 0.5em;
41
+ }
42
+
43
+ #content {
44
+ margin: 2em;
45
+ margin-left: 3.5em;
46
+ margin-right: 3.5em;
47
+ }
48
+
49
+ #description p {
50
+ margin-bottom: 0.5em;
51
+ }
52
+
53
+ .sectiontitle {
54
+ margin-top: 1em;
55
+ margin-bottom: 1em;
56
+ padding: 0.5em;
57
+ padding-left: 2em;
58
+ background: #005;
59
+ color: #FFF;
60
+ font-weight: bold;
61
+ }
62
+
63
+ .attr-rw {
64
+ padding-left: 1em;
65
+ padding-right: 1em;
66
+ text-align: center;
67
+ color: #055;
68
+ }
69
+
70
+ .attr-name {
71
+ font-weight: bold;
72
+ }
73
+
74
+ .attr-desc {
75
+ }
76
+
77
+ .attr-value {
78
+ font-family: monospace;
79
+ }
80
+
81
+ .file-title-prefix {
82
+ font-size: large;
83
+ }
84
+
85
+ .file-title {
86
+ font-size: large;
87
+ font-weight: bold;
88
+ background: #005;
89
+ color: #FFF;
90
+ }
91
+
92
+ .banner {
93
+ background: #005;
94
+ color: #FFF;
95
+ border: 1px solid black;
96
+ padding: 1em;
97
+ }
98
+
99
+ .banner td {
100
+ background: transparent;
101
+ color: #FFF;
102
+ }
103
+
104
+ h1 a, h2 a, .sectiontitle a, .banner a {
105
+ color: #FF0;
106
+ }
107
+
108
+ h1 a:hover, h2 a:hover, .sectiontitle a:hover, .banner a:hover {
109
+ color: #FF7;
110
+ }
111
+
112
+ .dyn-source {
113
+ display: none;
114
+ background: #fffde8;
115
+ color: #000;
116
+ border: #ffe0bb dotted 1px;
117
+ margin: 0.5em 2em 0.5em 2em;
118
+ padding: 0.5em;
119
+ }
120
+
121
+ .dyn-source .cmt {
122
+ color: #00F;
123
+ font-style: italic;
124
+ }
125
+
126
+ .dyn-source .kw {
127
+ color: #070;
128
+ font-weight: bold;
129
+ }
130
+
131
+ .method {
132
+ margin-left: 1em;
133
+ margin-right: 1em;
134
+ margin-bottom: 1em;
135
+ }
136
+
137
+ .description pre {
138
+ padding: 0.5em;
139
+ border: #ffe0bb dotted 1px;
140
+ background: #fffde8;
141
+ }
142
+
143
+ .method .title {
144
+ font-family: monospace;
145
+ font-size: large;
146
+ border-bottom: 1px dashed black;
147
+ margin-bottom: 0.3em;
148
+ padding-bottom: 0.1em;
149
+ }
150
+
151
+ .method .description, .method .sourcecode {
152
+ margin-left: 1em;
153
+ }
154
+
155
+ .description p, .sourcecode p {
156
+ margin-bottom: 0.5em;
157
+ }
158
+
159
+ .method .sourcecode p.source-link {
160
+ text-indent: 0em;
161
+ margin-top: 0.5em;
162
+ }
163
+
164
+ .method .aka {
165
+ margin-top: 0.3em;
166
+ margin-left: 1em;
167
+ font-style: italic;
168
+ text-indent: 2em;
169
+ }
170
+
171
+ h1 {
172
+ padding: 1em;
173
+ margin-left: -1.5em;
174
+ font-size: x-large;
175
+ font-weight: bold;
176
+ color: #FFF;
177
+ background: #007;
178
+ }
179
+
180
+ h2 {
181
+ padding: 0.5em 1em 0.5em 1em;
182
+ margin-left: -1.5em;
183
+ font-size: large;
184
+ font-weight: bold;
185
+ color: #FFF;
186
+ background: #009;
187
+ }
188
+
189
+ h3, h4, h5, h6 {
190
+ color: #220088;
191
+ border-bottom: #5522bb solid 1px;
192
+ }
193
+
194
+ .sourcecode > pre {
195
+ padding: 0.5em;
196
+ border: 1px dotted black;
197
+ background: #FFE;
198
+ }
199
+
200
+ dt {
201
+ font-weight: bold
202
+ }
203
+
204
+ dd {
205
+ margin-bottom: 0.7em;
206
+ }
207
+ CSS
208
+
209
+ XHTML_PREAMBLE = %{<?xml version="1.0" encoding="%charset%"?>
210
+ <!DOCTYPE html
211
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
212
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
213
+ }
214
+
215
+ XHTML_FRAMESET_PREAMBLE = %{
216
+ <!DOCTYPE html
217
+ PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
218
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
219
+ }
220
+
221
+ HEADER = XHTML_PREAMBLE + <<ENDHEADER
222
+ <html>
223
+ <head>
224
+ <title>%title%</title>
225
+ <meta http-equiv="Content-Type" content="text/html; charset=%charset%" />
226
+ <link rel="stylesheet" href="%style_url%" type="text/css" media="screen" />
227
+
228
+ <script language="JavaScript" type="text/javascript">
229
+ // <![CDATA[
230
+
231
+ function toggleSource( id )
232
+ {
233
+ var elem
234
+ var link
235
+
236
+ if( document.getElementById )
237
+ {
238
+ elem = document.getElementById( id )
239
+ link = document.getElementById( "l_" + id )
240
+ }
241
+ else if ( document.all )
242
+ {
243
+ elem = eval( "document.all." + id )
244
+ link = eval( "document.all.l_" + id )
245
+ }
246
+ else
247
+ return false;
248
+
249
+ if( elem.style.display == "block" )
250
+ {
251
+ elem.style.display = "none"
252
+ link.innerHTML = "show source"
253
+ }
254
+ else
255
+ {
256
+ elem.style.display = "block"
257
+ link.innerHTML = "hide source"
258
+ }
259
+ }
260
+
261
+ function openCode( url )
262
+ {
263
+ window.open( url, "SOURCE_CODE", "resizable=yes,scrollbars=yes,toolbar=no,status=no,height=480,width=750" ).focus();
264
+ }
265
+ // ]]>
266
+ </script>
267
+ </head>
268
+
269
+ <body>
270
+ ENDHEADER
271
+
272
+ FILE_PAGE = <<HTML
273
+ <table border='0' cellpadding='0' cellspacing='0' width="100%" class='banner'>
274
+ <tr><td>
275
+ <table width="100%" border='0' cellpadding='0' cellspacing='0'><tr>
276
+ <td class="file-title" colspan="2"><span class="file-title-prefix">File</span><br />%short_name%</td>
277
+ <td align="right">
278
+ <table border='0' cellspacing="0" cellpadding="2">
279
+ <tr>
280
+ <td>Path:</td>
281
+ <td>%full_path%
282
+ IF:cvsurl
283
+ &nbsp;(<a href="%cvsurl%">CVS</a>)
284
+ ENDIF:cvsurl
285
+ </td>
286
+ </tr>
287
+ <tr>
288
+ <td>Modified:</td>
289
+ <td>%dtm_modified%</td>
290
+ </tr>
291
+ </table>
292
+ </td></tr>
293
+ </table>
294
+ </td></tr>
295
+ </table><br />
296
+ HTML
297
+
298
+ ###################################################################
299
+
300
+ CLASS_PAGE = <<HTML
301
+ <table width="100%" border='0' cellpadding='0' cellspacing='0' class='banner'><tr>
302
+ <td class="file-title"><span class="file-title-prefix">%classmod%</span><br />%full_name%</td>
303
+ <td align="right">
304
+ <table cellspacing="0" cellpadding="2">
305
+ <tr valign="top">
306
+ <td>In:</td>
307
+ <td>
308
+ START:infiles
309
+ HREF:full_path_url:full_path:
310
+ IF:cvsurl
311
+ &nbsp;(<a href="%cvsurl%">CVS</a>)
312
+ ENDIF:cvsurl
313
+ END:infiles
314
+ </td>
315
+ </tr>
316
+ IF:parent
317
+ <tr>
318
+ <td>Parent:</td>
319
+ <td>
320
+ IF:par_url
321
+ <a href="%par_url%">
322
+ ENDIF:par_url
323
+ %parent%
324
+ IF:par_url
325
+ </a>
326
+ ENDIF:par_url
327
+ </td>
328
+ </tr>
329
+ ENDIF:parent
330
+ </table>
331
+ </td>
332
+ </tr>
333
+ </table>
334
+ HTML
335
+
336
+ ###################################################################
337
+
338
+ METHOD_LIST = <<HTML
339
+ <div id="content">
340
+ IF:diagram
341
+ <table cellpadding='0' cellspacing='0' border='0' width="100%"><tr><td align="center">
342
+ %diagram%
343
+ </td></tr></table>
344
+ ENDIF:diagram
345
+
346
+ IF:description
347
+ <div class="description">%description%</div>
348
+ ENDIF:description
349
+
350
+ IF:requires
351
+ <div class="sectiontitle">Required Files</div>
352
+ <ul>
353
+ START:requires
354
+ <li>HREF:aref:name:</li>
355
+ END:requires
356
+ </ul>
357
+ ENDIF:requires
358
+
359
+ IF:toc
360
+ <div class="sectiontitle">Contents</div>
361
+ <ul>
362
+ START:toc
363
+ <li><a href="#%href%">%secname%</a></li>
364
+ END:toc
365
+ </ul>
366
+ ENDIF:toc
367
+
368
+ IF:methods
369
+ <div class="sectiontitle">Methods</div>
370
+ <ul>
371
+ START:methods
372
+ <li>HREF:aref:name:</li>
373
+ END:methods
374
+ </ul>
375
+ ENDIF:methods
376
+
377
+ IF:includes
378
+ <div class="sectiontitle">Included Modules</div>
379
+ <ul>
380
+ START:includes
381
+ <li>HREF:aref:name:</li>
382
+ END:includes
383
+ </ul>
384
+ ENDIF:includes
385
+
386
+ START:sections
387
+ IF:sectitle
388
+ <div class="sectiontitle"><a name="%secsequence%">%sectitle%</a></div>
389
+ IF:seccomment
390
+ <div class="description">
391
+ %seccomment%
392
+ </div>
393
+ ENDIF:seccomment
394
+ ENDIF:sectitle
395
+
396
+ IF:classlist
397
+ <div class="sectiontitle">Classes and Modules</div>
398
+ %classlist%
399
+ ENDIF:classlist
400
+
401
+ IF:constants
402
+ <div class="sectiontitle">Constants</div>
403
+ <table border='0' cellpadding='5'>
404
+ START:constants
405
+ <tr valign='top'>
406
+ <td class="attr-name">%name%</td>
407
+ <td>=</td>
408
+ <td class="attr-value">%value%</td>
409
+ </tr>
410
+ IF:desc
411
+ <tr valign='top'>
412
+ <td>&nbsp;</td>
413
+ <td colspan="2" class="attr-desc">%desc%</td>
414
+ </tr>
415
+ ENDIF:desc
416
+ END:constants
417
+ </table>
418
+ ENDIF:constants
419
+
420
+ IF:attributes
421
+ <div class="sectiontitle">Attributes</div>
422
+ <table border='0' cellpadding='5'>
423
+ START:attributes
424
+ <tr valign='top'>
425
+ <td class='attr-rw'>
426
+ IF:rw
427
+ [%rw%]
428
+ ENDIF:rw
429
+ </td>
430
+ <td class='attr-name'>%name%</td>
431
+ <td class='attr-desc'>%a_desc%</td>
432
+ </tr>
433
+ END:attributes
434
+ </table>
435
+ ENDIF:attributes
436
+
437
+ IF:method_list
438
+ START:method_list
439
+ IF:methods
440
+ <div class="sectiontitle">%type% %category% methods</div>
441
+ START:methods
442
+ <div class="method">
443
+ <div class="title">
444
+ IF:callseq
445
+ <a name="%aref%"></a><b>%callseq%</b>
446
+ ENDIF:callseq
447
+ IFNOT:callseq
448
+ <a name="%aref%"></a><b>%name%</b>%params%
449
+ ENDIF:callseq
450
+ IF:codeurl
451
+ [&nbsp;<a href="%codeurl%" target="SOURCE_CODE" onclick="javascript:openCode('%codeurl%'); return false;">source</a>&nbsp;]
452
+ ENDIF:codeurl
453
+ </div>
454
+ IF:m_desc
455
+ <div class="description">
456
+ %m_desc%
457
+ </div>
458
+ ENDIF:m_desc
459
+ IF:aka
460
+ <div class="aka">
461
+ This method is also aliased as
462
+ START:aka
463
+ <a href="%aref%">%name%</a>
464
+ END:aka
465
+ </div>
466
+ ENDIF:aka
467
+ IF:sourcecode
468
+ <div class="sourcecode">
469
+ <p class="source-link">[ <a href="javascript:toggleSource('%aref%_source')" id="l_%aref%_source">show source</a> ]</p>
470
+ <div id="%aref%_source" class="dyn-source">
471
+ <pre>
472
+ %sourcecode%
473
+ </pre>
474
+ </div>
475
+ </div>
476
+ ENDIF:sourcecode
477
+ </div>
478
+ END:methods
479
+ ENDIF:methods
480
+ END:method_list
481
+ ENDIF:method_list
482
+ END:sections
483
+ </div>
484
+ HTML
485
+
486
+ FOOTER = <<ENDFOOTER
487
+ </body>
488
+ </html>
489
+ ENDFOOTER
490
+
491
+ BODY = HEADER + <<ENDBODY
492
+ !INCLUDE! <!-- banner header -->
493
+
494
+ <div id="bodyContent">
495
+ #{METHOD_LIST}
496
+ </div>
497
+
498
+ #{FOOTER}
499
+ ENDBODY
500
+
501
+ ########################## Source code ##########################
502
+
503
+ SRC_PAGE = XHTML_PREAMBLE + <<HTML
504
+ <html>
505
+ <head><title>%title%</title>
506
+ <meta http-equiv="Content-Type" content="text/html; charset=%charset%" />
507
+ <style type="text/css">
508
+ .ruby-comment { color: green; font-style: italic }
509
+ .ruby-constant { color: #4433aa; font-weight: bold; }
510
+ .ruby-identifier { color: #222222; }
511
+ .ruby-ivar { color: #2233dd; }
512
+ .ruby-keyword { color: #3333FF; font-weight: bold }
513
+ .ruby-node { color: #777777; }
514
+ .ruby-operator { color: #111111; }
515
+ .ruby-regexp { color: #662222; }
516
+ .ruby-value { color: #662222; font-style: italic }
517
+ .kw { color: #3333FF; font-weight: bold }
518
+ .cmt { color: green; font-style: italic }
519
+ .str { color: #662222; font-style: italic }
520
+ .re { color: #662222; }
521
+ </style>
522
+ </head>
523
+ <body bgcolor="white">
524
+ <pre>%code%</pre>
525
+ </body>
526
+ </html>
527
+ HTML
528
+
529
+ ########################## Index ################################
530
+
531
+ FR_INDEX_BODY = <<HTML
532
+ !INCLUDE!
533
+ HTML
534
+
535
+ FILE_INDEX = XHTML_PREAMBLE + <<HTML
536
+ <html>
537
+ <head>
538
+ <meta http-equiv="Content-Type" content="text/html; charset=%charset%" />
539
+ <title>Index</title>
540
+ <style type="text/css">
541
+ <!--
542
+ body {
543
+ background-color: #EEE;
544
+ font-family: #{FONTS};
545
+ color: #000;
546
+ margin: 0px;
547
+ }
548
+ .banner {
549
+ background: #005;
550
+ color: #FFF;
551
+ padding: 0.2em;
552
+ font-size: small;
553
+ font-weight: bold;
554
+ text-align: center;
555
+ }
556
+ .entries {
557
+ margin: 0.25em 1em 0 1em;
558
+ font-size: x-small;
559
+ }
560
+ a {
561
+ color: #00F;
562
+ text-decoration: none;
563
+ white-space: nowrap;
564
+ }
565
+ a:hover {
566
+ color: #77F;
567
+ text-decoration: underline;
568
+ }
569
+ -->
570
+ </style>
571
+ <base target="docwin" />
572
+ </head>
573
+ <body>
574
+ <div class="banner">%list_title%</div>
575
+ <div class="entries">
576
+ START:entries
577
+ <a href="%href%">%name%</a><br />
578
+ END:entries
579
+ </div>
580
+ </body></html>
581
+ HTML
582
+
583
+ CLASS_INDEX = FILE_INDEX
584
+ METHOD_INDEX = FILE_INDEX
585
+
586
+ INDEX = XHTML_FRAMESET_PREAMBLE + <<HTML
587
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
588
+ <head>
589
+ <title>%title%</title>
590
+ <meta http-equiv="Content-Type" content="text/html; charset=%charset%" />
591
+ </head>
592
+
593
+ <frameset cols="20%,*">
594
+ <frameset rows="15%,55%,30%">
595
+ <frame src="fr_file_index.html" title="Files" name="Files" />
596
+ <frame src="fr_class_index.html" name="Classes" />
597
+ <frame src="fr_method_index.html" name="Methods" />
598
+ </frameset>
599
+ <frame src="%initial_page%" name="docwin" />
600
+ <noframes>
601
+ <body bgcolor="white">
602
+ Click <a href="html/index.html">here</a> for a non-frames
603
+ version of this page.
604
+ </body>
605
+ </noframes>
606
+ </frameset>
607
+
608
+ </html>
609
+ HTML
610
+
611
+ end
612
+ end
613
+
@@ -5,81 +5,108 @@ LUKE = 'Luke Skywalker'
5
5
 
6
6
  describe Paranoid do
7
7
  before(:each) do
8
- Sticker.delete_all
9
- Place.delete_all
10
- Android.delete_all
11
- Person.delete_all
12
- Component.delete_all
13
-
14
- @luke = Person.create(:name => LUKE)
15
- @r2d2 = Android.create(:name => 'R2D2', :owner_id => @luke.id)
16
- @c3p0 = Android.create(:name => 'C3P0', :owner_id => @luke.id)
17
-
18
- @r2d2.components.create(:name => 'Rotors')
19
-
20
- @r2d2.memories.create(:name => 'A pretty sunset')
21
- @c3p0.sticker = Sticker.create(:name => 'OMG, PONIES!')
22
- @tatooine = Place.create(:name => "Tatooine")
23
- @r2d2.places << @tatooine
8
+ # Sticker.delete_all
9
+ # Place.delete_all
10
+ # Android.delete_all
11
+ # Person.delete_all
12
+ # Component.delete_all
13
+ #
14
+ # @luke = Person.create(:name => LUKE)
15
+ # @r2d2 = Android.create(:name => 'R2D2', :owner_id => @luke.id)
16
+ # @c3p0 = Android.create(:name => 'C3P0', :owner_id => @luke.id)
17
+ #
18
+ # @r2d2.components.create(:name => 'Rotors')
19
+ #
20
+ # @r2d2.memories.create(:name => 'A pretty sunset')
21
+ # @c3p0.sticker = Sticker.create(:name => 'OMG, PONIES!')
22
+ # @tatooine = Place.create(:name => "Tatooine")
23
+ # @r2d2.places << @tatooine
24
24
  end
25
25
 
26
- it 'should recognize a class as paranoid' do
27
- Person.paranoid?.should be_false
28
- Place.paranoid?.should be_true
29
- end
26
+ describe 'basic functionality' do
27
+ before(:each) do
28
+ Place.delete_all
29
+ @tatooine, @mos_eisley, @coruscant = Place.create([{:name => "Tatooine"}, {:name => 'Mos Eisley'}, {:name => 'Coruscant'}])
30
+ end
30
31
 
31
- it 'should hide destroyed records' do
32
- @tatooine.update_attribute('deleted_at', Time.now)
33
- Place.first(:conditions => {:name => 'Tatooine'}).should be_nil
34
- end
32
+ it 'should recognize a class as paranoid' do
33
+ Person.paranoid?.should be_false
34
+ Place.paranoid?.should be_true
35
+ end
35
36
 
36
- it 'should reveal destroyed records when with_destroyed' do
37
- @tatooine.update_attribute('deleted_at', Time.now)
38
- Place.with_destroyed.first(:conditions => {:name => 'Tatooine'}).should_not be_nil
39
- end
37
+ it 'should hide destroyed records' do
38
+ @tatooine.update_attribute('deleted_at', Time.now)
39
+ Place.first(:conditions => {:name => 'Tatooine'}).should be_nil
40
+ end
40
41
 
41
- it 'should restore the destroyed record' do
42
- @tatooine.update_attribute('deleted_at', Time.now)
42
+ it 'should reveal destroyed records when with_destroyed' do
43
+ @tatooine.update_attribute('deleted_at', Time.now)
44
+ Place.with_destroyed.first(:conditions => {:name => 'Tatooine'}).should_not be_nil
45
+ end
43
46
 
44
- @tatooine = Place.with_destroyed.first(:conditions => {:name => 'Tatooine'})
45
- @tatooine.restore
47
+ it 'should restore the destroyed record' do
48
+ @tatooine.update_attribute('deleted_at', Time.now)
46
49
 
47
- Place.first(:conditions => {:name => 'Tatooine'}).should_not be_nil
48
- end
50
+ @tatooine = Place.with_destroyed.first(:conditions => {:name => 'Tatooine'})
51
+ @tatooine.restore
49
52
 
50
- it 'should soft delete paranoid records' do
51
- @tatooine.destroy
53
+ Place.first(:conditions => {:name => 'Tatooine'}).should_not be_nil
54
+ end
52
55
 
53
- record = Place.with_destroyed.first(:conditions => {:name => 'Tatooine'})
54
- record.should_not be_nil
55
- record.deleted_at.should_not be_nil
56
- end
56
+ it 'should soft delete paranoid records' do
57
+ @tatooine.destroy
57
58
 
58
- it 'should mark the record destroyed' do
59
- @tatooine.destroy
60
- @tatooine.destroyed?.should be_true
61
- end
59
+ record = Place.with_destroyed.first(:conditions => {:name => 'Tatooine'})
60
+ record.should_not be_nil
61
+ record.deleted_at.should_not be_nil
62
+ end
62
63
 
63
- it 'should set the deleted_field' do
64
- @tatooine.destroy
65
- @tatooine.deleted_at.should_not be_nil
64
+ it 'should mark the record destroyed' do
65
+ @tatooine.destroy
66
+ @tatooine.destroyed?.should be_true
67
+ end
68
+
69
+ it 'should set the deleted_field' do
70
+ @tatooine.destroy
71
+ @tatooine.deleted_at.should_not be_nil
72
+ end
73
+
74
+ it 'should show deleted_only' do
75
+ @tatooine.destroy
76
+ destroyed = Place.with_destroyed_only.all
77
+ destroyed.size.should == 1
78
+ destroyed[0].should == @tatooine
79
+ end
80
+
81
+ it 'should properly count records' do
82
+ Place.count.should == 3
83
+
84
+ @tatooine.destroy
85
+ Place.count.should == 2
86
+ Place.with_destroyed.count.should == 3
87
+ Place.with_destroyed_only.count.should == 1
88
+ end
66
89
  end
67
90
 
68
91
  describe 'for alternate field information' do
69
92
  before(:each) do
70
93
  Ninja.delete_all
71
- @ninja = Ninja.create(:name => 'Steve')
94
+ @steve, @bob, @tim = Ninja.create([{:name => 'Steve', :visible => true}, {:name => 'Bob', :visible => true}, {:name => 'Tim', :visible => true}])
95
+ end
96
+
97
+ it 'should have 3 visible ninjas' do
98
+ Ninja.all.size.should == 3
72
99
  end
73
100
 
74
101
  it 'should vanish the ninja' do
75
- @ninja.destroy
102
+ @steve.destroy
76
103
 
77
104
  record = Ninja.first(:conditions => {:name => 'Steve'})
78
105
  record.should be_nil
79
106
  end
80
107
 
81
108
  it 'should not delete the ninja' do
82
- @ninja.destroy
109
+ @steve.destroy
83
110
 
84
111
  record = Ninja.with_destroyed.first(:conditions => {:name => 'Steve'})
85
112
  record.should_not be_nil
@@ -87,13 +114,29 @@ describe Paranoid do
87
114
  end
88
115
 
89
116
  it 'should mark the ninja vanished' do
90
- @ninja.destroy
91
- @ninja.destroyed?.should be_true
117
+ @steve.destroy
118
+ @steve.destroyed?.should be_true
92
119
  end
93
120
 
94
121
  it 'should set visible to false' do
95
- @ninja.destroy
96
- @ninja.visible.should be_false
122
+ @steve.destroy
123
+ @steve.visible.should be_false
124
+ end
125
+
126
+ it 'should show deleted_only' do
127
+ @steve.destroy
128
+ destroyed = Ninja.with_destroyed_only.all
129
+ destroyed.size.should == 1
130
+ destroyed[0].should == @steve
131
+ end
132
+
133
+ it 'should properly count records' do
134
+ Ninja.count.should == 3
135
+
136
+ @steve.destroy
137
+ Ninja.count.should == 2
138
+ Ninja.with_destroyed.count.should == 3
139
+ Ninja.with_destroyed_only.count.should == 1
97
140
  end
98
141
  end
99
142
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: paranoid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Genord II
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2010-02-18 00:00:00 -05:00
12
+ date: 2010-02-19 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -38,8 +38,10 @@ files:
38
38
  - init.rb
39
39
  - lib/paranoid.rb
40
40
  - lib/paranoid/base.rb
41
+ - lib/paranoid/paranoid_methods.rb
41
42
  - lib/paranoid/relation.rb
42
43
  - paranoid.gemspec
44
+ - rdoc/template.rb
43
45
  - spec/database.yml
44
46
  - spec/models.rb
45
47
  - spec/paranoid_spec.rb