ancestry 3.0.2 → 3.0.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8088b41fa07084ef6a761d52c2d33b051c24eae3
4
- data.tar.gz: ed5f0c0678e1d238adb4b68ed6d6239204c397a1
3
+ metadata.gz: c1c70fafe20e537ed6cb98a60e671dc01fd540ac
4
+ data.tar.gz: 583d3382374a761c553d99329f71be36ac423222
5
5
  SHA512:
6
- metadata.gz: c97d7541dbba13b6a768edab18a21c2916fbb9ea0f61674f4831a0e56f0eed83bf3b1a18bd743b1f2e0eb26b6b78ca21c69271052e3fb1042d8cf194c74802ca
7
- data.tar.gz: 7274762db4aee75b633c14fb158b68da2e4e525c4c5e5557c9c164aa6a2dc8d68c7cdc96f443112a327b0a21b76a3acc57cb09c7653ec5b24a4a839266b7313b
6
+ metadata.gz: 55df9269223d805cd09f8fb87b5e0a25cad36aaad892e3527217cd086e15c6105e5552c0d7cd108645fe9a947712986a0885252f4b71a861eec1525be374f0b7
7
+ data.tar.gz: 3f8ea178c2f3cc918ddd1fb79f8af4e3b8ed63704cd764bed42d8fb79da3b5316ab7160871eade25f8af9aac3bb004a298abc59f17e5b9759085c1ef8eb3c41d
data/README.md CHANGED
@@ -12,13 +12,20 @@ gems, integrity checking, integrity restoration, arrangement of
12
12
  (sub)tree into hashes and different strategies for dealing with orphaned
13
13
  records.
14
14
 
15
+ NOTE:
16
+
17
+ - Ancestry 3.x supports Rails 5.0 and earlier.
18
+ - Ancestry 4.0 only supports rails 5.0 and higher
19
+
15
20
  # Installation
16
21
 
17
22
  To apply Ancestry to any `ActiveRecord` model, follow these simple steps:
18
23
 
24
+
19
25
  ## Install
20
26
 
21
27
  * Add to Gemfile:
28
+
22
29
  ```ruby
23
30
  # Gemfile
24
31
 
@@ -26,6 +33,7 @@ gem 'ancestry'
26
33
  ```
27
34
 
28
35
  * Install required gems:
36
+
29
37
  ```bash
30
38
  $ bundle install
31
39
  ```
@@ -33,11 +41,13 @@ $ bundle install
33
41
 
34
42
  ## Add ancestry column to your table
35
43
  * Create migration:
44
+
36
45
  ```bash
37
46
  $ rails g migration add_ancestry_to_[table] ancestry:string:index
38
47
  ```
39
48
 
40
49
  * Migrate your database:
50
+
41
51
  ```bash
42
52
  $ rake db:migrate
43
53
  ```
@@ -24,7 +24,7 @@ EOF
24
24
 
25
25
  s.authors = ['Stefan Kroes', 'Keenan Brock']
26
26
  s.email = 'keenan@thebrocks.net'
27
- s.homepage = 'http://github.com/stefankroes/ancestry'
27
+ s.homepage = 'https://github.com/stefankroes/ancestry'
28
28
  s.license = 'MIT'
29
29
 
