genealogy 2.1.1 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/lib/genealogy.rb +1 -0
- data/lib/genealogy/complex_query_methods.rb +46 -0
- data/lib/genealogy/genealogy.rb +18 -13
- data/lib/genealogy/query_methods.rb +46 -12
- data/lib/genealogy/version.rb +1 -1
- metadata +12 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 0b07b270d80c0d915e01f45424b27989f04b920a79bb4cd973484589d47530a6
|
4
|
+
data.tar.gz: 40753227d2d9e72a7779aa6fa28365f2a37e6e5121df1b0a821d71a835850de7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
data/lib/genealogy/genealogy.rb
CHANGED
@@ -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
|
-
# @
|
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
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
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
|
-
# @
|
145
|
-
|
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
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
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
|
data/lib/genealogy/version.rb
CHANGED
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.
|
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:
|
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: '
|
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: '
|
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: '
|
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: '
|
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.
|
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:
|