hyperactive 0.2.3 → 0.2.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/README CHANGED
@@ -7,15 +7,21 @@ It uses archipelago for persistence, and is meaningful only in an environment wh
7
7
  Hyperactive::Record:: The base class package itself, providing you with cached selectors and rejectors, along with cached finders for any number of attributes.
8
8
  Hyperactive::Hash:: A collection class that contains any number of Hyperactive::Records in a hash like structure.
9
9
  Hyperactive::List:: A collection class that contains any number of Hyperactive::Records in a list like structure.
10
+ Hyperactive::Index:: A module included into Hyperactive::Record that adds simple indexing capabilities of the <b>find_by_...</b> type.
11
+ Hyperactive::Transactions:: A module included into Hyperactive::Record that adds a few simplifications in handling transactions properly.
10
12
 
11
13
  == Usage:
12
14
 
13
15
  To use Hyperactive in the simplest way, just run the script/services.rb script from the Archipelago
14
16
  distribution to get a few services running, then subclass Hyperactive::Record::Bass and create
15
- objects with <b>MyBassSubclass.get_instance</b> (not <b>new</b>!). They will be automagically
16
- stored in the network, and fetchable via their <b>instance.record_id</b>.
17
+ objects with <b>MyBassSubclass.get_instance</b>. They will be automagically stored in the network,
18
+ and fetchable via their <b>instance.record_id</b>.
17
19
 
18
- By loading Hyperactive::Record you will automatically define Hyperactive::Record::CAPTAIN which will
20
+ You can also use <b>new</b> of course, but beware that this will not store the object persistently
21
+ <b>or</b> give you a proxy. Both of these things are good for you, so use get_instance unless you know
22
+ what you are doing.
23
+
24
+ By loading Hyperactive::Record you will automatically define Hyperactive::CAPTAIN which will
19
25
  be an Archipelago::Pirate::Captain that is your interface to the distributed database.
20
26
 
21
27
  == Examples:
data/TODO ADDED
@@ -0,0 +1,4 @@
1
+
2
+ * Create a validation framework for Hyperactive::Record::Bass
3
+
4
+ * Create a sorted data structure, for example AA trees: http://www.eternallyconfuzzled.com/tuts/andersson.html
@@ -62,6 +62,7 @@ module Hyperactive
62
62
  # NB: Remember to call create on the new instance or use Head.get_instance to get that done automatically.
63
63
  #
64
64
  def initialize
65
+ super
65
66
  self.list = nil
66
67
  end
67
68
 
@@ -72,11 +73,18 @@ module Hyperactive
72
73
  self.list.size
73
74
  end
74
75
 
76
+ #
77
+ # Return whether this Hash is empty.
78
+ #
79
+ def empty?
80
+ self.list.empty?
81
+ end
82
+
75
83
  #
76
84
  # Return the value for +key+.
77
85
  #
78
86
  def [](key)
79
- element = Hyperactive::Record::CAPTAIN[my_key_for(key), @transaction]
87
+ element = Hyperactive::CAPTAIN[my_key_for(key), @transaction]
80
88
  if element
81
89
  return element.value
82
90
  else
@@ -88,7 +96,7 @@ module Hyperactive
88
96
  # Returns whether +key+ is included in this Hash.
89
97
  #
90
98
  def include?(key)
91
- Hyperactive::Record::CAPTAIN.include?(my_key_for(key), @transaction)
99
+ Hyperactive::CAPTAIN.include?(my_key_for(key), @transaction)
92
100
  end
93
101
 
94
102
  #
@@ -97,13 +105,13 @@ module Hyperactive
97
105
  def []=(key, value)
98
106
  self.list = Hyperactive::List::Head.get_instance_with_transaction(@transaction) unless self.list
99
107
 
100
- if (element = Hyperactive::Record::CAPTAIN[my_key_for(key), @transaction])
108
+ if (element = Hyperactive::CAPTAIN[my_key_for(key), @transaction])
101
109
  element.value = value
102
110
  else
103
111
  element = Element.get_instance_with_transaction(@transaction, key, value, nil)
104
112
  self.list << element
105
113
  element.list_element = self.list.last_element
106
- Hyperactive::Record::CAPTAIN[my_key_for(key), @transaction] = element
114
+ Hyperactive::CAPTAIN[my_key_for(key), @transaction] = element
107
115
  end
