aixm 1.2.1 → 1.3.1

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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +23 -3
  4. data/README.md +37 -2
  5. data/exe/ckmid +1 -7
  6. data/exe/mkmid +1 -7
  7. data/lib/aixm/classes.rb +3 -1
  8. data/lib/aixm/component/address.rb +12 -15
  9. data/lib/aixm/component/approach_lighting.rb +11 -16
  10. data/lib/aixm/component/fato.rb +22 -34
  11. data/lib/aixm/component/frequency.rb +10 -15
  12. data/lib/aixm/component/geometry/arc.rb +2 -3
  13. data/lib/aixm/component/geometry/border.rb +6 -10
  14. data/lib/aixm/component/geometry/circle.rb +4 -4
  15. data/lib/aixm/component/geometry/point.rb +4 -4
  16. data/lib/aixm/component/geometry/rhumb_line.rb +4 -4
  17. data/lib/aixm/component/geometry.rb +4 -4
  18. data/lib/aixm/component/helipad.rb +13 -20
  19. data/lib/aixm/component/layer.rb +6 -8
  20. data/lib/aixm/component/lighting.rb +12 -17
  21. data/lib/aixm/component/runway.rb +26 -38
  22. data/lib/aixm/component/service.rb +12 -16
  23. data/lib/aixm/component/surface.rb +8 -10
  24. data/lib/aixm/component/timesheet.rb +9 -10
  25. data/lib/aixm/component/timetable.rb +6 -7
  26. data/lib/aixm/component/vasis.rb +6 -8
  27. data/lib/aixm/component/vertical_limit.rb +8 -8
  28. data/lib/aixm/component.rb +3 -2
  29. data/lib/aixm/concerns/association.rb +381 -0
  30. data/lib/aixm/concerns/memoize.rb +107 -0
  31. data/lib/aixm/concerns/xml_builder.rb +34 -0
  32. data/lib/aixm/document.rb +52 -21
  33. data/lib/aixm/feature/airport.rb +44 -47
  34. data/lib/aixm/feature/airspace.rb +29 -34
  35. data/lib/aixm/feature/generic.rb +67 -0
  36. data/lib/aixm/feature/navigational_aid/designated_point.rb +11 -13
  37. data/lib/aixm/feature/navigational_aid/dme.rb +12 -15
  38. data/lib/aixm/feature/navigational_aid/marker.rb +12 -15
  39. data/lib/aixm/feature/navigational_aid/ndb.rb +13 -16
  40. data/lib/aixm/feature/navigational_aid/tacan.rb +15 -17
  41. data/lib/aixm/feature/navigational_aid/vor.rb +16 -19
  42. data/lib/aixm/feature/navigational_aid.rb +7 -7
  43. data/lib/aixm/feature/obstacle.rb +20 -21
  44. data/lib/aixm/feature/obstacle_group.rb +19 -20
  45. data/lib/aixm/feature/organisation.rb +11 -12
  46. data/lib/aixm/feature/unit.rb +16 -18
  47. data/lib/aixm/feature.rb +26 -7
  48. data/lib/aixm/object.rb +1 -1
  49. data/lib/aixm/refinements.rb +57 -0
  50. data/lib/aixm/schedule/date.rb +13 -1
  51. data/lib/aixm/schedule/date_time.rb +56 -0
  52. data/lib/aixm/schedule/time.rb +5 -1
  53. data/lib/aixm/shortcuts.rb +8 -2
  54. data/lib/aixm/version.rb +1 -1
  55. data/lib/aixm.rb +5 -3
  56. data/schemas/ofmx/0.1/OFMX-Snapshot.xsd +6 -1
  57. data.tar.gz.sig +2 -3
  58. metadata +26 -38
  59. metadata.gz.sig +2 -3
  60. data/lib/aixm/association.rb +0 -378
  61. data/lib/aixm/memoize.rb +0 -105
