expiration-date 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,11 @@
1
+ == 1.1.0 / 2008-08-25
2
+
3
+ * 1 minor enhancement
4
+ * added expiring class attributes
5
+
1
6
  == 1.0.1 / 2008-08-23
2
7
 
3
- - 1 bug fix
8
+ * 1 bug fix
4
9
  * ensuring the block executes in the context of the
5
10
  instance and not the class
6
11
 
@@ -1,6 +1,7 @@
1
+ .gitignore
1
2
  History.txt
2
3
  Manifest.txt
3
- README.txt
4
+ README.rdoc
4
5
  Rakefile
5
6
  lib/expiration-date.rb
6
7
  spec/expiration-date_spec.rb
@@ -5,36 +5,17 @@
5
5
 
6
6
  == DESCRIPTION:
7
7
 
8
- Wandering the grocery aisle the other day I saw a package of bacon with a neon
9
- green sticker screaming for all to hear "THIS BACON ONLY COSTS ONE DOLLAR!!!".
10
- Screaming bacon always intrigues me, so I grabbed the nearest store manager. He
11
- works at the coffee shop next door, but when I told him about the screaming
12
- bacon he had to come and see for himself.
13
-
14
- "Oh, it's a manager's special" he told me. "When products get too old the
15
- manager will reduce the price so they sell more quickly. This allows new
16
- products to be put on the shelves, and the store can make some money instead of
17
- throwing out the expired products".
18
-
19
- I stared at him blankly. He wandered back to the coffee shop and had a latte.
20
-
21
- I continued to stand there thinking about expiring products and how Ruby could
22
- benefit from neon green stickers and stale bacon. Eventually the grocery
23
- store manager came by and asked me if everything was okay. I grabbed him by the
24
- collar, pointed at the bacon and yelled "DO YOU KNOW WHAT THIS MEANS!?!?". I
25
- ran from the store, grabbed my laptop, and whipped up this little gem.
8
+ Really simple caching by attaching an expiration date to attributes.
26
9
 
27
- EXPIRATION DATE (now with more neon green).
10
+ The ExpirationDate module adss two methods to a class -- expiring_attr and
11
+ expiring_class_attr. These two methods are used to declare attributes in the
12
+ instance and in the class, respectively, that will expire after some period of
13
+ seconds have elapsed. The attribute is re-initialized from the given block
14
+ after it has expired. This is a very simple form of caching.
28
15
 
29
- Now ruby can expire it's bacon, too, just like the grocery store, and make room
30
- for more bacon from the delivery truck.
31
16
 
32
17
  == SYNOPSIS:
33
18
 
34
- The ExpirationDate module adds the "expiring_attr" method to a class. This
35
- method is used to define an attribute that will expire after some period of
36
- seconds have elapsed.
37
-
38
19
  A simple example demonstrating how the block gets called after the expiration
39
20
  time is passed.
40
21
 
@@ -45,14 +26,9 @@ time is passed.
45
26
 
46
27
  a = A.new
47
28
  a.foo #=> 'foo'
48
- a.foo.object_id #=> 123456
49
- a.foo.object_id #=> 123456
50
-
51
- sleep 61
52
- a.foo.object_id #=> 654321
53
-
54
29
  a.foo = 'bar'
55
30
  a.foo #=> 'bar'
31
+
56
32
  sleep 61
57
33
  a.foo #=> 'foo'
58
34
 
@@ -62,13 +38,15 @@ database every five minutes. This assumes you have the 'activesupport' and
62
38
 
63
39
  class MyModel < ::ActiveRecord::Base
64
40
  include ExpirationDate