108
116
  end
109
117
 
@@ -115,9 +123,9 @@ module Hyperactive
115
123
 
116
124
  return_value = nil
117
125
 
118
- element = Hyperactive::Record::CAPTAIN[my_key_for(key), @transaction]
126
+ element = Hyperactive::CAPTAIN[my_key_for(key), @transaction]
119
127
  if element
120
- Hyperactive::Record::CAPTAIN.delete(my_key_for(key), @transaction)
128
+ Hyperactive::CAPTAIN.delete(my_key_for(key), @transaction)
121
129
  self.list.unlink!(element.list_element)
122
130
  return_value = element.value
123
131
  element.destroy!
@@ -19,61 +19,171 @@
19
19
  module Hyperactive
20
20
 
21
21
  #
22
- # A utility method to simplify using around-hooks for any block.
22
+ # A container of utility hook methods.
23
23
  #
24
24
  module Hooker
25
25
 
26
26
  #
27
- # Will call each hook in +hooks+ with a block that
28
- # calls the rest of the +hooks+ with +argument+ as argument
29
- # and after that yields to the given +block+.
27
+ # Something that uses hooks a lot, include if you want support for various types of hooks.
30
28
  #
31
- # This will allow you wrap any method call in a dynamic set
32
- # of other methods.
33
- #
34
- # For more examples, see Hyperactive::Record.
35
- #
36
- # Example:
37
- # class ImportantStuff
38
- # attr_accessor :timestamp
39
- # def is_valid?
40
- # # do nifty stuff
41
- # end
42
- # def notify_someone_that_i_am_changed!(message)
43
- # # do even more nifty stuff
44
- # end
45
- # def validate_and_notify(message)
46
- # raise "i am invalid!" unless is_valid?
47
- # yield
48
- # notify_someone_that_i_am_changed!(message)
49
- # end
50
- # def do_save
51
- # # save me in persistent storage
52
- # end
53
- # end
54
- # class ImportantHandler
55
- # def update_timestamp(important_stuff_instance)
56
- # important_stuff_instance.timestamp = Time.now
57
- # Hyperactive::Hooker.call_with_hooks("changed at #{Time.now}",
58
- # important_stuff_instance.method(:validate_and_notify)) do
59
- # important_stuff_instance.do_save
60
- # end
61
- # end
62
- # end
63
- #
64
- def call_with_hooks(argument, *hooks, &block)
65
- if hooks.empty?
66
- yield
67
- else
68
- first = hooks.first
69
- rest = hooks[1..-1]
70
- first.call(argument) do
71
- call_with_hooks(argument, *rest, &block)
29
+ module Pimp
30
+
31
+ #
32
+ # Upon inclusion, our +base+ class will be given a set of hook
33
+ # arrays, and then get extended by ClassMethods.
34
+ #
35
+ def self.append_features(base)
36
+ super
37
+ base.instance_variable_set(:@create_hooks, [])
38
+ base.instance_variable_set(:@destroy_hooks, [])
39
+ base.instance_variable_set(:@save_hooks, [])
40
+ base.instance_variable_set(:@load_hooks, [])
41
+ base.extend(ClassMethods)
42
+ end
43
+
44
+ #
45
+ # This will allow us to wrap any write of us to persistent storage
46
+ # in the @save_hooks as long as the Archipelago::Hashish provider
47
+ # supports it. See Archipelago::Hashish::BerkeleyHashish for an example
48
+ # of Hashish providers that do this.
49
+ #
50
+ def save_hook(old_value, &block)
51
+ self.class.with_hooks(:instance => self, :arguments => [old_value], :hooks => self.class.save_hooks) do
52
+ yield
72
53
  end
73
54
  end
74
- end
55
+
56
+ #
57
+ # This will allow us to wrap any load of us from persistent storage
58
+ # in the @@load_hooks as long as the Archipelago::Hashish provider
59
+ # supports it. See Archipelago::Hashish::BerkeleyHashish for an example
60
+ # of Hashish providers that do this.
61
+ #
62
+ def load_hook(&block)
63
+ self.class.with_hooks(:instance => self, :hooks => self.class.load_hooks) do
64
+ yield
65
+ end
66
+ end
67
+
75
68
 