@@ -0,0 +1,56 @@
1
+ using AIXM::Refinements
2
+
3
+ module AIXM
4
+ module Schedule
5
+
6
+ # Datetimes suitable for schedules
7
+ #
8
+ # This class combines +AIXM::Schedule::Date+ and +AIXM::Schedule::Time+:
9
+ #
10
+ # @example
11
+ # datetime = AIXM.datetime('2022-04-20', '20:00') # => 2022-04-20 20:00
12
+ # datetime.date # => 2022-04-20
13
+ # datetime.date.class # => AIXM::Schedule::Date
14
+ # datetime.time # => 20:00
15
+ # datetime.time.class # => AIXM::Schedule::Time
16
+ class DateTime
17
+
18
+ # Date part
19
+ #
20
+ # @return [AIXM::Schedule::Date]
21
+ attr_reader :date
22
+
23
+ # Time part
24
+ #
25
+ # @return [AIXM::Schedule::Time]
26
+ attr_reader :time
27
+
28
+ # Parse the given representation of date and time.
29
+ #
30
+ # @param date [AIXM::Schedule::Date]
31
+ # @param time [AIXM::Schedule::Time]
32
+ def initialize(date, time)
33
+ fail(ArgumentError, 'invalid date') unless date.instance_of? AIXM::Schedule::Date
34
+ fail(ArgumentError, 'invalid time') unless time.instance_of? AIXM::Schedule::Time
35
+ @date, @time = date, time
36
+ end
37
+
38
+ # Human readable representation such as "2002-05-19 20:00"
39
+ #
40
+ # @return [String]
41
+ def to_s
42
+ [@date.to_s, @time.to_s].join(' ')
43
+ end
44
+
45
+ def inspect
46
+ %Q(#<#{self.class} #{to_s}>)
47
+ end
48
+
49
+ # @see Object#hash
50
+ def hash
51
+ [@date.hash, @time.hash].hash
52
+ end
53
+ end
54
+
55
+ end
56
+ end
@@ -19,6 +19,10 @@ module AIXM
19
19
  # @example
20
20
  # time = AIXM.time('21:30') # => 21:30
21
21
  # time.covered_by?(AIXM.time('20:00')..AIXM.time('02:00')) # => true
22
+ #
23
+ # ===Shortcuts:
24
+ # * +AIXM::BEGINNING_OF_DAY+ - midnight expressed as "00:00"
25
+ # * +AIXM::END_OF_DAY+ - midnight expressed as "24:00"
22
26
  class Time
23
27
  include AIXM::Concerns::HashEquality
24
28
  extend Forwardable
@@ -73,7 +77,7 @@ module AIXM
73
77
  case time_or_event
74
78
  when Symbol
75
79
  self.event = time_or_event
76
- when ::Time, DateTime
80
+ when ::Time, ::DateTime
77
81
  time_or_event = time_or_event.to_time
78
82
  set_time(time_or_event.hour, time_or_event.min, time_or_event.utc_offset)
79
83
  when /\A(\d{2}):?(\d{2}) ?([+-]\d{2}:?\d{2}|UTC)?\z/
@@ -15,10 +15,16 @@ module AIXM
15
15
  # Max flight level used to signal "no upper limit"
16
16
  UNLIMITED = z(999, :qne).freeze
17
17
 
18
+ # Day to signal "whatever date or day"
19
+ ANY_DAY = AIXM.day(:any).freeze
20
+
18
21
  # Timetable used to signal "always active"
19
22
  H24 = timetable(code: :H24).freeze
20
23
 
21
- # Day to signal "whatever date or day"
22
- ANY_DAY = AIXM.day(:any).freeze
24
+ # Time which marks midnight at beginning of the day
25
+ BEGINNING_OF_DAY = AIXM.time('00:00').freeze
26
+
27
+ # Time which marks midnight at end of the day
28
+ END_OF_DAY = AIXM.time('24:00').freeze
23
29
 
24
30
  end
data/lib/aixm/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module AIXM
2
- VERSION = "1.2.1".freeze
2
+ VERSION = "1.3.1".freeze
3
3
  end
data/lib/aixm.rb CHANGED
@@ -8,7 +8,6 @@ require 'forwardable'
8
8
  require 'digest'
9
9
  require 'optparse'
10
10
 
11
- require 'builder'
12
11
  require 'nokogiri'
13
12
  require 'dry/inflector'
14
13
  require 'sun'
@@ -22,10 +21,11 @@ require_relative 'aixm/errors'
22
21
 
23
22
  require_relative 'aixm/classes'
24
23
  require_relative 'aixm/constants'
25
- require_relative 'aixm/memoize'
26
- require_relative 'aixm/association'
27
24
  require_relative 'aixm/payload_hash'
28
25
 
26
+ require_relative 'aixm/concerns/memoize'
27
+ require_relative 'aixm/concerns/association'
28
+ require_relative 'aixm/concerns/xml_builder'
29
29
  require_relative 'aixm/concerns/hash_equality'
30
30
  require_relative 'aixm/concerns/timetable'
31
31
  require_relative 'aixm/concerns/intensity'
@@ -45,6 +45,7 @@ require_relative 'aixm/p'
45
45
  require_relative 'aixm/schedule/date'
46
46
  require_relative 'aixm/schedule/day'
47
47
  require_relative 'aixm/schedule/time'
48
+ require_relative 'aixm/schedule/date_time'
48
49
 
49
50
  require_relative 'aixm/component'
50
51
  require_relative 'aixm/component/address'
@@ -82,6 +83,7 @@ require_relative 'aixm/feature/obstacle'
82
83
  require_relative 'aixm/feature/obstacle_group'
83
84
  require_relative 'aixm/feature/organisation'
84
85
  require_relative 'aixm/feature/unit'
86
+ require_relative 'aixm/feature/generic'
85
87
 
86
88
  require_relative 'aixm/shortcuts'
87
89
  require_relative 'aixm/executables'
@@ -215,7 +215,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
215
215
  </xsd:attribute>
216
216
  <xsd:attribute name="effective" type="xsd:dateTime" use="required">
217
217
  <xsd:annotation>
218
- <xsd:documentation>The date and time used as criteria to select valid versions included in the message</xsd:documentation>
218
+ <xsd:documentation>The beginning date and time used as criteria to select valid versions included in the message</xsd:documentation>
219
+ </xsd:annotation>
220
+ </xsd:attribute>
221
+ <xsd:attribute name="expiration" type="xsd:dateTime">
222
+ <xsd:annotation>
223
+ <xsd:documentation>The optional end date and time used as criteria to select valid versions included in the message</xsd:documentation>
219
224
  </xsd:annotation>
220
225
  </xsd:attribute>
221
226
  </xsd:complexType>
data.tar.gz.sig CHANGED
@@ -1,3 +1,2 @@
1
- /5�D�����owD~
2
- Z��.W;6��(s���Γ��~3��=�P(˲n/�f��,v����RS��Bi��Lb@��:*�<Ɨ�٩}W���wcFv�ܼ�VC��
3
- uL��y4Du3��"�����jwnTT���m����O*���n�
1
+ ��W���c
2
+ 滺P~��[9�z'�:������w3w�41�S�/0�Н$f�t��w;�m�O[�l�ϭ�ώK�Wy��z6�t���L��3�����3xjB��d�!}P�9�hQe�-���%�*4��֎�_B��Ku�'Y���5�K��)4FX�}�����&�� 0�PR����B�ՠ�B32�cZ���ӎA8v^���w��Ӎ�����ɹ��M�7|&6;�%7\���4n�M��!�O����r�
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aixm
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.1
4
+ version: 1.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sven Schwyn
@@ -10,42 +10,27 @@ bindir: exe
10
10
  cert_chain:
11
11
  - |
12
12
  -----BEGIN CERTIFICATE-----
13
- MIIDcDCCAligAwIBAgIBATANBgkqhkiG9w0BAQUFADA/MQ0wCwYDVQQDDARydWJ5
14
- MRkwFwYKCZImiZPyLGQBGRYJYml0Y2V0ZXJhMRMwEQYKCZImiZPyLGQBGRYDY29t
15
- MB4XDTIxMTEwODE0MzIyM1oXDTIyMTEwODE0MzIyM1owPzENMAsGA1UEAwwEcnVi
16
- eTEZMBcGCgmSJomT8ixkARkWCWJpdGNldGVyYTETMBEGCgmSJomT8ixkARkWA2Nv
17
- bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANwuD4geNdhpSVNJTtHb
18
- fmVAoPxmER4oyGgaVJSidn/OjU5PcpdypMI/WIxfvjfFizq6kQYAsJZbCr6fG+UN
19
- 2dZGMXcAC/uKQL5nYESjCPJ4IJP/SC+fiiEpxHQk7PNFoiUVRUieUZIAfHZAdnY3
20
- ye1/niW7ud0vwKIMrysKWxjgkE0Y6Af1QLzV/6brVRRC5MvHIzYJd8BiSP+wY1O8
21
- VElw1f6d90KEz2vaQfX7vCxrzIbvAnYiSvM0AIPy/zigTqpW6w3w4sQxQj81oQ9U
22
- 9vDYtQzXj0c9VrSLvb0DgiGug2cU2bDjA4L3cBE1szX4tbfo8syYqMq51/kTGDxW
23
- YNUCAwEAAaN3MHUwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0OBBYEFJ8r
24
- wy1HraZDqg3Khf9UonMWMtJUMB0GA1UdEQQWMBSBEnJ1YnlAYml0Y2V0ZXJhLmNv
25
- bTAdBgNVHRIEFjAUgRJydWJ5QGJpdGNldGVyYS5jb20wDQYJKoZIhvcNAQEFBQAD
26
- ggEBACI7lJKRbnRjz0T4Wb9jH4SE0A2yaHAoBzj96luVDjNyoRT3688trEZS75Sg
27
- GKfChxqKncBrSpxJ0YfWbymNHfUrKhcdSifJ/TtUrTasm6LSnJYLOnLKDO3v8eL3
28
- gRTq8a5wA7Xtijx3MJEyzdeUh7N+UMKuPps/flPgH5yabUxgxrvuhrXF7Z96nrsP
29
- EOmNMTc8H7wo4BAKfuMcI/Gh2oCf+tAhr0bGsXyBikmJ6XA45mrOMgv19M1+aMpn
30
- 1+2Y1+i+4jd1B7qxIgOLxQTNIJiwE0sqU1itFfuesfgUACS7M0IV9u9Bp4hBGNEw
31
- 5JcY2h7owdMxXIvgk1oakgldFJc=
13
+ MIIDODCCAiCgAwIBAgIBATANBgkqhkiG9w0BAQsFADAjMSEwHwYDVQQDDBhydWJ5
14
+ L0RDPWJpdGNldGVyYS9EQz1jb20wHhcNMjIxMTA2MTIzNjUwWhcNMjMxMTA2MTIz
15
+ NjUwWjAjMSEwHwYDVQQDDBhydWJ5L0RDPWJpdGNldGVyYS9EQz1jb20wggEiMA0G
16
+ CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDcLg+IHjXYaUlTSU7R235lQKD8ZhEe
17
+ KMhoGlSUonZ/zo1OT3KXcqTCP1iMX743xYs6upEGALCWWwq+nxvlDdnWRjF3AAv7
18
+ ikC+Z2BEowjyeCCT/0gvn4ohKcR0JOzzRaIlFUVInlGSAHx2QHZ2N8ntf54lu7nd
19
+ L8CiDK8rClsY4JBNGOgH9UC81f+m61UUQuTLxyM2CXfAYkj/sGNTvFRJcNX+nfdC
20
+ hM9r2kH1+7wsa8yG7wJ2IkrzNACD8v84oE6qVusN8OLEMUI/NaEPVPbw2LUM149H
21
+ PVa0i729A4IhroNnFNmw4wOC93ARNbM1+LW36PLMmKjKudf5Exg8VmDVAgMBAAGj
22
+ dzB1MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQWBBSfK8MtR62mQ6oN
23
+ yoX/VKJzFjLSVDAdBgNVHREEFjAUgRJydWJ5QGJpdGNldGVyYS5jb20wHQYDVR0S
24
+ BBYwFIEScnVieUBiaXRjZXRlcmEuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQAYG2na
25
+ ye8OE2DANQIFM/xDos/E4DaPWCJjX5xvFKNKHMCeQYPeZvLICCwyw2paE7Otwk6p
26
+ uvbg2Ks5ykXsbk5i6vxDoeeOLvmxCqI6m+tHb8v7VZtmwRJm8so0eSX0WvTaKnIf
27
+ CAn1bVUggczVdNoBXw9WAILKyw9bvh3Ft740XZrR74sd+m2pGwjCaM8hzLvrVbGP
28
+ DyYhlBeRWyQKQ0WDIsiTSRhzK8HwSTUWjvPwx7SEdIU/HZgyrk0ETObKPakVu6bH
29
+ kAyiRqgxF4dJviwtqI7mZIomWL63+kXLgjOjMe1SHxfIPo/0ji6+r1p4KYa7o41v
30
+ fwIwU1MKlFBdsjkd
32
31
  -----END CERTIFICATE-----
33
- date: 2022-04-23 00:00:00.000000000 Z
32
+ date: 2022-11-06 00:00:00.000000000 Z
34
33
  dependencies:
35
- - !ruby/object:Gem::Dependency
36
- name: builder
37
- requirement: !ruby/object:Gem::Requirement
38
- requirements:
39
- - - "~>"
40
- - !ruby/object:Gem::Version
41
- version: '3'
42
- type: :runtime
43
- prerelease: false
44
- version_requirements: !ruby/object:Gem::Requirement
45
- requirements:
46
- - - "~>"
47
- - !ruby/object:Gem::Version
48
- version: '3'
49
34
  - !ruby/object:Gem::Dependency
50
35
  name: nokogiri
51
36
  requirement: !ruby/object:Gem::Requirement
@@ -222,7 +207,6 @@ files:
222
207
  - exe/mkmid
223
208
  - lib/aixm.rb
224
209
  - lib/aixm/a.rb
225
- - lib/aixm/association.rb
226
210
  - lib/aixm/classes.rb
227
211
  - lib/aixm/component.rb
228
212
  - lib/aixm/component/address.rb
@@ -245,11 +229,14 @@ files:
245
229
  - lib/aixm/component/timetable.rb
246
230
  - lib/aixm/component/vasis.rb
247
231
  - lib/aixm/component/vertical_limit.rb
232
+ - lib/aixm/concerns/association.rb
248
233
  - lib/aixm/concerns/hash_equality.rb
249
234
  - lib/aixm/concerns/intensity.rb
250
235
  - lib/aixm/concerns/marking.rb
236
+ - lib/aixm/concerns/memoize.rb
251
237
  - lib/aixm/concerns/remarks.rb
252
238
  - lib/aixm/concerns/timetable.rb
239
+ - lib/aixm/concerns/xml_builder.rb
253
240
  - lib/aixm/config.rb
254
241
  - lib/aixm/constants.rb
255
242
  - lib/aixm/d.rb
@@ -260,6 +247,7 @@ files:
260
247
  - lib/aixm/feature.rb
261
248
  - lib/aixm/feature/airport.rb
262
249
  - lib/aixm/feature/airspace.rb
250
+ - lib/aixm/feature/generic.rb
263
251
  - lib/aixm/feature/navigational_aid.rb
264
252
  - lib/aixm/feature/navigational_aid/designated_point.rb
265
253
  - lib/aixm/feature/navigational_aid/dme.rb
@@ -271,13 +259,13 @@ files:
271
259
  - lib/aixm/feature/obstacle_group.rb
272
260
  - lib/aixm/feature/organisation.rb
273
261
  - lib/aixm/feature/unit.rb
274
- - lib/aixm/memoize.rb
275
262
  - lib/aixm/object.rb
276
263
  - lib/aixm/p.rb
277
264
  - lib/aixm/payload_hash.rb
278
265
  - lib/aixm/r.rb
279
266
  - lib/aixm/refinements.rb
280
267
  - lib/aixm/schedule/date.rb
268
+ - lib/aixm/schedule/date_time.rb
281
269
  - lib/aixm/schedule/day.rb
282
270
  - lib/aixm/schedule/time.rb
283
271
  - lib/aixm/shortcuts.rb
@@ -324,7 +312,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
324
312
  - !ruby/object:Gem::Version
325
313
  version: '0'
326
314
  requirements: []
327
- rubygems_version: 3.3.12
315
+ rubygems_version: 3.3.25
328
316
  signing_key:
329
317
  specification_version: 4
330
318
  summary: Builder for AIXM/OFMX aeronautical information
metadata.gz.sig CHANGED
@@ -1,3 +1,2 @@
1
- }�j�%�1dZX{I������:�bq�K�@�bb��l��heP�c M��AV.蚲�%�i:OX�+�`<���En2��ʎT���!)b��@ЅZ>1�ח�
2
- S4�z���4D�qvh��܋w��%U7�(���ZoH@�Cv�ǟ������82ʿk�E(I@��>�[&���|�x4o�{�ǍqEP���v�$��&��aI �ټ�"�iz�;c���M�X����7(�؝
3
- F}Ue�h�0
1
+ Z���%ى��Z�0�~2[}O-'B<RgWG��Rf
2
+ ���(����@B!�NB~�d,��� ~��q4Q7WwV�E`fwA�`r�(e=���P��J
@@ -1,378 +0,0 @@
1
- using AIXM::Refinements
2
-
3
- module AIXM
4
-
5
- # Associate features and components with a minimalistic implementation of
6
- # +has_many+, +has_one+ and +belongs_to+ associations.
7
- #
8
- # When adding or assigning an object on the associator (where the +has_many+
9
- # or +has_one+ declaration is made), the object is verified and must be an
10
- # instance of the declared class or a superclass thereof.
11
- #
12
- # When assigning an object on the associated (where the +belongs_to+
13
- # declaration is made), the object is not verified. However, since the actual
14
- # assignment is always delegated to the associator, unacceptable objects will
15
- # raise errors.
16
- #
17
- # @example Simple +has_many+ association
18
- # class Blog
19
- # has_many :posts # :post has to be a key in AIXM::CLASSES
20
- # end
21
- # class Post
22
- # belongs_to :blog
23
- # end
24
- # blog, post = Blog.new, Post.new
25
- # # --either--
26
- # blog.add_post(post) # => Blog
27
- # blog.posts.count # => 1
28
- # blog.posts.first == post # => true
29
- # post.blog == blog # => true
30
- # blog.remove_post(post) # => Blog
31
- # blog.posts.count # => 0
32
- # # --or--
33
- # post.blog = blog # => Blog
34
- # blog.posts.count # => 1
35
- # blog.posts.first == post # => true
36
- # post.blog == blog # => true
37
- # post.blog = nil # => nil
38
- # blog.posts.count # => 0
39
- # # --or--
40
- # post_2 = Post.new
41
- # blog.add_posts([post, post_2])
42
- # blog.posts.count # => 2
43
- # blog.posts == [post, post_2] # => true
44
- # blog.remove_posts([post_2, post])
45
- # blog.posts.count # => 0
46
- #
47
- # @example Simple +has_one+ association
48
- # class Blog
49
- # has_one :posts # :post has to be a key in AIXM::CLASSES
50
- # end
51
- # class Post
52
- # belongs_to :blog
53
- # end
54
- # blog, post = Blog.new, Post.new
55
- # # --either--
56
- # blog.post = post # => Post (standard assignment)
57
- # blog.add_post(post) # => Blog (alternative for chaining)
58
- # blog.post == post # => true
59
- # post.blog == blog # => true
60
- # blog.post = nil # => nil
61
- # blog.post # => nil
62
- # post.blog # => nil
63
- # # --or--
64
- # post.blog = blog # => Blog (standard assignment)
65
- # post.add_blog(blog) # => Post (alternative for chaining)
66
- # post.blog == blog # => true
67
- # blog.post == post # => true
68
- # post.blog = nil # => nil
69
- # post.blog # => nil
70
- # blog.post # => nil
71
- #
72
- # @example Association with readonly +belongs_to+ (idem for +has_one+)
73
- # class Blog
74
- # has_many :posts # :post has to be a key in AIXM::CLASSES
75
- # end
76
- # class Post
77
- # belongs_to :blog, readonly: true
78
- # end
79
- # blog, post = Blog.new, Post.new
80
- # post.blog = blog # => NoMethodError
81
- #
82
- # @example Association with explicit class (idem for +has_one+)
83
- # class Blog
84
- # include AIXM::Association
85
- # has_many :posts, accept: 'Picture'
86
- # end
87
- # class Picture
88
- # include AIXM::Association
89
- # belongs_to :blog
90
- # end
91
- # blog, picture = Blog.new, Picture.new
92
- # blog.add_post(picture)
93
- # blog.posts.first == picture # => true
94
- #
95
- # @example Polymorphic associator (idem for +has_one+)
96
- # class Blog
97
- # has_many :posts, as: :postable
98
- # end
99
- # class Feed
100
- # has_many :posts, as: :postable
101
- # end
102
- # class Post
103
- # belongs_to :postable
104
- # end
105
- # blog, feed, post_1, post_2, post_3 = Blog.new, Feed.new, Post.new, Post.new, Post.new
106
- # blog.add_post(post_1)
107
- # post_1.postable == blog # => true
108
- # feed.add_post(post_2)
109
- # post_2.postable == feed # => true
110
- # post_3.postable = blog # => NoMethodError
111
- #
112
- # @example Polymorphic associated (idem for +has_one+)
113
- # class Blog
114
- # include AIXM::Association
115
- # has_many :items, accept: ['Post', :picture]
116
- # end
117
- # class Post
118
- # include AIXM::Association
119
- # belongs_to :blog, as: :item
120
- # end
121
- # class Picture
122
- # include AIXM::Association
123
- # belongs_to :blog, as: :item
124
- # end
125
- # blog, post, picture = Blog.new, Post.new, Picture.new
126
- # blog.add_item(post)
127
- # blog.add_item(picture)
128
- # blog.items.count # => 2
129
- # blog.items.first == post # => true
130
- # blog.items.last == picture # => true
131
- # post.blog == blog # => true
132
- # picture.blog == blog # => true
133
- #
134
- # @example Add method which enriches passed associated object (+has_many+ only)
135
- # class Blog
136
- # has_many :posts do |post, related_to: nil| # this defines the signature of add_post
137
- # post.related_to = related_to || @posts.last # executes in the context of the current blog
138
- # end
139
- # end
140
- # class Post
141
- # belongs_to :blog
142
- # attr_accessor :related_to
143
- # end
144
- # blog, post_1, post_2, post_3 = Blog.new, Post.new, Post.new, Post.new
145
- # blog.add_post(post_1)
146
- # post_1.related_to # => nil
147
- # blog.add_post(post_2)
148
- # post_2.related_to == post_1 # => true
149
- # blog.add_post(post_3, related_to: post_1)
150
- # post_3.related_to == post_1 # => true
151
- #
152
- # @example Add method which builds and yields new associated object (+has_many+ only)
153
- # class Blog
154
- # include AIXM::Association
155
- # has_many :posts do |post, title:| end
156
- # end
157
- # class Post
158
- # include AIXM::Association
159
- # belongs_to :blog
160
- # attr_accessor :title, :text
161
- # def initialize(title:) # same signature as "has_many" block above
162
- # @title = title
163
- # end
164
- # end
165
- # blog = Blog.new
166
- # blog.add_post(title: "title") do |post| # note that no post instance is passed
167
- # post.text = "text"
168
- # end
169
- # blog.posts.first.title # => "title"
170
- # blog.posts.first.text # => "text"
171
- module Association
172
- module ClassMethods
173
- attr_reader :has_many_attributes, :has_one_attributes, :belongs_to_attributes
174
-
175
- def has_many(attribute, as: nil, accept: nil, &association_block)
176
- association = attribute.to_s.inflect(:singularize)
177
- inversion = as || self.to_s.inflect(:demodulize, :tableize, :singularize)
178
- class_names = [accept || association].flatten.map { AIXM::CLASSES[_1.to_sym] || _1 }
179
- (@has_many_attributes ||= []) << attribute
180
- # features
181
- define_method(attribute) do
182
- instance_variable_get(:"@#{attribute}") || AIXM::Association::Array.new
183
- end
184
- # add_feature
185
- define_method(:"add_#{association}") do |object=nil, **options, &add_block|
186
- unless object
187
- fail(ArgumentError, "must pass object to add") if class_names.count > 1
188
- object = class_names.first.to_class.new(**options)
189
- add_block.call(object) if add_block
190
- end
191
- instance_exec(object, **options, &association_block) if association_block
192
- fail(ArgumentError, "#{object.__class__} not allowed") unless class_names.any? { |c| object.is_a?(c.to_class) }
193
- instance_eval("@#{attribute} ||= AIXM::Association::Array.new")
194
- send(attribute).send(:push, object)
195
- object.instance_variable_set(:"@#{inversion}", self)
196
- self
197
- end
198
- # add_features
199
- define_method(:"add_#{attribute}") do |objects=[], **options, &add_block|
200
- objects.each { send(:"add_#{association}", _1, **options, &add_block) }
201
- self
202
- end
203
- # remove_feature
204
- define_method(:"remove_#{association}") do |object|
205
- send(attribute).send(:delete, object)
206
- object.instance_variable_set(:"@#{inversion}", nil)
207
- self
208
- end
209
- # remove_features
210
- define_method(:"remove_#{attribute}") do |objects=[]|
211
- objects.each { send(:"remove_#{association}", _1) }
212
- self
213
- end
214
- end
215
-
216
- def has_one(attribute, as: nil, accept: nil, allow_nil: false)
217
- association = attribute.to_s
218
- inversion = (as || self.to_s.inflect(:demodulize, :tableize, :singularize)).to_s
219
- class_names = [accept || association].flatten.map { AIXM::CLASSES[_1.to_sym] || _1 }
220
- class_names << 'NilClass' if allow_nil
221
- (@has_one_attributes ||= []) << attribute
222
- # feature
223
- attr_reader attribute
224
- # feature=
225
- define_method(:"#{association}=") do |object|
226
- fail(ArgumentError, "#{object.__class__} not allowed") unless class_names.any? { |c| object.is_a?(c.to_class) }
227
- instance_variable_get(:"@#{attribute}")&.instance_variable_set(:"@#{inversion}", nil)
228
- instance_variable_set(:"@#{attribute}", object)
229
- object&.instance_variable_set(:"@#{inversion}", self)
230
- object
231
- end
232
- # add_feature
233
- define_method(:"add_#{association}") do |object|
234
- send("#{association}=", object)
235
- self
236
- end
237
- # remove_feature
238
- define_method(:"remove_#{association}") do |_|
239
- send(:"#{association}=", nil)
240
- self
241
- end
242
- end
243
-
244
- def belongs_to(attribute, as: nil, readonly: false)
245
- association = self.to_s.inflect(:demodulize, :tableize, :singularize)
246
- inversion = (as || association).to_s
247
- (@belongs_to_attributes ||= []) << attribute
248
- # feature
249
- attr_reader attribute
250
- unless readonly
251
- # feature=
252
- define_method(:"#{attribute}=") do |object|
253
- instance_variable_get(:"@#{attribute}")&.send(:"remove_#{inversion}", self)
254
- object&.send(:"add_#{inversion}", self)
255
- object
256
- end
257
- # add_feature
258
- define_method(:"add_#{attribute}") do |object|
259
- send("#{attribute}=", object)
260
- self
261
- end
262
- end
263
- end
264
- end
265
-
266
- def self.included(base)
267
- base.extend(ClassMethods)
268
- end
269
-
270
- class Array < ::Array
271
- private :<<, :push, :append, :unshift, :prepend
272
- private :delete, :pop, :shift
273
-
274
- # Find objects of the given class and optionally with the given
275
- # attribute values on a has_many association.
276
- #
277
- # The class can either be declared by passing the class itself or by
278
- # passing a shortcut symbol as listed in +AIXM::CLASSES+.
279
- #
280
- # @example
281
- # class Blog
282
- # include AIXM::Association
283
- # has_many :items, accept: %i(post picture)
284
- # end
285
- # class Post
286
- # include AIXM::Association
287
- # belongs_to :blog, as: :item
288
- # attr_accessor :title
289
- # end
290
- # class Picture
291
- # include AIXM::Association
292
- # belongs_to :blog, as: :item
293
- # end
294
- # blog, post, picture = Blog.new, Post.new, Picture.new
295
- # post.title = "title"
296
- # blog.add_item(post)
297
- # blog.add_item(picture)
298
- # blog.items.find_by(:post) == [post] # => true
299
- # blog.items.find_by(Post) == [post] # => true
300
- # blog.items.find_by(:post, title: "title") == [post] # => true
301
- # blog.items.find_by(Object) == [post, picture] # => true
302
- #
303
- # @param klass [Class, Symbol] class (e.g. AIXM::Feature::Airport,
304
- # AIXM::Feature::NavigationalAid::VOR) or shortcut symbol (e.g.
305
- # :airport or :vor) as listed in AIXM::CLASSES
306
- # @param attributes [Hash] search attributes by their values
307
- # @return [AIXM::Association::Array]
308
- def find_by(klass, attributes={})
309
- if klass.is_a? Symbol
310
- klass = AIXM::CLASSES[klass]&.to_class || fail(ArgumentError, "unknown class shortcut `#{klass}'")
311
- end
312
- self.class.new(
313
- select do |element|
314
- if element.kind_of? klass
315
- attributes.all? { |a, v| element.send(a) == v }
316
- end
317
- end
318
- )
319
- end
320
-
321
- # Find equal objects on a has_many association.
322
- #
323
- # This may seem redundant at first, but keep in mind that two instances
324
- # of +AIXM::CLASSES+ which implement `#to_uid` are considered equal if
325
- # they are instances of the same class and both their UIDs as calculated
326
- # by `#to_uid` are equal. Attributes which are not part of the `#to_uid`
327
- # calculation are irrelevant!
328
- #
329
- # @example
330
- # class Blog
331
- # include AIXM::Association
332
- # has_many :items, accept: %i(post picture)
333
- # end
334
- # class Post
335
- # include AIXM::Association
336
- # belongs_to :blog, as: :item
337
- # attr_accessor :title
338
- # end
339
- # blog, post = Blog.new, Post.new
340
- # blog.add_item(post)
341
- # blog.items.find(post) == [post] # => true
342
- #
343
- # @param object [Object] instance of class listed in AIXM::CLASSES
344
- # @return [AIXM::Association::Array]
345
- def find(object)
346
- klass = object.__class__
347
- self.class.new(
348
- select do |element|
349
- element.kind_of?(klass) && element == object
350
- end
351
- )
352
- end
353
-
354
- # Find equal or identical duplicates on a has_many association.
355
- #
356
- # @example
357
- # class Blog
358
- # include AIXM::Association
359
- # has_many :posts
360
- # end
361
- # class Post
362
- # include AIXM::Association
363
- # belongs_to :blog
364
- # end
365
- # blog, post = Blog.new, Post.new
366
- # duplicate_post = post.dup
367
- # blog.add_posts([post, duplicate_post])
368
- # blog.posts.duplicates # => [[post, duplicate_post]]
369
- #
370
- # @return [Array<Array<AIXM::Feature>>]
371
- def duplicates
372
- AIXM::Memoize.method :to_uid do
373
- group_by(&:to_uid).select { |_, a| a.count > 1 }.map(&:last)
374
- end
375
- end
376
- end
377
- end
378
- end