decoupage_administratif 0.2.0 → 0.3.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 +4 -4
- data/CHANGELOG.md +13 -0
- data/README.md +8 -2
- data/lib/decoupage_administratif/base_model.rb +87 -21
- data/lib/decoupage_administratif/commune.rb +3 -2
- data/lib/decoupage_administratif/departement.rb +1 -0
- data/lib/decoupage_administratif/search.rb +3 -3
- data/lib/decoupage_administratif/territory_strategies.rb +6 -6
- data/lib/decoupage_administratif/version.rb +1 -1
- data/sig/decoupage_administratif/base_model.rbs +9 -1
- data/sig/decoupage_administratif/commune.rbs +2 -2
- data/sig/decoupage_administratif/territory_strategies.rbs +5 -5
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 849db1d260bc7687d74158eb642a2a49c63dffc39bf20ff53635231b9875d955
|
|
4
|
+
data.tar.gz: cae4db6bbef56db6524be7d77fd6a10a78656d17c98c07ed4bd1d4f823e0b2d4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ddc815e22292f9318259046937c23fc7754ba1565d47ac9c49ea24c57bab315a858d4c2df782e04b7bb92583bd011ee64d770b0f11084f6f8e02a2779e7d4ac6
|
|
7
|
+
data.tar.gz: 8933516d15633dc0f4a60df35ae2039a28328c2e0ca29eda49867810b1edeebf2c177917c2d94b011cc134811c6c6799934062f37d226e9f9c17f03b870e7208
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.3.0] - 2025-11-14
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **BaseModel**: Support for array values in `where` method to filter on multiple values (e.g., `where(commune_type: [:commune_actuelle, :arrondissement_municipal])`)
|
|
7
|
+
- **Commune**: Municipal districts (arrondissements of Paris, Lyon, Marseille) are now included in `Commune.actuelles` method
|
|
8
|
+
- New commune type `:arrondissement_municipal` for municipal districts
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- `Commune.actuelles` now returns both current communes and municipal districts (`:commune_actuelle` and `:arrondissement_municipal`)
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- RBS type signature for `commune_type` attribute corrected from `String` to `Symbol`
|
|
15
|
+
|
|
3
16
|
## [0.2.0] - 2025-08-29
|
|
4
17
|
|
|
5
18
|
### Changed
|
data/README.md
CHANGED
|
@@ -63,10 +63,13 @@ Exemple d’utilisation basique :
|
|
|
63
63
|
DecoupageAdministratif::Region.all
|
|
64
64
|
|
|
65
65
|
# Trouver une commune par code INSEE
|
|
66
|
-
# Une commune a un commune_type qui peut être
|
|
66
|
+
# Une commune a un commune_type qui peut être :commune_actuelle, :commune_deleguee (anciennes communes), :commune_associee (anciennes communes avec un statut spécial) ou :arrondissement_municipal (arrondissements de Paris, Lyon, Marseille)
|
|
67
67
|
commune = DecoupageAdministratif::Commune.find('75056')
|
|
68
68
|
puts commune.nom # => "Paris"
|
|
69
69
|
|
|
70
|
+
# Lister toutes les communes actuelles (communes actuelles + arrondissements municipaux)
|
|
71
|
+
DecoupageAdministratif::Commune.actuelles
|
|
72
|
+
|
|
70
73
|
# Lister les départements d'une région
|
|
71
74
|
DecoupageAdministratif::Region.find('84').departements
|
|
72
75
|
|
|
@@ -224,10 +227,13 @@ Basic usage example:
|
|
|
224
227
|
DecoupageAdministratif::Region.all
|
|
225
228
|
|
|
226
229
|
# Find a municipality by INSEE code
|
|
227
|
-
# A municipality has a `commune_type` which can be :commune_actuelle (current municipalities), :commune_deleguee (former municipalities),
|
|
230
|
+
# A municipality has a `commune_type` which can be :commune_actuelle (current municipalities), :commune_deleguee (former municipalities), :commune_associee (former municipalities with a special status), or :arrondissement_municipal (municipal districts of Paris, Lyon, Marseille)
|
|
228
231
|
commune = DecoupageAdministratif::Commune.find('75056')
|
|
229
232
|
puts commune.nom # => "Paris"
|
|
230
233
|
|
|
234
|
+
# List all current municipalities (current communes + municipal districts)
|
|
235
|
+
DecoupageAdministratif::Commune.actuelles
|
|
236
|
+
|
|
231
237
|
# List departments of a region
|
|
232
238
|
DecoupageAdministratif::Region.find('84').departements
|
|
233
239
|
|
|
@@ -24,37 +24,103 @@ module DecoupageAdministratif
|
|
|
24
24
|
all.find { |item| criteria.all? { |k, v| item.send(k) == v } }
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
# Filter records based on criteria with optional case-insensitive and partial matching
|
|
28
|
+
#
|
|
27
29
|
# @param args [Hash] keyword arguments containing filter criteria and options
|
|
28
|
-
# @option args [Boolean] :case_insensitive perform case-insensitive matching
|
|
29
|
-
# @option args [Boolean] :partial allow partial string matching (
|
|
30
|
+
# @option args [Boolean] :case_insensitive perform case-insensitive matching (only for String values)
|
|
31
|
+
# @option args [Boolean] :partial allow partial string matching using contains logic (only for String values)
|
|
32
|
+
#
|
|
33
|
+
# @note When filtering with an Array:
|
|
34
|
+
# - Without options: checks if the item's value is included in the array (similar to SQL IN clause)
|
|
35
|
+
# - With case_insensitive: performs case-insensitive matching for each array element (String values only)
|
|
36
|
+
# - With partial: checks if item's value contains any of the array elements (String values only)
|
|
37
|
+
# - Both options can be combined for case-insensitive partial matching
|
|
38
|
+
#
|
|
30
39
|
# @return [Array] an array of all items that match the criteria
|
|
31
|
-
#
|
|
40
|
+
#
|
|
41
|
+
# @example Simple filtering
|
|
32
42
|
# DecoupageAdministratif::Departement.where(code_region: '52')
|
|
43
|
+
#
|
|
44
|
+
# @example Case-insensitive and partial matching
|
|
33
45
|
# DecoupageAdministratif::Commune.where(nom: 'pari', case_insensitive: true, partial: true)
|
|
34
|
-
#
|
|
46
|
+
#
|
|
47
|
+
# @example Array filtering (SQL IN-like behavior)
|
|
48
|
+
# DecoupageAdministratif::Commune.where(commune_type: [:commune_actuelle, :arrondissement_municipal])
|
|
49
|
+
#
|
|
50
|
+
# @example Array filtering with case-insensitive matching
|
|
51
|
+
# DecoupageAdministratif::Commune.where(nom: ['paris', 'mamers'], case_insensitive: true)
|
|
52
|
+
#
|
|
53
|
+
# @example Array filtering with partial matching
|
|
54
|
+
# DecoupageAdministratif::Commune.where(nom: ['par', 'mam'], partial: true)
|
|
35
55
|
def where(**args)
|
|
36
|
-
# Separate options from criteria
|
|
37
56
|
case_insensitive = args.delete(:case_insensitive) || false
|
|
38
57
|
partial = args.delete(:partial) || false
|
|
39
58
|
|
|
40
59
|
all.select do |item|
|
|
41
|
-
args.all?
|
|
42
|
-
item_value = item.send(key)
|
|
43
|
-
|
|
44
|
-
if case_insensitive && value.is_a?(String) && item_value.is_a?(String)
|
|
45
|
-
if partial
|
|
46
|
-
item_value.downcase.include?(value.downcase)
|
|
47
|
-
else
|
|
48
|
-
item_value.downcase == value.downcase
|
|
49
|
-
end
|
|
50
|
-
elsif partial && value.is_a?(String) && item_value.is_a?(String)
|
|
51
|
-
item_value.include?(value)
|
|
52
|
-
else
|
|
53
|
-
item_value == value
|
|
54
|
-
end
|
|
55
|
-
end
|
|
60
|
+
args.all? { |key, value| matches?(item.send(key), value, case_insensitive, partial) }
|
|
56
61
|
end
|
|
57
|
-
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
# Check if item_value matches the filter_value based on options
|
|
67
|
+
#
|
|
68
|
+
# @param item_value [Object] the value from the item being filtered
|
|
69
|
+
# @param filter_value [Object, Array] the value(s) to filter by
|
|
70
|
+
# @param case_insensitive [Boolean] whether to perform case-insensitive matching
|
|
71
|
+
# @param partial [Boolean] whether to perform partial (contains) matching
|
|
72
|
+
# @return [Boolean] true if the value matches the filter
|
|
73
|
+
def matches?(item_value, filter_value, case_insensitive, partial)
|
|
74
|
+
return match_array?(item_value, filter_value, case_insensitive, partial) if filter_value.is_a?(Array)
|
|
75
|
+
return match_single_value?(item_value, filter_value, case_insensitive, partial) if strings?(item_value, filter_value)
|
|
76
|
+
|
|
77
|
+
item_value == filter_value
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Check if item_value matches any value in the filter array
|
|
81
|
+
#
|
|
82
|
+
# @param item_value [Object] the value from the item being filtered
|
|
83
|
+
# @param filter_array [Array] array of values to match against
|
|
84
|
+
# @param case_insensitive [Boolean] whether to perform case-insensitive matching
|
|
85
|
+
# @param partial [Boolean] whether to perform partial (contains) matching
|
|
86
|
+
# @return [Boolean] true if the value matches any element in the array
|
|
87
|
+
def match_array?(item_value, filter_array, case_insensitive, partial)
|
|
88
|
+
return filter_array.include?(item_value) unless apply_string_options?(item_value, filter_array)
|
|
89
|
+
|
|
90
|
+
filter_array.any? { |filter_element| match_single_value?(item_value, filter_element, case_insensitive, partial) }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Check if item_value matches filter_value with string options
|
|
94
|
+
#
|
|
95
|
+
# @param item_value [String] the string value from the item
|
|
96
|
+
# @param filter_value [String] the string value to filter by
|
|
97
|
+
# @param case_insensitive [Boolean] whether to perform case-insensitive matching
|
|
98
|
+
# @param partial [Boolean] whether to use partial (contains) matching
|
|
99
|
+
# @return [Boolean] true if strings match according to options
|
|
100
|
+
def match_single_value?(item_value, filter_value, case_insensitive, partial)
|
|
101
|
+
return item_value.downcase.include?(filter_value.downcase) if case_insensitive && partial
|
|
102
|
+
return item_value.downcase == filter_value.downcase if case_insensitive
|
|
103
|
+
return item_value.include?(filter_value) if partial
|
|
104
|
+
|
|
105
|
+
item_value == filter_value
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Check if both values are strings
|
|
109
|
+
#
|
|
110
|
+
# @param value1 [Object] first value to check
|
|
111
|
+
# @param value2 [Object] second value to check
|
|
112
|
+
# @return [Boolean] true if both values are strings
|
|
113
|
+
def strings?(value1, value2)
|
|
114
|
+
value1.is_a?(String) && value2.is_a?(String)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Check if string matching options should be applied
|
|
118
|
+
#
|
|
119
|
+
# @param item_value [Object] the value from the item
|
|
120
|
+
# @param filter_array [Array] the filter array
|
|
121
|
+
# @return [Boolean] true if string options should be applied
|
|
122
|
+
def apply_string_options?(item_value, filter_array)
|
|
123
|
+
item_value.is_a?(String) && filter_array.all? { |v| v.is_a?(String) }
|
|
58
124
|
end
|
|
59
125
|
end
|
|
60
126
|
end
|
|
@@ -20,6 +20,7 @@ module DecoupageAdministratif
|
|
|
20
20
|
# - :commune_actuelle: Standard current commune
|
|
21
21
|
# - :commune_deleguee: Delegated commune
|
|
22
22
|
# - :commune_associee: Associated commune
|
|
23
|
+
# - :arrondissement_municipal: Municipal district (Paris, Lyon, Marseille)
|
|
23
24
|
# @note Default value is :commune_actuelle
|
|
24
25
|
attr_reader :code, :nom, :zone, :region_code, :departement_code, :commune_type
|
|
25
26
|
|
|
@@ -54,9 +55,9 @@ module DecoupageAdministratif
|
|
|
54
55
|
end
|
|
55
56
|
end
|
|
56
57
|
|
|
57
|
-
# @return [Array<Commune>] a collection of all communes _actuelles_
|
|
58
|
+
# @return [Array<Commune>] a collection of all communes _actuelles_ and municipal districts
|
|
58
59
|
def self.actuelles
|
|
59
|
-
@actuelles ||= where(commune_type:
|
|
60
|
+
@actuelles ||= where(commune_type: %i[commune_actuelle arrondissement_municipal])
|
|
60
61
|
end
|
|
61
62
|
|
|
62
63
|
# @raise [NotFoundError] if no region is found for the code
|
|
@@ -118,7 +118,7 @@ module DecoupageAdministratif
|
|
|
118
118
|
# Check if only one key and it is nil
|
|
119
119
|
# @return [Boolean]
|
|
120
120
|
def only_nil_key?
|
|
121
|
-
@codes.keys.uniq.
|
|
121
|
+
@codes.keys.uniq.one? && @codes.keys.first.nil?
|
|
122
122
|
end
|
|
123
123
|
|
|
124
124
|
# Should add departement to result?
|
|
@@ -154,7 +154,7 @@ module DecoupageAdministratif
|
|
|
154
154
|
# @return [void]
|
|
155
155
|
def search_for_epcis
|
|
156
156
|
@epcis = []
|
|
157
|
-
return if @codes.keys.uniq.
|
|
157
|
+
return if @codes.keys.uniq.one? && @codes.keys.first.nil?
|
|
158
158
|
|
|
159
159
|
@codes.each_value do |communes|
|
|
160
160
|
# Find EPCIs that match all communes in the current group
|
|
@@ -171,7 +171,7 @@ module DecoupageAdministratif
|
|
|
171
171
|
# @return [void]
|
|
172
172
|
def search_for_communes
|
|
173
173
|
@communes = []
|
|
174
|
-
return if @codes.keys.uniq.
|
|
174
|
+
return if @codes.keys.uniq.one? && @codes.keys.first.nil?
|
|
175
175
|
|
|
176
176
|
@codes.each_value do |communes|
|
|
177
177
|
communes.map { |commune| @communes << commune }
|
|
@@ -10,7 +10,7 @@ module DecoupageAdministratif
|
|
|
10
10
|
def includes_any_commune_code?(commune_insee_codes)
|
|
11
11
|
return false if commune_insee_codes.empty?
|
|
12
12
|
|
|
13
|
-
check_inclusion(commune_insee_codes)
|
|
13
|
+
check_inclusion?(commune_insee_codes)
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def insee_codes
|
|
@@ -21,7 +21,7 @@ module DecoupageAdministratif
|
|
|
21
21
|
|
|
22
22
|
attr_reader :territory
|
|
23
23
|
|
|
24
|
-
def check_inclusion(commune_insee_codes)
|
|
24
|
+
def check_inclusion?(commune_insee_codes)
|
|
25
25
|
raise NotImplementedError, "Must be implemented by subclass"
|
|
26
26
|
end
|
|
27
27
|
|
|
@@ -33,7 +33,7 @@ module DecoupageAdministratif
|
|
|
33
33
|
class CommuneStrategy < Base
|
|
34
34
|
private
|
|
35
35
|
|
|
36
|
-
def check_inclusion(commune_insee_codes)
|
|
36
|
+
def check_inclusion?(commune_insee_codes)
|
|
37
37
|
commune_insee_codes.include?(territory.code)
|
|
38
38
|
end
|
|
39
39
|
|
|
@@ -45,7 +45,7 @@ module DecoupageAdministratif
|
|
|
45
45
|
class DepartementStrategy < Base
|
|
46
46
|
private
|
|
47
47
|
|
|
48
|
-
def check_inclusion(commune_insee_codes)
|
|
48
|
+
def check_inclusion?(commune_insee_codes)
|
|
49
49
|
departement_prefix = territory.code.length == 2 ? territory.code : territory.code[0..1]
|
|
50
50
|
commune_insee_codes.any? { |commune_code| commune_code.start_with?(departement_prefix) }
|
|
51
51
|
end
|
|
@@ -58,7 +58,7 @@ module DecoupageAdministratif
|
|
|
58
58
|
class RegionStrategy < Base
|
|
59
59
|
private
|
|
60
60
|
|
|
61
|
-
def check_inclusion(commune_insee_codes)
|
|
61
|
+
def check_inclusion?(commune_insee_codes)
|
|
62
62
|
departement_codes = territory.departements.map(&:code)
|
|
63
63
|
commune_insee_codes.any? do |commune_code|
|
|
64
64
|
dept_code = commune_code.length >= 3 && commune_code[0..1].to_i >= 96 ? commune_code[0..2] : commune_code[0..1]
|
|
@@ -74,7 +74,7 @@ module DecoupageAdministratif
|
|
|
74
74
|
class EpciStrategy < Base
|
|
75
75
|
private
|
|
76
76
|
|
|
77
|
-
def check_inclusion(commune_insee_codes)
|
|
77
|
+
def check_inclusion?(commune_insee_codes)
|
|
78
78
|
epci_commune_codes = territory.membres.map { |membre| membre[:code] || membre["code"] }
|
|
79
79
|
(epci_commune_codes & commune_insee_codes).any?
|
|
80
80
|
end
|
|
@@ -2,6 +2,14 @@ module DecoupageAdministratif
|
|
|
2
2
|
module BaseModel
|
|
3
3
|
def find: (String) -> untyped
|
|
4
4
|
def find_by: (Hash[Symbol, untyped]) -> untyped
|
|
5
|
-
def where: (
|
|
5
|
+
def where: (**untyped) -> Array[untyped]
|
|
6
|
+
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def matches?: (untyped item_value, untyped filter_value, bool case_insensitive, bool partial) -> bool
|
|
10
|
+
def match_array?: (untyped item_value, Array[untyped] filter_array, bool case_insensitive, bool partial) -> bool
|
|
11
|
+
def match_single_value?: (String item_value, String filter_value, bool case_insensitive, bool partial) -> bool
|
|
12
|
+
def strings?: (untyped value1, untyped value2) -> bool
|
|
13
|
+
def apply_string_options?: (untyped item_value, Array[untyped] filter_array) -> bool
|
|
6
14
|
end
|
|
7
15
|
end
|
|
@@ -8,7 +8,7 @@ module DecoupageAdministratif
|
|
|
8
8
|
attr_reader zone: String
|
|
9
9
|
attr_reader region_code: String
|
|
10
10
|
attr_reader departement_code: String
|
|
11
|
-
attr_reader commune_type:
|
|
11
|
+
attr_reader commune_type: Symbol
|
|
12
12
|
|
|
13
13
|
def initialize: (
|
|
14
14
|
code: String,
|
|
@@ -21,7 +21,7 @@ module DecoupageAdministratif
|
|
|
21
21
|
|
|
22
22
|
def self.all: () -> Array[Commune]
|
|
23
23
|
|
|
24
|
-
def self.
|
|
24
|
+
def self.actuelles: () -> Array[Commune]
|
|
25
25
|
|
|
26
26
|
def region: () -> Region
|
|
27
27
|
|
|
@@ -11,7 +11,7 @@ module DecoupageAdministratif
|
|
|
11
11
|
|
|
12
12
|
attr_reader territory: untyped
|
|
13
13
|
|
|
14
|
-
def check_inclusion
|
|
14
|
+
def check_inclusion?: (Array[String] commune_insee_codes) -> bool
|
|
15
15
|
|
|
16
16
|
def calculate_insee_codes: () -> Array[String]
|
|
17
17
|
end
|
|
@@ -19,7 +19,7 @@ module DecoupageAdministratif
|
|
|
19
19
|
class CommuneStrategy < Base
|
|
20
20
|
private
|
|
21
21
|
|
|
22
|
-
def check_inclusion
|
|
22
|
+
def check_inclusion?: (Array[String] commune_insee_codes) -> bool
|
|
23
23
|
|
|
24
24
|
def calculate_insee_codes: () -> Array[String]
|
|
25
25
|
end
|
|
@@ -27,7 +27,7 @@ module DecoupageAdministratif
|
|
|
27
27
|
class DepartementStrategy < Base
|
|
28
28
|
private
|
|
29
29
|
|
|
30
|
-
def check_inclusion
|
|
30
|
+
def check_inclusion?: (Array[String] commune_insee_codes) -> bool
|
|
31
31
|
|
|
32
32
|
def calculate_insee_codes: () -> Array[String]
|
|
33
33
|
end
|
|
@@ -35,7 +35,7 @@ module DecoupageAdministratif
|
|
|
35
35
|
class RegionStrategy < Base
|
|
36
36
|
private
|
|
37
37
|
|
|
38
|
-
def check_inclusion
|
|
38
|
+
def check_inclusion?: (Array[String] commune_insee_codes) -> bool
|
|
39
39
|
|
|
40
40
|
def calculate_insee_codes: () -> Array[String]
|
|
41
41
|
end
|
|
@@ -43,7 +43,7 @@ module DecoupageAdministratif
|
|
|
43
43
|
class EpciStrategy < Base
|
|
44
44
|
private
|
|
45
45
|
|
|
46
|
-
def check_inclusion
|
|
46
|
+
def check_inclusion?: (Array[String] commune_insee_codes) -> bool
|
|
47
47
|
|
|
48
48
|
def calculate_insee_codes: () -> Array[String]
|
|
49
49
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: decoupage_administratif
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Lucien Mollard
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-
|
|
11
|
+
date: 2025-11-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rake
|