76
- module_function :call_with_hooks
69
+ #
70
+ # The class methods of a Pimp.
71
+ #
72
+ module ClassMethods
73
+
74
+ #
75
+ # Will wrap a block within nested calls to given hooks.
76
+ #
77
+ # Parameters:
78
+ # * <i>:instance</i>: An instance to send methods to, defaults to self
79
+ # * <i>:arguments</i>: Extra arguments to send to whatever we call, defaults to self
80
+ # * <i>:hooks</i>: An Array of hook objects. They can be either Strings or Symbols, or
81
+ # whatever else can be given to <b>respond_to?</b>, <b>or</b> they can be anything
82
+ # that <b>respond_to?(:call)</b>. If the hook is something that
83
+ # <i>respond_to?(:call)</i> it will be <i>call</i>ed and given
84
+ # the the <i>:instance</i> and *<i>:arguments</i> and the provided +block+.
85
+ # Else the <i>:instance</i>.respond_to?(<i>the given hook</i>)
86
+ # must be true, and that <i>:instance</i> will be sent the <i>given hook</i> along with the <i>:arguments</i> and
87
+ # a block that does the rest of the hooks.
88
+ #
89
+ def with_hooks(options = {}, &block)
90
+ instance = options[:instance] || self
91
+ arguments = options[:arguments] || []
92
+ hooks = options[:hooks] || []
93
+
94
+ if hooks.empty?
95
+ yield
96
+ else
97
+ first = hooks.first
98
+ rest = hooks[1..-1]
99
+ if first.respond_to?(:call)
100
+ first.call(instance, *arguments) do
101
+ with_hooks(:instance => instance, :hooks => rest, :arguments => arguments, &block)
102
+ end
103
+ elsif instance.respond_to?(first)
104
+ instance.send(first, *arguments) do
105
+ with_hooks(:instance => instance, :hooks => rest, :arguments => arguments, &block)
106
+ end
107
+ else
108
+ raise "You can only send in hooks that are Symbols in the given instance or call'able objects"
109
+ end
110
+ end
111
+ end
112
+
113
+ #
114
+ # Upon inheritance, our +subclass+ will be given copies of our hooks.
115
+ #
116
+ def inherited(subclass)
117
+ subclass.instance_variable_set(:@create_hooks, @create_hooks.clone)
118
+ subclass.instance_variable_set(:@destroy_hooks, @destroy_hooks.clone)
119
+ subclass.instance_variable_set(:@save_hooks, @save_hooks.clone)
120
+ subclass.instance_variable_set(:@load_hooks, @load_hooks.clone)
121
+ end
122
+
123
+ #
124
+ # Return our create_hooks, which can then be treated as any old Array.
125
+ #
126
+ # These must be <b>call</b>able objects with an arity of 1
127
+ # that will be sent the instance about to be created (initial
128
+ # insertion into the database system) that take a block argument.
129
+ #
130
+ # The block argument will be a Proc that actually injects the
131
+ # instance into the database system.
132
+ #
133
+ # Use this to preprocess, validate and/or postprocess your
134
+ # instances upon creation.
135
+ #
136
+ attr_reader :create_hooks
137
+
138
+ #
139
+ # Return our destroy_hooks, which can then be treated as any old Array.
140
+ #
141
+ # These must be <b>call</b>able objects with an arity of 1
142
+ # that will be sent the instance about to be destroyed (removal
143
+ # from the database system) that take a block argument.
144
+ #
145
+ # The block argument will be a Proc that actually removes the
146
+ # instance from the database system.
147
+ #
148
+ # Use this to preprocess, validate and/or postprocess your
149
+ # instances upon destruction.
150
+ #
151
+ attr_reader :destroy_hooks
152
+
153
+ #
154
+ # Return our load_hooks, which can then be treated as any old Array.
155
+ #
156
+ # These must be <b>call</b>able objects with an arity of 1
157
+ # that will be sent the instance about to be loaded (insertion
158
+ # into the live hash of the database in question) that take a block argument.
159
+ #
160
+ # The block argument will be a Proc that actually puts the
161
+ # instance into the live hash.
162
+ #
163
+ # Use this to preprocess, validate and/or postprocess your
164
+ # instances upon loading.
165
+ #
166
+ attr_reader :load_hooks
167
+
168
+ #
169
+ # Return our save_hooks, which can then be treated as any old Array.
170
+ #
171
+ # These must be <b>call</b>able objects with an arity of 1
172
+ # that will be sent [the old version, the new version] of the
173
+ # instance about to be saved (storage into the database system)
174
+ # along with a block argument.
175
+ #
176
+ # The block argument will be a Proc that actually saves the
177
+ # instance into the database system.
178
+ #
179
+ # Use this to preprocess, validate and/or postprocess your
180
+ # instances upon saving.
181
+ #
182
+ attr_reader :save_hooks
183
+
184
+ end
185
+
186
+ end
77
187
 