30
30
  s.files = [
@@ -2,7 +2,7 @@ module Ancestry
2
2
  module ClassMethods
3
3
  # Fetch tree node if necessary
4
4
  def to_node object
5
- if object.is_a?(self.ancestry_base_class) then object else find(object) end
5
+ if object.is_a?(self.ancestry_base_class) then object else unscoped_where{|scope| scope.find object} end
6
6
  end
7
7
 
8
8
  # Scope on relative depth options
@@ -206,5 +206,24 @@ module Ancestry
206
206
  yield self.ancestry_base_class.unscope(:where)
207
207
  end
208
208
  end
209
+
210
+ ANCESTRY_UNCAST_TYPES = [:string, :uuid, :text].freeze
211
+ if ActiveSupport::VERSION::STRING < "4.0"
212
+ def primary_key_is_an_integer?
213
+ if defined?(@primary_key_is_an_integer)
214
+ @primary_key_is_an_integer
215
+ else
216
+ @primary_key_is_an_integer = !ANCESTRY_UNCAST_TYPES.include?(columns_hash[primary_key.to_s].type)
217
+ end
218
+ end
219
+ else
220
+ def primary_key_is_an_integer?
221
+ if defined?(@primary_key_is_an_integer)
222
+ @primary_key_is_an_integer
223
+ else
224
+ @primary_key_is_an_integer = !ANCESTRY_UNCAST_TYPES.include?(type_for_attribute(primary_key))
225
+ end
226
+ end
227
+ end
209
228
  end
210
229
  end
@@ -46,7 +46,10 @@ module Ancestry
46
46
  scope :siblings_of, lambda { |object| where(sibling_conditions(object)) }
47
47
  scope :ordered_by_ancestry, Proc.new { |order|
48
48
  if %w(mysql mysql2 sqlite sqlite3 postgresql).include?(connection.adapter_name.downcase) && ActiveRecord::VERSION::MAJOR >= 5
49
- reorder(Arel.sql("coalesce(#{connection.quote_table_name(table_name)}.#{connection.quote_column_name(ancestry_column)}, '')"), order)
49
+ reorder(
50
+ Arel::Nodes::Ascending.new(Arel::Nodes::NamedFunction.new('COALESCE', [arel_table[ancestry_column], Arel.sql("''")])),
51
+ order
52
+ )
50
53
  else
51
54
  reorder(Arel.sql("(CASE WHEN #{connection.quote_table_name(table_name)}.#{connection.quote_column_name(ancestry_column)} IS NULL THEN 0 ELSE 1 END), #{connection.quote_table_name(table_name)}.#{connection.quote_column_name(ancestry_column)}"), order)
52
55
  end
@@ -1,11 +1,14 @@
1
1
  module Ancestry
2
2
  module InstanceMethods
3
+ BEFORE_LAST_SAVE_SUFFIX = ActiveRecord::VERSION::STRING >= '5.1.0' ? '_before_last_save' : '_was'
4
+ IN_DATABASE_SUFFIX = ActiveRecord::VERSION::STRING >= '5.1.0' ? '_in_database' : '_was'
5
+
3
6
  # Validate that the ancestors don't include itself
4
7
  def ancestry_exclude_self
5
8
  errors.add(:base, "#{self.class.name.humanize} cannot be a descendant of itself.") if ancestor_ids.include? self.id
6
9
  end
7
10
 
8
- # Update descendants with new ancestry
11
+ # Update descendants with new ancestry (before save)
9
12
  def update_descendants_with_new_ancestry
10
13
  # If enabled and node is existing and ancestry was updated and the new ancestry is sane ...
11
14
  if !ancestry_callbacks_disabled? && !new_record? && ancestry_changed? && sane_ancestry?
@@ -16,7 +19,9 @@ module Ancestry
16
19
  descendant.update_attribute(
17
20
  self.ancestry_base_class.ancestry_column,
18
21
  descendant.read_attribute(descendant.class.ancestry_column).gsub(
22
+ # child_ancestry_was
19
23
  /^#{self.child_ancestry}/,
24
+ # future child_ancestry
20
25
  if ancestors? then "#{read_attribute self.class.ancestry_column }/#{id}" else id.to_s end
21
26
  )
22
27
  )
@@ -25,7 +30,7 @@ module Ancestry
25
30
  end
26
31
  end
27
32
 
28
- # Apply orphan strategy
33
+ # Apply orphan strategy (before destroy - no changes)
29
34
  def apply_orphan_strategy
30
35
  if !ancestry_callbacks_disabled? && !new_record?
31
36
  case self.ancestry_base_class.orphan_strategy
@@ -35,6 +40,7 @@ module Ancestry
35
40
  new_ancestry = if descendant.ancestry == child_ancestry
36
41
  nil
37
42
  else
43
+ # child_ancestry did not change so child_ancestry_was will work here
38
44
  descendant.ancestry.gsub(/^#{child_ancestry}\//, '')
39
45
  end
40
46
  descendant.update_attribute descendant.class.ancestry_column, new_ancestry
@@ -61,7 +67,7 @@ module Ancestry
61
67
  end
62
68
  end
63
69
 
64
- # Touch each of this record's ancestors
70
+ # Touch each of this record's ancestors (after save)
65
71
  def touch_ancestors_callback
66
72
  if !ancestry_callbacks_disabled? && self.ancestry_base_class.touch_ancestors
67
73
  # Touch each of the old *and* new ancestors
@@ -73,12 +79,17 @@ module Ancestry
73
79
  end
74
80
  end
75
81
 
76
- # The ancestry value for this record's children
82
+ # The ancestry value for this record's children (before save)
83
+ # This is technically child_ancestry_was
77
84
  def child_ancestry
78
85
  # New records cannot have children
79
86
  raise Ancestry::AncestryException.new('No child ancestry for new record. Save record before performing tree operations.') if new_record?
80
87
 
81
- if self.send("#{self.ancestry_base_class.ancestry_column}_was").blank? then id.to_s else "#{self.send "#{self.ancestry_base_class.ancestry_column}_was"}/#{id}" end
88
+ if self.send("#{self.ancestry_base_class.ancestry_column}#{IN_DATABASE_SUFFIX}").blank?
89
+ id.to_s
90
+ else
91
+ "#{self.send "#{self.ancestry_base_class.ancestry_column}#{IN_DATABASE_SUFFIX}"}/#{id}"
92
+ end
82
93
  end
83
94
 
84
95
  # Ancestors
@@ -93,10 +104,6 @@ module Ancestry
93
104
  changed.include?(self.ancestry_base_class.ancestry_column.to_s)
94
105
  end
95
106
 
96
- def parse_ancestry_column obj
97
- obj.to_s.split('/').map { |id| cast_primary_key(id) }
98
- end
99
-
100
107
  def ancestor_ids
101
108
  parse_ancestry_column(read_attribute(self.ancestry_base_class.ancestry_column))
102
109
  end
@@ -109,14 +116,20 @@ module Ancestry
109
116
  self.ancestry_base_class.scope_depth(depth_options, depth).ordered_by_ancestry.where ancestor_conditions
110
117
  end
111
118
 
119
+ # deprecate
112
120
  def ancestor_was_conditions
113
- {primary_key_with_table => ancestor_ids_was}
121
+ {primary_key_with_table => ancestor_ids_before_last_save}
114
122
  end
115
123
 
124
+ # deprecated - probably don't want to use anymore
116
125
  def ancestor_ids_was
117
126
  parse_ancestry_column(send("#{self.ancestry_base_class.ancestry_column}_was"))
118
127
  end
119
128
 
129
+ def ancestor_ids_before_last_save
130
+ parse_ancestry_column(send("#{self.ancestry_base_class.ancestry_column}#{BEFORE_LAST_SAVE_SUFFIX}"))
131
+ end
132
+
120
133
  def path_ids
121
134
  ancestor_ids + [id]
122
135
  end
@@ -143,6 +156,8 @@ module Ancestry
143
156
 
144
157
  # Parent
145
158
 
159
+ # currently parent= does not work in after save callbacks
160
+ # assuming that parent hasn't changed
146
161
  def parent= parent
147
162
  write_attribute(self.ancestry_base_class.ancestry_column, if parent.nil? then nil else parent.child_ancestry end)
148
163
  end
@@ -288,16 +303,10 @@ module Ancestry
288
303
 
289
304
  private
290
305
 
291
- def cast_primary_key(key)
292
- if [:string, :uuid, :text].include? primary_key_type
293
- key
294
- else
295
- key.to_i
296
- end
297
- end
298
-
299
- def primary_key_type
300
- @primary_key_type ||= column_for_attribute(self.class.primary_key).type
306
+ def parse_ancestry_column obj
307
+ obj_ids = obj.to_s.split('/')
308
+ obj_ids.map!(&:to_i) if self.class.primary_key_is_an_integer?
309
+ obj_ids
301
310
  end
302
311
 
303
312
  def unscoped_descendants
@@ -306,9 +315,10 @@ module Ancestry
306
315
  end
307
316
  end
308
317
 
318
+ # works with after save context (hence before_last_save)
309
319
  def unscoped_current_and_previous_ancestors
310
320
  unscoped_where do |scope|
311
- scope.where id: (ancestor_ids + ancestor_ids_was).uniq
321
+ scope.where id: (ancestor_ids + ancestor_ids_before_last_save).uniq
312
322
  end
313
323
  end
314
324
 
@@ -41,7 +41,7 @@ module Ancestry
41
41
  def subtree_conditions(object)
42
42
  t = arel_table
43
43
  node = to_node(object)
44
- descendant_conditions(object).or(t[primary_key].eq(node.id))
44
+ descendant_conditions(node).or(t[primary_key].eq(node.id))
45
45
  end
46
46
 
47
47
  def sibling_conditions(object)
@@ -1,3 +1,3 @@
1
1
  module Ancestry
2
- VERSION = "3.0.2"
2
+ VERSION = "3.0.3"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ancestry
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.2
4
+ version: 3.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefan Kroes
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2018-04-23 00:00:00.000000000 Z
12
+ date: 2018-10-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
@@ -134,7 +134,7 @@ files:
134
134
  - lib/ancestry/instance_methods.rb
135
135
  - lib/ancestry/materialized_path.rb
136
136
  - lib/ancestry/version.rb
137
- homepage: http://github.com/stefankroes/ancestry
137
+ homepage: https://github.com/stefankroes/ancestry
138
138
  licenses:
139
139
  - MIT
140
140
  metadata:
@@ -158,7 +158,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
158
158
  version: '0'
159
159
  requirements: []
160
160
  rubyforge_project:
161
- rubygems_version: 2.6.13
161
+ rubygems_version: 2.6.14.1
162
162
  signing_key:
163
163
  specification_version: 4
164
164
  summary: Organize ActiveRecord model into a tree structure