65
- expiring_attr( :costly_data, 5.minutes ) {
41
+ expiring_class_attr( :costly_data, 5.minutes ) {
66
42
  models = MyModel.find( :all, :conditions => ['costly query conditions'] )
67
43
  result = models.map {|m| # costly operations here}
68
44
  result
69
45
  }
70
46
  end
71
47
 
48
+ MyModel.costly_data #=> result
49
+
72
50
  Attributes can be expired manually, and the time it takes them to expire can be
73
51
  modified as well.
74
52
 
@@ -83,11 +61,11 @@ modified as well.
83
61
  sleep 60
84
62
  demo.bar #=> 60 seconds ago
85
63
 
86
- demo.expire_now(:bar)
64
+ demo.expire_bar_now
87
65
  demo.bar #=> now
88
66
 
89
- demo.alter_expiration_label(:bar, 10)
90
- demo.expire_now(:bar)
67
+ demo.alter_bar_age( 10 )
68
+ demo.expire_bar_now
91
69
  demo.bar #=> now
92
70
  sleep 11
93
71
  demo.bar #=> now
@@ -100,6 +78,32 @@ This is a pure ruby library. There are no requirements for using this code.
100
78
 
101
79
  sudo gem install expiration-date
102
80
 
81
+ == FUN STORY:
82
+
83
+ Wandering the grocery aisle the other day I saw a package of bacon with a neon
84
+ green sticker screaming for all to hear "THIS BACON ONLY COSTS ONE DOLLAR!!!".
85
+ Screaming bacon always intrigues me, so I grabbed the nearest store manager. He
86
+ works at the coffee shop next door, but when I told him about the screaming
87
+ bacon he had to come and see for himself.
88
+
89
+ "Oh, it's a manager's special" he told me. "When products get too old the
90
+ manager will reduce the price so they sell more quickly. This allows new
91
+ products to be put on the shelves, and the store can make some money instead of
92
+ throwing out the expired products".
93
+
94
+ I stared at him blankly. He wandered back to the coffee shop and had a latte.
95
+
96
+ I continued to stand there thinking about expiring products and how Ruby could
97
+ benefit from neon green stickers and stale bacon. Eventually the grocery
98
+ store manager came by and asked me if everything was okay. I grabbed him by the
99
+ collar, pointed at the bacon and yelled "DO YOU KNOW WHAT THIS MEANS!?!?". I
100
+ ran from the store, grabbed my laptop, and whipped up this little gem.
101
+
102
+ EXPIRATION DATE (now with more neon green).
103
+
104
+ Now ruby can expire it's bacon, too, just like the grocery store, and make room
105
+ for more bacon from the delivery truck.
106
+
103
107
  == LICENSE:
104
108
 
105
109
  The MIT License
data/Rakefile CHANGED
@@ -14,7 +14,9 @@ PROJ.url = 'http://codeforpeople.rubyforge.org/expiration-date'
14
14
  PROJ.rubyforge.name = 'codeforpeople'
15
15
  PROJ.rdoc.remote_dir = 'expiration-date'
16
16
  PROJ.version = ExpirationDate::VERSION
17
- PROJ.release_name = 'Buggy Bacon'
17
+ PROJ.release_name = 'Screaming Bacon'
18
+ PROJ.readme_file = 'README.rdoc'
19
+ PROJ.rdoc.include << 'README.rdoc'
18
20
 
19
21
  PROJ.spec.opts << '--color'
20
22
 
@@ -4,7 +4,7 @@ require 'thread'
4
4
  module ExpirationDate
5
5
 
6
6
  # :stopdoc:
7
- VERSION = '1.0.1'
7
+ VERSION = '1.1.0'
8
8
  ExpirationLabel = Struct.new(:mutex, :age, :expires_on)
9
9
  # :startdoc:
10
10
 
@@ -28,7 +28,9 @@ module ExpirationDate
28
28
  # Declares a new instance attribute that will expire after _age_ seconds
29
29
  # and be replaced by the results of running the _block_. The block is
30
30
  # lazily evaluated when the attribute is accessed after the expiration
31
- # time.
31
+ # time. The block is evaluated in the context of the instance (as
32
+ # opposed to being evaluated in the context of the class where the block
33
+ # is declared).
32
34
  #
33
35
  # Obviously this scheme will only work if the attribute is only accessed
34
36
  # using the setter and getter methods defined by this function.
@@ -39,9 +41,11 @@ module ExpirationDate
39
41
  # end
40
42
  #
41
43
  # a = A.new
42
- # a.foo.object_id #=> 123456
44
+ # a.foo #=> 'foo'
45
+ # a.foo = 'bar'
46
+ # a.foo #=> 'bar'
43
47
  # sleep 61
44
- # a.foo.object_id #=> 654321
48
+ # a.foo #=> 'foo'
45
49
  #
46
50
  def expiring_attr( name, age, &block )
47
51
  raise ArgumentError, "a block must be given" if block.nil?
@@ -53,7 +57,7 @@ module ExpirationDate
53
57
  self.class_eval <<-CODE, __FILE__, __LINE__
54
58
  def #{name}
55
59
  now = Time.now
56
- label = expiration_labels[#{name.inspect}]
60
+ label = _expiration_labels[#{name.inspect}]
57
61
  if label.expires_on.nil? || now >= label.expires_on
58
62
  label.mutex.synchronize {
59
63
  break unless label.expires_on.nil? || now >= label.expires_on
@@ -68,7 +72,7 @@ module ExpirationDate
68
72
 
69
73
  def #{name}=( val )
70
74
  now = Time.now
71
- label = expiration_labels[#{name.inspect}]
75
+ label = _expiration_labels[#{name.inspect}]
72
76
  label.mutex.synchronize {
73
77
  @#{name} = val
74
78
  label.age ||= #{age}
@@ -76,6 +80,83 @@ module ExpirationDate
76
80
  }
77
81
  @#{name}
78
82
  end
83
+
84
+ def expire_#{name}_now
85
+ label = _expiration_labels[#{name.inspect}]
86
+ label.mutex.synchronize {label.expires_on = Time.now - 1}
87
+ @#{name}
88
+ end
89
+
90
+ def alter_#{name}_age( age )
91
+ label = _expiration_labels[#{name.inspect}]
92
+ label.mutex.synchronize {label.age = Float(age)}
93
+ end
94
+ CODE
95
+ end
96
+
97
+ # Declares a new class attribute that will expire after _age_ seconds
98
+ # and be replaced by the results of running the _block_. The block is
99
+ # lazily evaluated when the attribute is accessed after the expiration
100
+ # time. The block is evaluated in the context of the class.
101
+ #
102
+ # Obviously this scheme will only work if the attribute is only accessed
103
+ # using the setter and getter methods defined by this function.
104
+ #
105
+ # class A
106
+ # include ExpirationDate
107
+ # expiring_class_attr( :foo, 60 ) { 'foo' }
108
+ # end
109
+ #
110
+ # A.foo #=> 'foo'
111
+ # A.foo = 'bar'
112
+ # A.foo #=> 'bar'
113
+ # sleep 61
114
+ # A.foo #=> 'foo'
115
+ #
116
+ def expiring_class_attr( name, age, &block )
117
+ raise ArgumentError, "a block must be given" if block.nil?
118
+
119
+ name = name.to_sym
120
+ age = Float(age)
121
+ _class_managers_specials[name] = block
122
+
123
+ self.class_eval <<-CODE, __FILE__, __LINE__
124
+ def self.#{name}
125
+ now = Time.now
126
+ label = _class_expiration_labels[#{name.inspect}]
127
+ if label.expires_on.nil? || now >= label.expires_on
128
+ label.mutex.synchronize {
129
+ break unless label.expires_on.nil? || now >= label.expires_on
130
+ block = ::#{self.name}._class_managers_specials[#{name.inspect}]
131
+ @#{name} = instance_eval(&block)
132
+ label.age ||= #{age}
133
+ label.expires_on = now + label.age
134
+ }
135
+ end
136
+ @#{name}
137
+ end
138
+
139
+ def self.#{name}=( val )
140
+ now = Time.now
141
+ label = _class_expiration_labels[#{name.inspect}]
142
+ label.mutex.synchronize {
143
+ @#{name} = val
144
+ label.age ||= #{age}
145
+ label.expires_on = now + label.age
146
+ }
147
+ @#{name}
148
+ end
149
+
150
+ def self.expire_#{name}_now
151
+ label = _class_expiration_labels[#{name.inspect}]
152
+ label.mutex.synchronize {label.expires_on = Time.now - 1}
153
+ @#{name}
154
+ end
155
+
156
+ def self.alter_#{name}_age( age )
157
+ label = _class_expiration_labels[#{name.inspect}]
158
+ label.mutex.synchronize {label.age = Float(age)}
159
+ end
79
160
  CODE
80
161
  end
81
162
 
@@ -86,55 +167,23 @@ module ExpirationDate
86
167
  def _managers_specials
87
168
  @_managers_specials ||= Hash.new
88
169
  end
89
- # :startdoc:
90
- end
91
170
 
92
- # Immediately expire an attribute so that it will be refreshed the next
93
- # time it is requested.
94
- #
95
- # expire_now( :foo )
96
- #
97
- def expire_now( name )
98
- name = name.to_sym
99
- if expiration_labels.key?(name)
100
- now = Time.now
101
- label = expiration_labels[name]
102
- label.mutex.synchronize {
103
- label.expires_on = now - 1
104
- }
171
+ def _class_managers_specials
172
+ @_class_managers_specials ||= Hash.new
105
173
  end
106
- end
107
174
 
108
- # Alter the _age_ of the named attribute. This new age will be used the
109
- # next time the attribute expries to determine the new expiration date --
110
- # i.e. the new age does not immediately take effect. You will need to
111
- # manually expire the attribute if you want the new age to take effect
112
- # immediately.
113
- #
114
- # Modify the 'foo' attribute so that it expires every 60 seconds.
115
- #
116
- # alter_expiration_label( :foo, 60 )
117
- #
118
- # Modify the 'bar' attribute so that it expires every two minutes. Make
119
- # this new age take effect immediately.
120
- #
121
- # alter_expiration_label( :bar, 120 )
122
- # expire_now( :bar )
123
- #
124
- def alter_expiration_label( name, age )
125
- name = name.to_sym
126
- if expiration_labels.key?(name)
127
- label = expiration_labels[name]
128
- label.mutex.synchronize {
129
- label.age = Float(age)
130
- }
175
+ def _class_expiration_labels
176
+ @_class_expiration_labels ||= Hash.new do |h,k|
177
+ h[k] = ExpirationLabel.new(Mutex.new)
178
+ end
131
179
  end
180
+ # :startdoc:
132
181
  end
133
182
 
134
183
  # Accessor that returns the hash of ExpirationLabel objects.
135
184
  #
136
- def expiration_labels
137
- @expiration_labels ||= Hash.new do |h,k|
185
+ def _expiration_labels
186
+ @_expiration_labels ||= Hash.new do |h,k|
138
187
  h[k] = ExpirationLabel.new(Mutex.new)
139
188
  end
140
189
  end
@@ -7,8 +7,11 @@ class TestA
7
7
  expiring_attr(:foo, 0.5) { 'foo' }
8
8
  expiring_attr('bar', 10 ) { 'bar' }
9
9
  expiring_attr(:me, 60 ) { self }
10
- end
11
10
 
11
+ expiring_class_attr(:foo, 0.5) { 'class foo' }
12
+ expiring_class_attr('bar', 10 ) { 'class bar' }
13
+ expiring_class_attr(:me, 60 ) { self }
14
+ end
12
15
 
13
16
  describe ExpirationDate do
14
17
  it 'returns the same attribute before the expiration time' do
@@ -35,7 +38,7 @@ describe ExpirationDate do
35
38
  bar.should be_equal(a.bar)
36
39
  bar.should == 'bar'
37
40
 
38
- a.expire_now(:bar)
41
+ a.expire_bar_now
39
42
  bar.should_not be_equal(a.bar)
40
43
  a.bar.should == 'bar'
41
44
  end
@@ -53,20 +56,20 @@ describe ExpirationDate do
53
56
  a.foo.should == 'foo'
54
57
  end
55
58
 
56
- it 'can alter an expiration label' do
59
+ it "can alter an attribute's age" do
57
60
  a = TestA.new
58
61
  bar = a.bar
59
62
  bar.should be_equal(a.bar)
60
63
  bar.should == 'bar'
61
64
 
62
65
  # this alteration takes place only after the attribute expires
63
- a.alter_expiration_label('bar', 0.5)
66
+ a.alter_bar_age 0.5
64
67
 
65
68
  sleep 0.75
66
69
  bar.should be_equal(a.bar)
67
70
 
68
71
  # so now the age should only be 0.5 seconds
69
- a.expire_now(:bar)
72
+ a.expire_bar_now
70
73
  bar.should_not be_equal(a.bar)
71
74
  a.bar.should == 'bar'
72
75
 
@@ -74,23 +77,75 @@ describe ExpirationDate do
74
77
  bar.should_not be_equal(a.bar)
75
78
  end
76
79
 
77
- it 'ignores requests on unknown expriation labels' do
78
- a = TestA.new
79
-
80
- h = a.expiration_labels
81
- h.keys.length == 2
82
-
83
- a.expire_now(:foobar)
84
- h.keys.length == 2
85
-
86
- a.alter_expiration_label(:foobar, 42)
87
- h.keys.length == 2
88
- end
89
-
90
80
  it 'executes the block in the context of the instance' do
91
81
  a = TestA.new
92
82
  a.me.should be_equal(a)
93
83
  end
84
+
85
+ describe 'when used at the class level' do
86
+ it 'returns the same attribute before the expiration time' do
87
+ foo = TestA.foo
88
+ foo.should be_equal(TestA.foo)
89
+ foo.should == 'class foo'
90
+ end
91
+
92
+ it 'returns a new attribute after the expiration time' do
93
+ foo = TestA.foo
94
+ foo.should be_equal(TestA.foo)
95
+ foo.should == 'class foo'
96
+
97
+ sleep 0.75
98
+ foo.should_not be_equal(TestA.foo)
99
+ TestA.foo.should == 'class foo'
100
+ end
101
+
102
+ it 'can immediately expire an attribute' do
103
+ bar = TestA.bar
104
+ bar.should be_equal(TestA.bar)
105
+ bar.should == 'class bar'
106
+
107
+ TestA.expire_bar_now
108
+ bar.should_not be_equal(TestA.bar)
109
+ TestA.bar.should == 'class bar'
110
+ end
111
+
112
+ it 'can assign an attribute' do
113
+ TestA.foo.should == 'class foo'
114
+
115
+ foobar = 'foobar'
116
+ TestA.foo = foobar
117
+ TestA.foo.should be_equal(foobar)
118
+
119
+ sleep 0.75
120
+ TestA.foo.should_not be_equal(foobar)
121
+ TestA.foo.should == 'class foo'
122
+ end
123
+
124
+ it "can alter an attribute's age" do
125
+ bar = TestA.bar
126
+ bar.should be_equal(TestA.bar)
127
+ bar.should == 'class bar'
128
+
129
+ # this alteration takes place only after the attribute expires
130
+ TestA.alter_bar_age 0.5
131
+
132
+ sleep 0.75
133
+ bar.should be_equal(TestA.bar)
134
+
135
+ # so now the age should only be 0.5 seconds
136
+ TestA.expire_bar_now
137
+ bar.should_not be_equal(TestA.bar)
138
+ TestA.bar.should == 'class bar'
139
+
140
+ sleep 0.75
141
+ bar.should_not be_equal(TestA.bar)
142
+ end
143
+
144
+ it 'executes the block in the context of the class' do
145
+ TestA.me.should be_equal(TestA)
146
+ end
147
+
148
+ end
94
149
  end
95
150
 
96
151
  # EOF
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: expiration-date
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tim Pease
@@ -9,11 +9,11 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-08-23 00:00:00 -06:00
12
+ date: 2008-08-25 00:00:00 -06:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
16
- description: Wandering the grocery aisle the other day I saw a package of bacon with a neon green sticker screaming for all to hear "THIS BACON ONLY COSTS ONE DOLLAR!!!". Screaming bacon always intrigues me, so I grabbed the nearest store manager. He works at the coffee shop next door, but when I told him about the screaming bacon he had to come and see for himself. "Oh, it's a manager's special" he told me. "When products get too old the manager will reduce the price so they sell more quickly. This allows new products to be put on the shelves, and the store can make some money instead of throwing out the expired products". I stared at him blankly. He wandered back to the coffee shop and had a latte. I continued to stand there thinking about expiring products and how Ruby could benefit from neon green stickers and stale bacon. Eventually the grocery store manager came by and asked me if everything was okay. I grabbed him by the collar, pointed at the bacon and yelled "DO YOU KNOW WHAT THIS MEANS!?!?". I ran from the store, grabbed my laptop, and whipped up this little gem. EXPIRATION DATE (now with more neon green). Now ruby can expire it's bacon, too, just like the grocery store, and make room for more bacon from the delivery truck.
16
+ description: Really simple caching by attaching an expiration date to attributes. The ExpirationDate module adss two methods to a class -- expiring_attr and expiring_class_attr. These two methods are used to declare attributes in the instance and in the class, respectively, that will expire after some period of seconds have elapsed. The attribute is re-initialized from the given block after it has expired. This is a very simple form of caching.
17
17
  email: tim.pease@gmail.com
18
18
  executables: []
19
19
 
@@ -21,11 +21,12 @@ extensions: []
21
21
 
22
22
  extra_rdoc_files:
23
23
  - History.txt
24
- - README.txt
24
+ - README.rdoc
25
25
  files:
26
+ - .gitignore
26
27
  - History.txt
27
28
  - Manifest.txt
28
- - README.txt
29
+ - README.rdoc
29
30
  - Rakefile
30
31
  - lib/expiration-date.rb
31
32
  - spec/expiration-date_spec.rb
@@ -46,7 +47,7 @@ homepage: http://codeforpeople.rubyforge.org/expiration-date
46
47
  post_install_message:
47
48
  rdoc_options:
48
49
  - --main
49
- - README.txt
50
+ - README.rdoc
50
51
  require_paths:
51
52
  - lib
52
53
  required_ruby_version: !ruby/object:Gem::Requirement