cleaner inflation interface
[dbsrgits/DBIx-Data-Store-old.git] / lib / DBIx / Data / Collection / Set.pm
CommitLineData
65b76960 1package DBIx::Data::Collection::Set;
2
3use Moose;
4use Method::Signatures::Simple;
5use Data::Perl::Stream::Array;
7cd0d8a9 6use Data::Perl::Collection::Set;
50166086 7use Scalar::Util qw(weaken refaddr);
65b76960 8
67769de0 9has _inflator => (
10 is => 'ro',
11 handles => {
12 _inflate => 'inflate',
13 _deflate => 'deflate',
14 _merge => 'merge',
15 _refresh => 'refresh',
16 _deflate_spec => 'deflate_spec',
17 _merge_spec => 'merge_spec',
18 },
19 default => method {
20 require DBIx::Data::Store::SimpleInflator;
21 my $args = {};
22 $args->{class} = $self->_class if $self->_has_class;
23 DBIx::Data::Store::SimpleInflator->new($args)
24 },
25);
26
65b76960 27has _store => (is => 'ro', required => 1, init_arg => 'store');
28
9f2b6cc8 29has _class => (is => 'ro', predicate => '_has_class', init_arg => 'class');
65b76960 30
3a2e7c1c 31has _set_over => (is => 'ro', required => 1, init_arg => 'set_over');
32
33## member cache (all members)
34
35has _member_cache => (
50166086 36 is => 'ro', lazy_build => 1,
3a2e7c1c 37 predicate => '_member_cache_built',
50166086 38 writer => '_set_member_cache',
3a2e7c1c 39);
65b76960 40
41method _build__member_cache {
42 my $stream = $self->_new_raw_stream;
43 my @cache;
44 while (my ($raw) = $stream->next) {
3a2e7c1c 45 my $obj = do {
46 if (my ($obj) = $self->_key_cache_get_raw($raw)) {
9f2b6cc8 47 # can't just $self->_merge($obj, $raw) since $obj might have changed
48 $self->_refresh($obj, $raw)
3a2e7c1c 49 } else {
de9534fa 50 $self->_add_to_key_cache($self->_inflate($raw))
3a2e7c1c 51 }
52 };
53 push @cache, $obj;
65b76960 54 }
9f2b6cc8 55 $self->_notify_observers(all_members => \@cache);
de9534fa 56 \@cache
65b76960 57}
58
3a2e7c1c 59method _add_to_member_cache ($to_add) {
c51eabc5 60 return $to_add unless $self->_member_cache_built;
3a2e7c1c 61 push @{$self->_member_cache}, $to_add;
de9534fa 62 $to_add
3a2e7c1c 63}
64
c51eabc5 65method _remove_from_member_cache ($to_remove) {
66 return $to_remove unless $self->_member_cache_built;
67 @{$self->_member_cache} = grep $_ ne $to_remove, @{$self->_member_cache};
68 $to_remove
69}
70
3a2e7c1c 71## key cache - by primary/unique key
72
73has _key_cache => (is => 'ro', default => sub { {} });
74
75method _add_to_key_cache ($to_add) {
76 $self->_key_cache->{$self->_object_to_id($to_add)} = $to_add;
de9534fa 77 $to_add
3a2e7c1c 78}
79
c51eabc5 80method _remove_from_key_cache ($to_remove) {
81 # should return $to_remove
82 delete $self->_key_cache->{$self->_object_to_id($to_remove)}
83}
84
3a2e7c1c 85method _key_cache_has_raw ($raw) {
86 exists $self->_key_cache->{$self->_raw_to_id($raw)}
87}
88
89method _key_cache_has_object ($obj) {
90 exists $self->_key_cache->{$self->_object_to_id($obj)}
91}
92
93method _key_cache_get_raw ($raw) {
e49bd861 94 $self->_key_cache_get_id($self->_raw_to_id($raw))
3a2e7c1c 95}
96
97method _key_cache_get_object ($obj) {
e49bd861 98 $self->_key_cache_get_id($self->_object_to_id($obj))
3a2e7c1c 99}
100
e49bd861 101method _key_cache_get_object_spec ($spec) {
102 # see _object_spec_to_id for doc of what the difference is
103 $self->_key_cache_get_id($self->_object_spec_to_id($spec))
104}
3a2e7c1c 105
e49bd861 106method _key_cache_get_id ($id) {
107 exists $self->_key_cache->{$id}
108 ? ($self->_key_cache->{$id})
109 : ()
65b76960 110}
111
50166086 112method _all_key_cache_members {
113 values %{$self->_key_cache}
114}
115
32746a09 116method _set_key_cache_members ($members) {
117 %{$self->_key_cache} = (map +($self->_object_to_id($_) => $_), @$members);
118 return
119}
120
9f2b6cc8 121## observers
122
123has _observer_callbacks => (
50166086 124 is => 'ro', default => sub { {} },
9f2b6cc8 125);
126
127method _notify_observers ($event, $payload) {
50166086 128 my $oc = $self->_observer_callbacks;
129 foreach my $refaddr (keys %$oc) {
130 my ($obj, $cb) = @{$oc->{$refaddr}};
131 unless (defined $obj) { # weak ref was garbage collected
132 delete $oc->{$refaddr};
133 next;
134 }
135 $obj->$cb($self, $event, $payload);
9f2b6cc8 136 }
50166086 137 $payload
138}
139
140method _register_observer ($obj, $cb) {
141 my $entry = [ $obj, $cb ];
142 weaken($entry->[0]);
143 $self->_observer_callbacks->{refaddr($obj)} = $entry;
144 return
145}
146
147method _setup_observation_of ($other) {
148 $other->_register_observer($self, method ($from, $event, $payload) {
149 if ($event eq 'add' or $event eq 'get') {
150 $self->_add_to_caches($payload);
151 } elsif ($event eq 'remove') {
152 $self->_remove_from_caches($payload);
153 } elsif ($event eq 'all_members') {
154 # separate arrayref since future add will trigger push()
32746a09 155 $self->_set_caches([ @$payload ]);
50166086 156 }
157 });
158 return
9f2b6cc8 159}
160
32746a09 161
3a2e7c1c 162## methods to get ids
163
164method _raw_to_id ($raw) {
165 # XXX must escape this. or do something else.
166 join ';', map $raw->{$_}, @{$self->_set_over}
167}
168
169method _object_to_id ($obj) {
c51eabc5 170 $self->_raw_to_id($self->_deflate($obj))
3a2e7c1c 171}
172
e49bd861 173method _object_spec_to_id ($spec) {
174 # intentionally C&P from _raw_to - this is not the same thing. If a column
175 # were mapped to an attribute of a different name, the raw would have the
176 # column name as a key but an object spec would have the attribute name
177 join ';', map $spec->{$_}, @{$self->_set_over}
178}
179
c51eabc5 180## array-ish operations - i.e. get all members
181
e49bd861 182method _new_raw_stream {
ec6807f8 183 $self->_store->new_select_command({})->execute
e49bd861 184}
185
65b76960 186method flatten {
187 @{$self->_member_cache};
188}
189
190method as_stream {
191 Data::Perl::Stream::Array->new(array => $self->_member_cache);
192}
193
7cd0d8a9 194# theoretically inefficient except that if we're being asked this then
195# either the data should have been pre-loaded or we're going to get all
196# elements anyway
197
198method count {
199 scalar $self->flatten
200}
201
202method map ($sub) {
203 Data::Perl::Collection::Set->new(
204 members => [ map $sub->($_), $self->flatten ]
205 )
206}
207
32746a09 208method _set_caches ($members) {
209 $self->_set_member_cache($members);
210 $self->_set_key_cache_members($members);
211 return
212}
213
e49bd861 214## load single row
215
216method get ($spec) {
217 if (my ($got) = $self->_key_cache_get_object_spec($spec)) {
218 return $got
219 }
220 if (my ($raw) = $self->_get_from_store($self->_deflate_spec($spec))) {
50166086 221 return $self->_notify_observers(
222 get => $self->_add_to_key_cache($self->_inflate($raw))
223 );
e49bd861 224 }
225 return undef # we aren't handling cache misses here yet
226}
227
228method _get_from_store ($raw) {
229 $self->_store->new_select_single_command($raw)->execute
230}
231
32746a09 232## add member
c51eabc5 233
3a2e7c1c 234method add ($new) {
235 $self->_add_to_store($new);
236 $self->_add_to_caches($new);
9f2b6cc8 237 $self->_notify_observers(add => $new);
c51eabc5 238 $new
3a2e7c1c 239}
240
241method _add_to_store ($new) {
242 my $new_raw = $self->_deflate($new);
243 $self->_merge($new, $self->_store->new_insert_command($new_raw)->execute);
c51eabc5 244 $new
3a2e7c1c 245}
246
247method _add_to_caches ($new) {
248 $self->_add_to_member_cache($new);
249 $self->_add_to_key_cache($new);
de9534fa 250 $new
3a2e7c1c 251}
252
32746a09 253## remove member
c51eabc5 254
255method remove ($old) {
256 $self->_remove_from_store($old);
257 $self->_remove_from_caches($old);
9f2b6cc8 258 $self->_notify_observers(remove => $old);
c51eabc5 259 $old
260}
261
262method _remove_from_store ($old) {
a1e15ee1 263 $self->_store->new_delete_single_command($self->_deflate($old))->execute
c51eabc5 264}
265
266method _remove_from_caches ($old) {
267 $self->_remove_from_member_cache($old);
268 $self->_remove_from_key_cache($old);
269 $old
270}
271
32746a09 272## update member
48d91d77 273
274method _update_in_store ($obj) {
275 # this is currently a call command but we should think about it
276 # being a row command so that we can have RETURNING or other
277 # mechanisms handle things like set-on-update datetime values
a1e15ee1 278 $self->_store->new_update_single_command($self->_deflate($obj))->execute
48d91d77 279}
280
32746a09 281# I do wonder if we needed _merge_spec or if we'd be better off with
282# just using the raw merge routine ...
283
284method _update_set_in_store ($spec) {
285 $self->_store->new_update_command($self->_deflate_spec($spec))->execute;
286 if ($self->_member_cache_built) {
287 my $cache = $self->_member_cache;
288 foreach my $obj (@{$cache}) {
289 $self->_merge_spec($obj, $spec);
290 }
291 $self->_notify_observers(all_members => $cache);
292 }
293 return
294}
295
296method _remove_set_from_store {
297 $self->_store->new_delete_command->execute;
298 $self->_set_caches([]);
299 $self->_notify_observers(all_members => []);
300 return
301}
302
65b76960 3031;