78
188
  end
79
189
 
@@ -0,0 +1,210 @@
1
+ # Archipelago - a distributed computing toolkit for ruby
2
+ # Copyright (C) 2006 Martin Kihlgren <zond at troja dot ath dot cx>
3
+ #
4
+ # This program is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU General Public License
6
+ # as published by the Free Software Foundation; either version 2
7
+ # of the License, or (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program; if not, write to the Free Software
16
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
+
18
+ module Hyperactive
19
+
20
+ module Index
21
+
22
+ #
23
+ # A tiny <b>call</b>able class that saves stuff in
24
+ # indexes depending on certain attributes.
25
+ #
26
+ class IndexBuilder
27
+ #
28
+ # Get the key for the given attributes and values.
29
+ #
30
+ def self.get_key(klass, attributes, values)
31
+ "Hyperactive::IndexBuilder::#{klass}::#{attributes.join(",")}::#{values.join(",")}"
32
+ end
33
+ #
34
+ # Initialize an IndexBuilder giving it an array of +attributes+
35
+ # that will be indexed.
36
+ #
37
+ def initialize(klass, attributes)
38
+ @klass = klass
39
+ @attributes = attributes
40
+ end
41
+ #
42
+ # Get the key for the given +record+.
43
+ #
44
+ def get_key_for(record)
45
+ values = @attributes.collect do |att|
46
+ if record.respond_to?(att)
47
+ record.send(att)
48
+ else
49
+ nil
50
+ end
51
+ end
52
+ key = self.class.get_key(@klass, @attributes, values)
53
+ end
54
+ #
55
+ # Call this IndexBuilder and pass it a +block+.
56
+ #
57
+ # If the +argument+ is an Array then we know we are a save hook,
58
+ # otherwise we are a destroy hook.
59
+ #
60
+ def call(*arguments, &block)
61
+ yield
62
+
63
+ #
64
+ # If the argument is an Array (of old value, new value)
65
+ # then we are a save hook, otherwise a destroy hook.
66
+ #
67
+ if arguments.size > 1
68
+ record = arguments.first
69
+ old_record = arguments.last
70
+ old_key = get_key_for(old_record)
71
+ new_key = get_key_for(record)
72
+ if old_key != new_key
73
+ (CAPTAIN[old_key] ||= Hyperactive::Hash::Head.get_instance_with_transaction(record.transaction)).delete(record.record_id)
74
+ (CAPTAIN[new_key] ||= Hyperactive::Hash::Head.get_instance_with_transaction(record.transaction))[record.record_id] = record
75
+ end
76
+ else
77
+ record = arguments.first
78
+ (CAPTAIN[get_key_for(record)] ||= Hyperactive::Hash::Head.get_instance_with_transaction(record.transaction)).delete(record.record_id)
79
+ end
80
+ end
81
+ end
82
+
83
+ #
84
+ # A tiny <b>call</b>able class that saves stuff inside containers
85
+ # if they match certain criteria.
86
+ #
87
+ class MatchSaver
88
+ #
89
+ # Initialize this MatchSaver with a +key+, a <b>call</b>able +matcher+
90
+ # and a +mode+ (:select, :reject, :delete_if_match or :delete_unless_match).
91
+ #
92
+ def initialize(key, matcher, mode)
93
+ @key = key
94
+ @matcher = matcher
95
+ @mode = mode
96
+ end
97
+ #
98
+ # Depending on <i>@mode</i> and return value of <i>@matcher</i>.call
99
+ # may save record in the Hash-like container named <i>@key</i> in
100
+ # the main database after having yielded to +block+.
101
+ #
102
+ def call(*arguments, &block)
103
+ yield
104
+
105
+ record = arguments.first
106
+
107
+ case @mode
108
+ when :select
109
+ if @matcher.call(record)
110
+ CAPTAIN[@key, record.transaction][record.record_id] = record
111
+ else
112
+ CAPTAIN[@key, record.transaction].delete(record.record_id)
113
+ end
114
+ when :reject
115
+ if @matcher.call(record)
116
+ CAPTAIN[@key, record.transaction].delete(record.record_id)
117
+ else
118
+ CAPTAIN[@key, record.transaction][record.record_id] = record
119
+ end
120
+ when :delete_if_match
121
+ if @matcher.call(record)
122
+ CAPTAIN[@key, record.transaction].delete(record.record_id)
123
+ end
124
+ when :delete_unless_match
125
+ unless @matcher.call(record)
126
+ CAPTAIN[@key, record.transaction].delete(record.record_id)
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ module Indexable
133
+
134
+ def self.append_features(base)
135
+ super
136
+ base.extend(ClassMethods)
137
+ end
138
+
139
+ module ClassMethods
140
+ #
141
+ # Create an index for this class.
142
+ #
143
+ # Will create a method find_by_#{attributes.join("_and_")} for this
144
+ # class that will return what you expect.
145
+ #
146
+ def index_by(*attributes)
147
+ klass = self
148
+ self.class.class_eval do
149
+ define_method("find_by_#{attributes.join("_and_")}") do |*args|
150
+ CAPTAIN[IndexBuilder.get_key(klass, attributes, args)]
151
+ end
152
+ end
153
+ index_builder = IndexBuilder.new(self, attributes)
154
+ self.save_hooks << index_builder
155
+ self.destroy_hooks << index_builder
156
+ end
157
+
158
+ #
159
+ # Will define a method called +name+ that will include all
160
+ # existing instances of this class that when yielded to the +block+
161
+ # returns true. Will only return instances saved after this selector
162
+ # is defined.
163
+ #
164
+ def select(name, &block) #:yields: instance
165
+ key = collection_key(name)
166
+ CAPTAIN[key] ||= Hyperactive::Hash::Head.get_instance
167
+ self.class.class_eval do
168
+ define_method(name) do
169
+ CAPTAIN[key]
170
+ end
171
+ end
172
+ self.save_hooks << MatchSaver.new(key, block, :select)
173
+ self.destroy_hooks << MatchSaver.new(key, block, :delete_if_match)
174
+ end
175
+
176
+ #
177
+ # Will define a method called +name+ that will include all
178
+ # existing instances of this class that when yielded to +block+
179
+ # does not return true. Will only return instances saved after this
180
+ # rejector is defined.
181
+ #
182
+ def reject(name, &block) #:yields: instance
183
+ key = collection_key(name)
184
+ CAPTAIN[key] ||= Hyperactive::Hash::Head.get_instance
185
+ self.class.class_eval do
186
+ define_method(name) do
187
+ CAPTAIN[key]
188
+ end
189
+ end
190
+ self.save_hooks << MatchSaver.new(key, block, :reject)
191
+ self.destroy_hooks << MatchSaver.new(key, block, :delete_unless_match)
192
+ end
193
+
194
+ private
195
+
196
+ #
197
+ # The key used to store the collection with the given +sym+ as name.
198
+ #
199
+ def collection_key(sym)
200
+ "Hyperactive::Index::Indexable::collection_key::#{sym}"
201
+ end
202
+
203
+
204
+ end
205
+
206
+ end
207
+
208
+ end
209
+
210
+ end
@@ -70,6 +70,7 @@ module Hyperactive
70
70
  # NB: Remember to call create on the new instance or use Head.get_instance to get that done automatically.
71
71
  #
72
72
  def initialize
73
+ super
73
74
  self.size = 0
74
75
  self.first_element = self.last_element = nil
75
76
  end
@@ -102,6 +103,13 @@ module Hyperactive
102
103
  element.destroy!
103
104
  end
104
105
 
106
+ #
107
+ # Return whether this List is empty.
108
+ #
109
+ def empty?
110
+ return self.size == 0
111
+ end
112
+
105
113
  #
106
114
  # Remove all elements from this List::Head.
107
115
  #