genealogy 2.1.1 → 2.2.0

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
- SHA1:
3
- metadata.gz: 471eb61c7e2d5e29edac1c8796a0f35ab53d9405
4
- data.tar.gz: ae2209d3c81a59f012649877ad9678eff6b4ddd4
2
+ SHA256:
3
+ metadata.gz: 0b07b270d80c0d915e01f45424b27989f04b920a79bb4cd973484589d47530a6
4
+ data.tar.gz: 40753227d2d9e72a7779aa6fa28365f2a37e6e5121df1b0a821d71a835850de7
5
5
  SHA512:
6
- metadata.gz: 4fe4635b6ad80fe2cbd6dfb6d6fb1c8740e7bb9c8e429e8aac7fb82231c3e1520c9d19a10587f6e0485d984030cfde9e6f625f5b94cc303a0b82b9c20aae0700
7
- data.tar.gz: ff633aa6af193a025ae015edbaca533956b8e2fa2f391640bd213aacb08ecaccc00bfebf27d654b516cf053bb5f44b8efa09de049f22357b0c5474376b1cc2b9
6
+ metadata.gz: 67a933f6335a5a8d95dffa4284e8a198062a09cca098888e6d785608a22fbf463164322232376e1c83d7f1ea0362254ebbfac4e7e36bc8c500bb97510eb06b21
7
+ data.tar.gz: edb60383749cd28a0458252102e994b676796025a1fb55e716cc16a23f20810f23c3c9801163a4e93c06bbae45078cd41d71be40cbdd3808ddf4d8e1145b7845
data/lib/genealogy.rb CHANGED
@@ -3,6 +3,7 @@ require File.join(File.expand_path(File.dirname(__FILE__)), 'genealogy/exception
3
3
  require File.join(File.expand_path(File.dirname(__FILE__)), 'genealogy/util_methods')
4
4
  require File.join(File.expand_path(File.dirname(__FILE__)), 'genealogy/ineligible_methods')
5
5
  require File.join(File.expand_path(File.dirname(__FILE__)), 'genealogy/query_methods')
6
+ require File.join(File.expand_path(File.dirname(__FILE__)), 'genealogy/complex_query_methods')
6
7
  require File.join(File.expand_path(File.dirname(__FILE__)), 'genealogy/alter_methods')
7
8
  require File.join(File.expand_path(File.dirname(__FILE__)), 'genealogy/current_spouse_methods')
8
9
  require File.join(File.expand_path(File.dirname(__FILE__)), 'genealogy/genealogy')
@@ -0,0 +1,46 @@
1
+ module Genealogy
2
+ module ComplexQueryMethods
3
+ extend ActiveSupport::Concern
4
+ include Constants
5
+
6
+ module ClassMethods
7
+
8
+ # Takes a splat argument of gclass objects and @return [ActiveRecord::Relation] of the first individual(s) to show up in the ancestors of the given people
9
+ # It moves up one generation at a time for each individual, stopping when there is a shared ancestor id or when there are no more ancestors
10
+ # If called on two full siblings, it will return both parents, as they appears in the same generation
11
+ # If called on two half siblings, it will return only the shared parent
12
+ # If one individual is the ancestor of the other, it will return that individual as the least common shared ancestors
13
+ # If none are found, it will return an empty AR relation.
14
+ def lowest_common_ancestors(*people)
15
+ raise ArgumentError, "all inputs must be an instance of the #{gclass} class" if people.select{|record| !record.is_a?(gclass)}.length > 0
16
+
17
+ parent_ids_temp = people.map{|person| [person.id]}
18
+ parent_ids_store = parent_ids_temp.clone
19
+
20
+ generation_count = 1
21
+
22
+ while parent_ids_temp.select{|array_of_ids| array_of_ids.length > 0}.length > 0
23
+ next_gen_ids = parent_ids_temp.map{|ids| gclass.where(id: ids).select([:father_id, :mother_id]).map{|result| [result.father_id, result.mother_id]}.flatten.compact}
24
+
25
+ next_gen_ids.each_with_index do |ids, index|
26
+ parent_ids_store[index] += ids
27
+ parent_ids_temp[index] = ids
28
+ end
29
+
30
+ if parent_ids_store.reduce(:&).length > 0
31
+ return gclass.where(id: (parent_ids_store.reduce(:&)))
32
+ else
33
+ generation_count += 1
34
+ end
35
+ end
36
+ gclass.where(id: nil)
37
+ end
38
+ end
39
+
40
+ def lowest_common_ancestors(*people)
41
+ people << self
42
+ self.class.lowest_common_ancestors(*people)
43
+ end
44
+
45
+ end
46
+ end
@@ -3,36 +3,41 @@ module Genealogy
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  module ClassMethods
6
-
6
+
7
7
  include Genealogy::Constants
8
8
 
9
9
  # gives to ActiveRecord model geneaogy capabilities. Modules UtilMethods QueryMethods IneligibleMethods AlterMethods and SpouseMethods are included
10
10
  # @param [Hash] options
11
11
  # @option options [Boolean] current_spouse (false) specifies whether to track or not individual's current spouse
12
12
  # @option options [Boolean] perform_validation (true) specifies whether to perform validation or not while altering pedigree that is before updating relatives external keys
13
- # @option options [Boolean, Symbol] ineligibility (:pedigree) specifies ineligibility setting. If `false` ineligibility checks will be disabled and you can assign, as a relative, any individuals you want.
13
+ # @option options [Boolean, Symbol] ineligibility (:pedigree) specifies ineligibility setting. If `false` ineligibility checks will be disabled and you can assign, as a relative, any individuals you want.
14
14
  # This can be dangerous because you can build nosense loop (in terms of pedigree). If pass one of symbols `:pedigree` or `:pedigree_and_dates` ineligibility checks will be enabled.
15
- # More specifically with `:pedigree` (or `true`) checks will be based on pedigree topography, i.e., ineligible children will include ancestors. With `:pedigree_and_dates` check will also be based on
15
+ # More specifically with `:pedigree` (or `true`) checks will be based on pedigree topography, i.e., ineligible children will include ancestors. With `:pedigree_and_dates` check will also be based on
16
16
  # procreation ages (min and max, male and female) and life expectancy (male and female), i.e. an individual born 200 years before is an ineligible mother
17
- # @option options [Hash] limit_ages (min_male_procreation_age:12, max_male_procreation_age:75, min_female_procreation_age:9, max_female_procreation_age:50, max_male_life_expectancy:110, max_female_life_expectancy:110)
17
+ # @option options [Hash] limit_ages (min_male_procreation_age:12, max_male_procreation_age:75, min_female_procreation_age:9, max_female_procreation_age:50, max_male_life_expectancy:110, max_female_life_expectancy:110)
18
18
  # specifies one or more limit ages different than defaults
19
19
  # @option options [Hash] column_names (sex:'sex', father_id:'father_id', mother_id:'mother_id', current_spouse_id:'current_spouse_id', birth_date:'birth_date', death_date:'death_date') specifies column names to map database individual table
20
- # @option options [Array] sex_values (['M','F']) specifies values used in database sex column
21
- # @return [void]
20
+ # @option options [Array] sex_values (['M','F']) specifies values used in database sex column
21
+ # @return [void]
22
+
22
23
  def has_parents options = {}
23
24
 
24
25
  include Genealogy::UtilMethods
25
26
  include Genealogy::QueryMethods
27
+ include Genealogy::ComplexQueryMethods
26
28
  include Genealogy::IneligibleMethods
27
29
  include Genealogy::AlterMethods
28
30
  include Genealogy::CurrentSpouseMethods
29
31
 
32
+
30
33
  check_has_parents_options(options)
31
34
 
32
35
  # keep track of the original extend class to prevent wrong scopes in query method in case of STI
33
36
  class_attribute :gclass, instance_writer: false
34
- self.gclass = self
35
-
37
+ self.gclass = self
38
+
39
+ self.extend(Genealogy::ComplexQueryMethods::ClassMethods)
40
+
36
41
  class_attribute :ineligibility_level, instance_accessor: false
37
42
  self.ineligibility_level = case options[:ineligibility]
38
43
  when :pedigree
@@ -77,7 +82,7 @@ module Genealogy
77
82
  self.sex_values = options[:sex_values] || DEFAULTS[:sex_values]
78
83
  self.sex_male_value = self.sex_values.first
79
84
  self.sex_female_value = self.sex_values.last
80
-
85
+
81
86
  # validation
82
87
  validates_presence_of :sex
83
88
  validates_format_of :sex, with: /[#{sex_values.join}]/
@@ -85,14 +90,14 @@ module Genealogy
85
90
  tracked_relatives = [:father, :mother]
86
91
  tracked_relatives << :current_spouse if current_spouse_enabled
87
92
  tracked_relatives.each do |k|
88
- belongs_to k, class_name: self, foreign_key: self.send("#{k}_id_column")
93
+ belongs_to k, class_name: self.name, foreign_key: self.send("#{k}_id_column")
89
94
  end
90
95
 
91
- has_many :children_as_father, class_name: self, foreign_key: self.father_id_column, dependent: :nullify
92
- has_many :children_as_mother, class_name: self, foreign_key: self.mother_id_column, dependent: :nullify
96
+ has_many :children_as_father, class_name: self.name, foreign_key: self.father_id_column, dependent: :nullify
97
+ has_many :children_as_mother, class_name: self.name, foreign_key: self.mother_id_column, dependent: :nullify
93
98
 
94
99
  end
95
100
 
96
101
  end
97
102
 
98
- end
103
+ end
@@ -128,27 +128,61 @@ module Genealogy
128
128
 
129
129
  # get list of known ancestrors iterateing over parents
130
130
  # @param [Hash] options
131
- # @return [ActiveRecord::Relation] list of ancestors
131
+ # @option options [Symbol] generations lets you limit how many generations will be included in the output.
132
+ # @return [ActiveRecord::Relation] list of ancestors (limited by a number of generations if so indicated)
132
133
  def ancestors(options = {})
133
134
  ids = []
134
- remaining_ids = parents.compact.map(&:id)
135
- until remaining_ids.empty?
136
- ids << remaining_ids.shift
137
- remaining_ids += gclass.find(ids.last).parents.compact.map(&:id)
135
+ if options[:generations]
136
+ raise ArgumentError, ":generations option must be an Integer" unless options[:generations].is_a? Integer
137
+ generation_count = 0
138
+ generation_ids = parents.compact.map(&:id)
139
+ while (generation_count < options[:generations]) && (generation_ids.length > 0)
140
+ next_gen_ids = []
141
+ ids += generation_ids
142
+ until generation_ids.empty?
143
+ ids.unshift(generation_ids.shift)
144
+ next_gen_ids += gclass.find(ids.first).parents.compact.map(&:id)
145
+ end
146
+ generation_ids = next_gen_ids
147
+ generation_count += 1
148
+ end
149
+ else
150
+ remaining_ids = parents.compact.map(&:id)
151
+ until remaining_ids.empty?
152
+ ids << remaining_ids.shift
153
+ remaining_ids += gclass.find(ids.last).parents.compact.map(&:id)
154
+ end
138
155
  end
139
156
  gclass.where(id: ids)
140
157
  end
141
158
 
142
159
 
143
160
  # get list of known descendants iterateing over children ...
144
- # @return [ActiveRecord::Relation] list of descendants
145
- def descendants
161
+ # @param [Hash] options
162
+ # @option options [Symbol] generations lets you limit how many generations will be included in the output.
163
+ # @return [ActiveRecord::Relation] list of descendants (limited by a number of generations if so indicated)
164
+ def descendants(options = {})
146
165
  ids = []
147
- remaining_ids = children.map(&:id)
148
- until remaining_ids.empty?
149
- ids << remaining_ids.shift
150
- remaining_ids += gclass.find(ids.last).children.pluck(:id)
151
- # break if (remaining_ids - ids).empty? can be necessary in case of loop. Idem for ancestors method
166
+ if options[:generations]
167
+ generation_count = 0
168
+ generation_ids = children.map(&:id)
169
+ while (generation_count < options[:generations]) && (generation_ids.length > 0)
170
+ next_gen_ids = []
171
+ ids += generation_ids
172
+ until generation_ids.empty?
173
+ ids.unshift(generation_ids.shift)
174
+ next_gen_ids += gclass.find(ids.first).children.map(&:id)
175
+ end
176
+ generation_ids = next_gen_ids
177
+ generation_count += 1
178
+ end
179
+ else
180
+ remaining_ids = children.map(&:id)
181
+ until remaining_ids.empty?
182
+ ids << remaining_ids.shift
183
+ remaining_ids += gclass.find(ids.last).children.pluck(:id)
184
+ # break if (remaining_ids - ids).empty? can be necessary in case of loop. Idem for ancestors method
185
+ end
152
186
  end
153
187
  gclass.where(id: ids)
154
188
  end
@@ -1,3 +1,3 @@
1
1
  module Genealogy
2
- VERSION = "2.1.1"
2
+ VERSION = "2.2.0"
3
3
  end
metadata CHANGED
@@ -1,43 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: genealogy
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.1
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - masciugo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-05-11 00:00:00.000000000 Z
11
+ date: 2018-06-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - ">"
18
18
  - !ruby/object:Gem::Version
19
- version: '3.2'
19
+ version: '4.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - ">"
25
25
  - !ruby/object:Gem::Version
26
- version: '3.2'
26
+ version: '4.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: activesupport
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - ">"
32
32
  - !ruby/object:Gem::Version
33
- version: '3.2'
33
+ version: '4.0'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - ">"
39
39
  - !ruby/object:Gem::Version
40
- version: '3.2'
40
+ version: '4.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: sqlite3
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -244,6 +244,7 @@ extra_rdoc_files: []
244
244
  files:
245
245
  - lib/genealogy.rb
246
246
  - lib/genealogy/alter_methods.rb
247
+ - lib/genealogy/complex_query_methods.rb
247
248
  - lib/genealogy/constants.rb
248
249
  - lib/genealogy/current_spouse_methods.rb
249
250
  - lib/genealogy/exceptions.rb
@@ -272,9 +273,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
272
273
  version: '0'
273
274
  requirements: []
274
275
  rubyforge_project: "[none]"
275
- rubygems_version: 2.2.2
276
+ rubygems_version: 2.7.6
276
277
  signing_key:
277
278
  specification_version: 4
278
279
  summary: Make ActiveRecord model act as a pedigree
279
280
  test_files: []
280
- has_rdoc: