dm-is-awesome_set 0.10.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2 @@
1
+ pkg/*
2
+
data/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ Copyright (c) 2008 Jeremy Nicoll (http://gnexp.com, jnicoll@gnexp.com)
2
+
3
+ Some code and documentation janked from dm-is-nested_set by Sindre Aarsaether
4
+ (somebee.com)
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining
7
+ a copy of this software and associated documentation files (the
8
+ "Software"), to deal in the Software without restriction, including
9
+ without limitation the rights to use, copy, modify, merge, publish,
10
+ distribute, sublicense, and/or sell copies of the Software, and to
11
+ permit persons to whom the Software is furnished to do so, subject to
12
+ the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be
15
+ included in all copies or substantial portions of the Software
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,16 @@
1
+ dm-is-awesome_set
2
+ =================
3
+
4
+ Yes, finally! A nested_set for Datamapper that actually works! .... at least
5
+ I _think_ it does. Please test this out and let me know at jnicoll@gnexp.com
6
+ if you run into any problems. This readme will eventually have examples. Until
7
+ then, check the RDoc's and you should be fine.
8
+
9
+ A quick note about discriminators:
10
+
11
+ This version enables scoping that can either include or ignore discriminators.
12
+ If you wish to scope by a discriminator, please include that column name in
13
+ the scope option. Otherwise this plugin will work with all rows regardless of
14
+ the discriminator column.
15
+
16
+
@@ -0,0 +1,33 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+
6
+ gem 'jeweler', '>= 1.4'
7
+ require 'jeweler'
8
+
9
+ Jeweler::Tasks.new do |gem|
10
+
11
+ gem.name = "dm-is-awesome_set"
12
+ gem.summary = %Q{A nested set plugin for datamapper}
13
+ gem.description = %Q{A library that lets any datamapper model act like a nested set}
14
+ gem.email = "jnicoll@gnexp.com"
15
+ gem.homepage = "http://github.com/snusnu/dm-is-awesome_set"
16
+ gem.authors = ["Jeremy Nicoll", "David Haslem", "Martin Gamsjaeger (snusnu)"]
17
+
18
+ gem.add_dependency 'dm-core', '~> 0.10'
19
+ gem.add_dependency 'dm-adjust', '~> 0.10'
20
+ gem.add_dependency 'dm-aggregates', '~> 0.10'
21
+
22
+ gem.add_development_dependency 'rspec', '~> 1.2.9'
23
+
24
+ end
25
+
26
+ Jeweler::GemcutterTasks.new
27
+
28
+ rescue LoadError
29
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
30
+ end
31
+
32
+ task :spec => :check_dependencies
33
+ task :default => :spec
data/TODO ADDED
@@ -0,0 +1,4 @@
1
+ TODO:
2
+ Write documentation that doesn't suck
3
+ Integrate autospec functionality into specs for convenience
4
+ Refactor specs so that they are not ugly.
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.10.2
@@ -0,0 +1,65 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{dm-is-awesome_set}
8
+ s.version = "0.10.2"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Jeremy Nicoll", "David Haslem", "Martin Gamsjaeger (snusnu)"]
12
+ s.date = %q{2010-02-19}
13
+ s.description = %q{A library that lets any datamapper model act like a nested set}
14
+ s.email = %q{jnicoll@gnexp.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README",
18
+ "TODO"
19
+ ]
20
+ s.files = [
21
+ ".gitignore",
22
+ "LICENSE",
23
+ "README",
24
+ "Rakefile",
25
+ "TODO",
26
+ "VERSION",
27
+ "dm-is-awesome_set.gemspec",
28
+ "lib/dm-is-awesome_set.rb",
29
+ "lib/dm-is-awesome_set/is/awesome_set.rb",
30
+ "spec/dm-is-awesome_set_spec.rb",
31
+ "spec/spec_helper.rb"
32
+ ]
33
+ s.homepage = %q{http://github.com/snusnu/dm-is-awesome_set}
34
+ s.rdoc_options = ["--charset=UTF-8"]
35
+ s.require_paths = ["lib"]
36
+ s.rubygems_version = %q{1.3.5}
37
+ s.summary = %q{A nested set plugin for datamapper}
38
+ s.test_files = [
39
+ "spec/dm-is-awesome_set_spec.rb",
40
+ "spec/spec_helper.rb"
41
+ ]
42
+
43
+ if s.respond_to? :specification_version then
44
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
45
+ s.specification_version = 3
46
+
47
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
48
+ s.add_runtime_dependency(%q<dm-core>, ["~> 0.10"])
49
+ s.add_runtime_dependency(%q<dm-adjust>, ["~> 0.10"])
50
+ s.add_runtime_dependency(%q<dm-aggregates>, ["~> 0.10"])
51
+ s.add_development_dependency(%q<rspec>, ["~> 1.2.9"])
52
+ else
53
+ s.add_dependency(%q<dm-core>, ["~> 0.10"])
54
+ s.add_dependency(%q<dm-adjust>, ["~> 0.10"])
55
+ s.add_dependency(%q<dm-aggregates>, ["~> 0.10"])
56
+ s.add_dependency(%q<rspec>, ["~> 1.2.9"])
57
+ end
58
+ else
59
+ s.add_dependency(%q<dm-core>, ["~> 0.10"])
60
+ s.add_dependency(%q<dm-adjust>, ["~> 0.10"])
61
+ s.add_dependency(%q<dm-aggregates>, ["~> 0.10"])
62
+ s.add_dependency(%q<rspec>, ["~> 1.2.9"])
63
+ end
64
+ end
65
+
@@ -0,0 +1,5 @@
1
+ require 'pathname'
2
+ ['dm-core', 'dm-adjust', 'dm-aggregates', 'dm-validations'].each do |dm_var|
3
+ require dm_var
4
+ end
5
+ require Pathname(__FILE__).dirname.expand_path / 'dm-is-awesome_set' / 'is' / 'awesome_set.rb'
@@ -0,0 +1,428 @@
1
+ module DataMapper
2
+ module Is
3
+ ##
4
+ # Thanks for looking into dm-is-awesome_set. What makes it so awesome? Well,
5
+ # the fact that it actually works. At least I think it does. Give it a whirl,
6
+ # and if you come across any bugs let me know (check the readme file for
7
+ # information). Most of what you will be concerned with is the move method,
8
+ # though there are some other helper methods for selecting nodes.
9
+ # The way you use it in your model is like so:
10
+ #
11
+ # def ModelName
12
+ # include DataMapper::Resource
13
+ # # ... set up your properties ...
14
+ # is :awesome_set, :scope => [:col1, :col2], :child_key => [:parent_id]
15
+ # end
16
+ #
17
+ # Note that scope is optional, and :child_key's default is [:parent_id]
18
+
19
+ module AwesomeSet
20
+ # Available options for is awesome_set:
21
+ # :scope => array of keys for scope (default is [])
22
+ # :child_key => array of keys for setting the parent-child relationship (default is [:parent_id])
23
+
24
+ def is_awesome_set(options={})
25
+ extend DataMapper::Is::AwesomeSet::ClassMethods
26
+ include DataMapper::Is::AwesomeSet::InstanceMethods
27
+
28
+ opts = set_options(options)
29
+ [:child_key, :scope].each {|var| raise "#{var} must be an Array" unless opts[var].is_a?(Array)}
30
+
31
+ property :parent_id, Integer, :min => 0, :writer => :protected
32
+
33
+ property :lft, Integer, :writer => :private, :index => true
34
+ property :rgt, Integer, :writer => :private, :index => true
35
+
36
+ class_opts = {:model => self.name, :child_key => opts[:child_key], :order => [:lft.asc] }
37
+ belongs_to :parent, class_opts
38
+ has n, :children, class_opts
39
+
40
+ before :save_self do
41
+ move_without_saving(:root) if lft.nil? #You don't want to use new_record? here. Trust me, you don't.
42
+ end
43
+
44
+ end # def is_awesome_set
45
+
46
+ module ClassMethods
47
+ def set_options(options) #:nodoc:
48
+ @ias_options = { :child_key => [:parent_id], :scope => [] }.merge(options)
49
+ end
50
+
51
+ def ias_options; @ias_options || superclass.ias_options end #:nodoc:
52
+
53
+ def child_keys; ias_options[:child_key]; end
54
+ def scope_keys; ias_options[:scope]; end
55
+ def is_nested_set? #:nodoc:
56
+ true
57
+ end
58
+
59
+ # Checks to see if the hash or object contains a valid scope by checking attributes or keys
60
+ def valid_scope?(hash)
61
+ return true if hash.is_a?(self)
62
+ return false unless hash.is_a?(Hash)
63
+ scope_keys.each { |sk| return false unless hash.keys.include?(sk) }
64
+ true
65
+ end
66
+
67
+ # Raises an error if the scope is not valid
68
+ def check_scope(hash)
69
+ raise 'Invalid scope: ' + hash.inspect if !valid_scope?(hash)
70
+ end
71
+
72
+ # Return only the attributes that deal with the scope, will raise an error on invalid scope
73
+ def extract_scope(hash)
74
+ check_scope(hash)
75
+ ret = {}
76
+ send_to_obj = hash.is_a?(self)
77
+ scope_keys.each { |sk| ret[sk] = send_to_obj ? hash.attribute_get(sk) : hash[sk] }
78
+ ret
79
+ end
80
+
81
+ def adjust_gap!(scoped_set, at, adjustment) #:nodoc:
82
+ scoped_set.all(:rgt.gt => at).adjust!({:rgt => adjustment},true)
83
+ scoped_set.all(:lft.gt => at).adjust!({:lft => adjustment},true)
84
+ end
85
+
86
+ # Return a hash that gets the roots
87
+ def root_hash
88
+ ret = {}
89
+ child_keys.each { |ck| ret[ck] = nil }
90
+ ret
91
+ end
92
+
93
+
94
+ # Get the root with no args if there is no scope
95
+ # Pass the scope or an object with scope to get the first root
96
+ def root(scope = {})
97
+ scope = extract_scope(scope)
98
+ get_class.first(scope.merge(root_hash.merge(:order => [:lft.asc])))
99
+ end
100
+
101
+ # Same as @root, but gets all roots
102
+ def roots(scope = {})
103
+ scope = extract_scope(scope)
104
+ get_class.all(scope.merge(root_hash.merge(:order => [:lft.asc])))
105
+ end
106
+
107
+ # Gets the full set with scope behavior like @root
108
+ def full_set(scope = {})
109
+ scope = extract_scope(scope)
110
+ get_class.all(scope.merge(:order => [:lft.asc]))
111
+ end
112
+
113
+ # Retrieves all nodes that do not have children.
114
+ # This needs to be refactored for more of a DM style, if possible.
115
+ def leaves(scope = {})
116
+ scope = extract_scope(scope)
117
+ get_class.all(scope.merge(:order => [:lft.asc], :conditions => ["`rgt` - `lft` = 1"]))
118
+ end
119
+
120
+ # Since DataMapper looks for all records in a table when using discriminators
121
+ # when using the parent model , we'll look for the earliest ancestor class
122
+ # that is a nested set.
123
+ def get_class #:nodoc:
124
+ klass = self
125
+ klass = klass.superclass while klass.superclass.respond_to?(:is_nested_set?) && klass.superclass.is_nested_set?
126
+ klass
127
+ end
128
+ end # mod ClassMethods
129
+
130
+ module InstanceMethods
131
+ ##
132
+ # move self / node to a position in the set. position can _only_ be changed through this
133
+ #
134
+ # @example [Usage]
135
+ # * node.move :higher # moves node higher unless it is at the top of parent
136
+ # * node.move :lower # moves node lower unless it is at the bottom of parent
137
+ # * node.move :below => other # moves this node below other resource in the set
138
+ # * node.move :into => other # same as setting a parent-relationship
139
+ #
140
+ # @param vector <Symbol, Hash> A symbol, or a key-value pair that describes the requested movement
141
+ #
142
+ # @option :higher<Symbol> move node higher
143
+ # @option :highest<Symbol> move node to the top of the list (within its parent)
144
+ # @option :lower<Symbol> move node lower
145
+ # @option :lowest<Symbol> move node to the bottom of the list (within its parent)
146
+ # @option :indent<Symbol> move node into sibling above
147
+ # @option :outdent<Symbol> move node out below its current parent
148
+ # @option :root<Symbol|Hash|Resource> move node to root. If passed an object / hash, it uses the scope of that. Otherwise, it uses currently set scope.
149
+ # @option :into<Resource> move node into another node
150
+ # @option :above<Resource> move node above other node
151
+ # @option :below<Resource> move node below other node
152
+ # @option :to<Integer> move node to a specific location in the nested set
153
+ # @see move_without_saving
154
+
155
+ def move(vector)
156
+ transaction do
157
+ move_without_saving(vector)
158
+ save!
159
+ end
160
+ reload
161
+ end
162
+
163
+ def level
164
+ ancestors.length
165
+ end
166
+
167
+ # Gets the root of this node
168
+ def root
169
+ get_class.first(root_hash.merge(:lft.lt => lft, :rgt.gt => rgt))
170
+ end
171
+
172
+ # Gets all the roots of this node's tree
173
+ def roots
174
+ get_class.all(root_hash.merge(:order => [:lft.asc]))
175
+ end
176
+
177
+ # Gets all ancestors of this node
178
+ def ancestors
179
+ get_class.all(scope_hash.merge(:lft.lt => lft, :rgt.gt => rgt, :order => [:lft.asc]))
180
+ end
181
+
182
+ # Same as ancestors, but also including this node
183
+ def self_and_ancestors
184
+ get_class.all(scope_hash.merge(:lft.lte => lft, :rgt.gte => rgt, :order => [:lft.asc]))
185
+ end
186
+
187
+ # Gets all nodes that share the same parent node, except for this node
188
+ def siblings
189
+ get_class.all(scope_and_parent_hash.merge(:order => [:lft.asc], :lft.not => lft))
190
+ end
191
+
192
+ # Same as siblings, but returns this node as well
193
+ def self_and_siblings
194
+ get_class.all(scope_and_parent_hash.merge(:order => [:lft.asc]))
195
+ end
196
+
197
+ # Returns next node with same parent, or nil
198
+ def next_sibling
199
+ get_class.first(scope_and_parent_hash.merge(:lft.gt => rgt, :order => [:lft.asc]))
200
+ end
201
+
202
+ # Returns previous node with same parent, or nil
203
+ def previous_sibling
204
+ get_class.first(scope_and_parent_hash.merge(:rgt.lt => lft, :order => [:rgt.desc]))
205
+ end
206
+
207
+ # Returns the full set within this scope
208
+ def full_set
209
+ get_class.all(scope_hash)
210
+ end
211
+
212
+ # Gets all descendents of this node
213
+ def descendents
214
+ get_class.all(scope_hash.merge(:rgt.lt => rgt, :lft.gt => lft, :order => [:lft.asc]))
215
+ end
216
+
217
+
218
+ # Same as descendents, but returns self as well
219
+ def self_and_descendents
220
+ get_class.all(scope_hash.merge(:rgt.lte => rgt, :lft.gte => lft, :order => [:lft.asc]))
221
+ end
222
+
223
+
224
+ # Fixed spelling for when English majors are peering over your shoulder
225
+ def descendants; descendents; end
226
+ def self_and_descendants; self_and_descendents; end
227
+
228
+ # Retrieves the nodes without any children.
229
+ def leaves
230
+ get_class.leaves(self)
231
+ end
232
+
233
+ def attributes_set(hash) #:nodoc:
234
+ hash = hash || {}
235
+ hash.each { |k,v| attribute_set(k,v) }
236
+ end
237
+
238
+ # Destroys the current node and all children nodes, running their before and after hooks
239
+ # Returns the destroyed objects
240
+ def destroy
241
+ sads = self_and_descendants
242
+ hooks = get_class.const_get('INSTANCE_HOOKS')
243
+ before_methods = hooks[:destroy][:before].map { |hash| hash[:name] }
244
+ after_methods = hooks[:destroy][:after].map { |hash| hash[:name] }
245
+ # Trigger all the before :destroy methods
246
+ sads.each { |sad| before_methods.each { |bf| sad.send(bf) } }
247
+ # dup is called here because destroy! likes to clear out the array, understandably.
248
+ transaction do
249
+ sads.dup.destroy!
250
+ adjust_gap!(full_set, lft, -(rgt - lft + 1))
251
+ end
252
+ # Now go through after all the after :destroy methods.
253
+ sads.each { |sad| after_methods.each { |bf| sad.send(bf) } }
254
+ end
255
+
256
+ # Same as @destroy, but does not run the hooks
257
+ def destroy!
258
+ sad = self_and_descendants
259
+ transaction do
260
+ sad.dup.destroy!
261
+ adjust_gap!(full_set, lft, -(rgt - lft + 1))
262
+ end
263
+ sad
264
+ end
265
+
266
+ protected
267
+ def skip_adjust=(var) #:nodoc:
268
+ @skip_adjust = true
269
+ end
270
+
271
+ def adjust_gap!(*args) #:nodoc:
272
+ get_class.adjust_gap!(*args)
273
+ end
274
+
275
+ def get_finder_hash(*args)
276
+ ret = {}
277
+ args.each { |arg| get_class.ias_options[arg].each { |s| ret[s] = send(s) } }
278
+ ret
279
+ end
280
+
281
+ def root_hash
282
+ ret = {}
283
+ get_class.child_keys.each { |ck| ret[ck] = nil }
284
+ scope_hash.merge(ret)
285
+ end
286
+
287
+ def scope_and_parent_hash
288
+ get_finder_hash(:child_key, :scope)
289
+ end
290
+
291
+ def extract_scope(hash)
292
+ get_class.extract_scope(hash)
293
+ end
294
+
295
+ def scope_hash
296
+ get_finder_hash(:scope)
297
+ end
298
+
299
+ def parent_hash
300
+ get_finder_hash(:child_key)
301
+ end
302
+
303
+ def same_scope?(obj)
304
+ case obj
305
+ when get_class then scope_hash == obj.send(:scope_hash)
306
+ when Hash then scope_hash == obj
307
+ when nil then true
308
+ end
309
+ end
310
+
311
+ def valid_scope?(hash)
312
+ get_class.valid_scope?(hash)
313
+ end
314
+
315
+ def move_without_saving(vector)
316
+ # Do some checking of the variable...
317
+ if vector.respond_to?(:'[]') && vector.respond_to?(:size) && vector.size == 1
318
+ action = vector.keys[0]
319
+ obj = vector[action]
320
+ elsif vector.is_a?(Symbol)
321
+ obj = nil
322
+ action = vector
323
+ else
324
+ raise 'You must pass either a symbol or a hash with one property to the method "move".'
325
+ end
326
+
327
+
328
+ # Convenience methods
329
+ ret_value = case action
330
+ when :higher then previous_sibling ? move_without_saving(:above => previous_sibling) : false
331
+ when :highest then move_without_saving(:to => parent ? (parent.lft + 1) : 1)
332
+ when :lower then next_sibling ? move_without_saving(:below => next_sibling) : false
333
+ when :lowest then parent ? move_without_saving(:to => parent.rgt - 1) : move_without_saving(:root)
334
+ when :indent then previous_sibling ? move_without_saving(:into => previous_sibling) : false
335
+ when :outdent then parent ? move_without_saving(:below => parent) : false
336
+ else :no_action
337
+ end
338
+ return ret_value unless ret_value == :no_action
339
+
340
+ this_gap = lft.to_i > 0 && rgt.to_i > 0 ? rgt - lft : 1
341
+ old_parent = parent
342
+ new_scope = nil
343
+ max = nil
344
+
345
+ # Here's where the real heavy lifting happens. Any action can be taken
346
+ # care of by :root, :above, :below, or :to
347
+ pos, adjust_at, p_obj = case action
348
+ when :root
349
+ new_scope = obj ? extract_scope(obj) : scope_hash
350
+ max = (get_class.max(:rgt, new_scope) || 0) + 1
351
+ when :into then [obj.rgt, obj.rgt - 1, obj]
352
+ when :above then [obj.lft, obj.lft - 1, obj.parent]
353
+ when :below then [obj.rgt + 1, obj.rgt, obj.parent]
354
+ when :to
355
+ pos = obj.to_i
356
+ p_obj = get_class.first(scope_hash.merge(:lft.lt => pos, :rgt.gt => pos, :order => [:lft.desc]))
357
+ [pos, pos - 1, p_obj]
358
+ else raise 'Invalid action sent to the method "move": ' + action.to_s
359
+ end
360
+
361
+ old_scope = nil
362
+ new_scope ||= extract_scope(p_obj) if p_obj
363
+
364
+ max ||= (get_class.max(:rgt, new_scope || scope_hash) || 0) + 1
365
+ if pos == 0 || pos > max
366
+ raise "You cannot move a node outside of the bounds of the tree. You passed: #{pos}. Acceptable numbers are 1 through #{max}"
367
+ end
368
+
369
+ raise 'You are trying to move a node into one that has not been saved yet.' if p_obj && p_obj.lft.nil?
370
+
371
+ if lft
372
+ adjustment = pos < lft ? this_gap + 1 : 0
373
+ raise 'Illegal move: you are trying to move a node within itself' if pos.between?(lft+adjustment,rgt+adjustment) && same_scope?(new_scope)
374
+ end
375
+
376
+ # make a new hole and assign parent
377
+ #
378
+ # Note: with identity map on an already saved object, making a hole
379
+ # will alter lft & rgt values immediately, so we need to keep copies
380
+ old_lft, old_rgt = lft, rgt if lft && rgt
381
+ adjust_gap!(get_class.full_set(new_scope || scope_hash) , adjust_at, this_gap + 1) if adjust_at
382
+
383
+ # Do we need to move the node (already present in the tree), or just save the attributes?
384
+ if lft && (pos != old_lft || !same_scope?(new_scope))
385
+ # Move elements
386
+ if same_scope?(new_scope)
387
+ move_by = pos - (old_lft + adjustment)
388
+ full_set.all(:lft.gte => old_lft + adjustment, :rgt.lte => old_rgt + adjustment).adjust!({:lft => move_by, :rgt => move_by}, true)
389
+ else # Things have to be done a little differently if moving scope
390
+ move_by = pos - old_lft
391
+ old_scope = extract_scope(self)
392
+ sads = self_and_descendants
393
+ sads.adjust!({:lft => move_by, :rgt => move_by}, true)
394
+ # Update the attributes to match how they are in the database now.
395
+ # Be sure to do this between adjust! and setting the new scope
396
+ attribute_set(:rgt, old_rgt + move_by)
397
+ attribute_set(:lft, old_lft + move_by)
398
+
399
+ sads.each { |d| d.update!(new_scope)}
400
+ end
401
+
402
+ # Close hole
403
+ if old_scope
404
+ adjust_gap!(get_class.full_set(old_scope), old_lft, -(this_gap + 1))
405
+ else
406
+ adjustment += 1 if parent == old_parent
407
+ adjust_gap!(full_set, old_lft + adjustment, -(this_gap + 1))
408
+ end
409
+ else # just save the attributes
410
+ attribute_set(:lft, pos)
411
+ attribute_set(:rgt, lft + this_gap)
412
+ attributes_set(p_obj.send(:scope_hash)) if p_obj
413
+ end
414
+ # We set parent here because we don't want to throw errors with dirty
415
+ # tracking during all of the adjust! and update!(scope) calls
416
+ self.parent = p_obj
417
+
418
+ end
419
+
420
+ def get_class #:no_doc:
421
+ self.class.get_class
422
+ end
423
+ end # mod InstanceMethods
424
+
425
+ Model.send(:include, self)
426
+ end # mod AwesomeSet
427
+ end # mod Is
428
+ end # mod DM