paranoid 0.0.1 → 0.0.2

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