lutaml-model 0.3.4 → 0.3.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d80c5eb43638cf5d690c6c59db2fba76ddb8e2fc7252117a3c7933bc566b2607
4
- data.tar.gz: d75c694c330c017500dcceb604aaa889633dadb53c7766a4b6310ee3aef1725e
3
+ metadata.gz: 45fc67305626a2fe4d38db31804a618fd887a9855ff8ddb7ef2d0d0e8cc8b9b8
4
+ data.tar.gz: 7ffbc1f1b1958d05ebbf82889b1b54f032df48a4ed9052f3a963b896bfb43238
5
5
  SHA512:
6
- metadata.gz: fc9ed09d01c0c19b4038af7de38c3c458f7c200f7b5ae509f4e4cfc2d3abe116ad862168b7d2ffef16e0d5205c67b3a54226b8b826f9859a72ec5d32da00545e
7
- data.tar.gz: b4b8348522112cce9236dc7989f30b42df22ac04d6a7459b2fff6ad6c6dc7018d4bd88e97ec9f83ab8b2dcf6d92938d18fcdc882914240d3d53fb16e0ba116d3
6
+ metadata.gz: 37a4e64c1b61b6a1112329b18ea4bd9b544fe125859cd81a1054b162817e1ffb22e03f4168836ce8890e6f6cba6f137c78110522322c0a0fe6fde3e121c533d4
7
+ data.tar.gz: bd3bfb6600103c25034c3604fcdc83947d8eb3a14b23ec54e93186f66d273a5270a923394bd5f00e5caf3d58c9ff7c65efce9cd5f7a874ac22f4582711622d27
data/.rubocop_todo.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2024-08-16 11:07:41 UTC using RuboCop version 1.65.1.
3
+ # on 2024-08-19 03:44:26 UTC using RuboCop version 1.65.1.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
@@ -14,7 +14,7 @@ Gemspec/RequireMFA:
14
14
  Exclude:
15
15
  - 'lutaml-model.gemspec'
16
16
 
17
- # Offense count: 76
17
+ # Offense count: 78
18
18
  # This cop supports safe autocorrection (--autocorrect).
19
19
  # Configuration parameters: Max, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns.
20
20
  # URISchemes: http, https
@@ -28,6 +28,7 @@ Layout/LineLength:
28
28
  - 'lib/lutaml/model/xml_adapter/ox_adapter.rb'
29
29
  - 'lib/lutaml/model/xml_adapter/xml_document.rb'
30
30
  - 'spec/lutaml/model/comparable_model_spec.rb'
31
+ - 'spec/lutaml/model/custom_serialization_spec.rb'
31
32
  - 'spec/lutaml/model/delegation_spec.rb'
32
33
  - 'spec/lutaml/model/schema/json_schema_spec.rb'
33
34
  - 'spec/lutaml/model/serializable_spec.rb'
@@ -55,11 +56,11 @@ Metrics/AbcSize:
55
56
  - 'lib/lutaml/model/xml_adapter/ox_adapter.rb'
56
57
  - 'lib/lutaml/model/xml_adapter/xml_document.rb'
57
58
 
58
- # Offense count: 3
59
+ # Offense count: 4
59
60
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode.
60
61
  # AllowedMethods: refine
61
62
  Metrics/BlockLength:
62
- Max: 30
63
+ Max: 31
63
64
 
64
65
  # Offense count: 23
65
66
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
@@ -75,7 +76,7 @@ Metrics/CyclomaticComplexity:
75
76
  # Offense count: 38
76
77
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
77
78
  Metrics/MethodLength:
78
- Max: 41
79
+ Max: 52
79
80
 
80
81
  # Offense count: 4
81
82
  # Configuration parameters: CountKeywordArgs, MaxOptionalParameters.
@@ -102,7 +103,7 @@ RSpec/ContextWording:
102
103
  - 'spec/lutaml/model/xml_adapter/oga_adapter_spec.rb'
103
104
  - 'spec/lutaml/model/xml_adapter/ox_adapter_spec.rb'
104
105
 
105
- # Offense count: 70
106
+ # Offense count: 73
106
107
  # Configuration parameters: CountAsOne.
107
108
  RSpec/ExampleLength:
108
109
  Max: 57
@@ -129,7 +130,7 @@ RSpec/MultipleDescribes:
129
130
  - 'spec/lutaml/model/xml_adapter/xml_namespace_spec.rb'
130
131
  - 'spec/lutaml/model/xml_adapter_spec.rb'
131
132
 
132
- # Offense count: 69
133
+ # Offense count: 70
133
134
  RSpec/MultipleExpectations:
134
135
  Max: 15
135
136
 
data/README.adoc CHANGED
@@ -13,16 +13,13 @@ objects to and from various formats such as JSON, XML, YAML, and TOML. It uses
13
13
  an adapter pattern to support multiple libraries for each format, providing
14
14
  flexibility and extensibility for your data modeling needs.
15
15
 
16
- The name "LutaML" comes from the Latin word for clay, "Lutum", and "ML"
17
- for Markup Language. Just as clay can be molded and modeled into beautiful and
18
- practical end products, the Lutaml::Model gem is used for data modeling,
19
- allowing you to shape and structure your data into useful forms.
20
-
16
+ NOTE: Lutaml::Model is designed to be mostly compatible with the data modeling
17
+ API of https://www.shalerb.org[Shale], an impressive Ruby data modeller.
18
+ Lutaml::Model is meant to address advanced needs not currently addressed by
19
+ Shale.
21
20
 
22
- NOTE: Lutaml::Model is designed to be compatible with the
23
- https://www.shalerb.org[Shale] data modeling API. Shale is an amazing Ruby data
24
- modeller. Lutaml::Model is meant to address needs that are not currently
25
- addressed by Shale.
21
+ NOTE: Instructions on how to migrate from Shale to Lutaml::Model are provided in
22
+ <<migrate-from-shale>>.
26
23
 
27
24
 
28
25
  == Data modeling in a nutshell
@@ -80,6 +77,7 @@ There are two ways to define a data model in Lutaml::Model:
80
77
  * Inheriting from the `Lutaml::Model::Serializable` class
81
78
  * Including the `Lutaml::Model::Serialize` module
82
79
 
80
+ [[define-through-inheritance]]
83
81
  ==== Definition through inheritance
84
82
 
85
83
  The simplest way to define a model is to create a class that inherits from
@@ -98,6 +96,7 @@ class Kiln < Lutaml::Model::Serializable
98
96
  end
99
97
  ----
100
98
 
99
+ [[define-through-inclusion]]
101
100
  ==== Definition through inclusion
102
101
 
103
102
  If the model class already has a super class that it inherits from, the model
@@ -169,12 +168,6 @@ attribute :name_of_attribute, {symbol | string | class}
169
168
  | `Boolean` | `:boolean` | `Lutaml::Model::Type::Boolean` | `Boolean`
170
169
  | `Decimal` | `:decimal` | `Lutaml::Model::Type::Decimal` | `::BigDecimal`
171
170
  | `Hash` | `:hash` | `Lutaml::Model::Type::Hash` | `::Hash`
172
- | `Uuid` | `:uuid` | `Lutaml::Model::Type::Uuid` | `::String`
173
- | `Symbol` | `:symbol` | `Lutaml::Model::Type::Symbol` | `Symbol`
174
- | `Binary` | `:binary` | `Lutaml::Model::Type::Binary` | `Binary`
175
- | `Url` | `:url` | `Lutaml::Model::Type::Url` | `::URI`
176
- | `IpAddress` | `:ip_address` | `Lutaml::Model::Type::IpAddress` | `::IPAddr`
177
- | `Json` | `:json` | `Lutaml::Model::Type::Json` | `::JSON`
178
171
 
179
172
  |===
180
173
 
@@ -238,6 +231,7 @@ end
238
231
  ----
239
232
  ====
240
233
 
234
+ [[attribute-enumeration]]
241
235
  === Attribute as an enumeration
242
236
 
243
237
  An attribute can be defined as an enumeration by using the `values` directive.
@@ -777,6 +771,7 @@ end
777
771
  ----
778
772
  ====
779
773
 
774
+ [[namespace-inherit]]
780
775
  ===== Namespace with `inherit` option
781
776
 
782
777
  The `inherit` option is used at the element level to inherit the namespace from
@@ -838,7 +833,7 @@ end
838
833
  ----
839
834
  ====
840
835
 
841
-
836
+ [[mixed-content]]
842
837
  ==== Mixed content
843
838
 
844
839
  ===== General
@@ -859,6 +854,8 @@ To map this to Lutaml::Model we can use the `mixed` option in either way:
859
854
  * when defining the model;
860
855
  * when referencing the model.
861
856
 
857
+ NOTE: This feature is not supported by Shale.
858
+
862
859
 
863
860
  ===== Specifying the `mixed` option at `root`
864
861
 
@@ -1071,7 +1068,7 @@ end
1071
1068
  ----
1072
1069
  ====
1073
1070
 
1074
-
1071
+ [[separate-serialization-model]]
1075
1072
  === Separate serialization model
1076
1073
 
1077
1074
  The `Serialize` module can be used to define only serialization mappings for a
@@ -1283,6 +1280,9 @@ end
1283
1280
  ----
1284
1281
  ====
1285
1282
 
1283
+ NOTE: The corresponding keyword used by Shale is `receiver:` instead of
1284
+ `delegate:`.
1285
+
1286
1286
 
1287
1287
  ==== Attribute serialization with custom methods
1288
1288
 
@@ -1291,10 +1291,30 @@ each serialization mapping block for `from` and `to`.
1291
1291
 
1292
1292
  Syntax:
1293
1293
 
1294
+ .XML serialization with custom methods
1294
1295
  [source,ruby]
1295
1296
  ----
1296
- xml | json | yaml | toml do
1297
- map 'key_value_model_attribute_name', to: :name_of_attribute, with: {
1297
+ xml do
1298
+ map_element 'element_name', to: :name_of_element, with: {
1299
+ to: :method_name_to_serialize,
1300
+ from: :method_name_to_deserialize
1301
+ }
1302
+ map_attribute 'attribute_name', to: :name_of_attribute, with: {
1303
+ to: :method_name_to_serialize,
1304
+ from: :method_name_to_deserialize
1305
+ }
1306
+ map_content, to: :name_of_content, with: {
1307
+ to: :method_name_to_serialize,
1308
+ from: :method_name_to_deserialize
1309
+ }
1310
+ end
1311
+ ----
1312
+
1313
+ .Key-value data model serialization with custom methods
1314
+ [source,ruby]
1315
+ ----
1316
+ json | yaml | toml do
1317
+ map 'attribute_name', to: :name_of_attribute, with: {
1298
1318
  to: :method_name_to_serialize,
1299
1319
  from: :method_name_to_deserialize
1300
1320
  }
@@ -1345,7 +1365,7 @@ end
1345
1365
  ====
1346
1366
 
1347
1367
 
1348
-
1368
+ [[attribute-extraction]]
1349
1369
  ==== Attribute extraction
1350
1370
 
1351
1371
  NOTE: This feature is for key-value data model serialization only.
@@ -1541,7 +1561,7 @@ serialization format.
1541
1561
  You will need to specify the configuration for the adapter you want to use. The
1542
1562
  easiest way is to copy and paste the following configuration into your code.
1543
1563
 
1544
- The default configuration is as follows:
1564
+ The configuration is as follows:
1545
1565
 
1546
1566
  [source,ruby]
1547
1567
  ----
@@ -1559,6 +1579,21 @@ Lutaml::Model::Config.configure do |config|
1559
1579
  end
1560
1580
  ----
1561
1581
 
1582
+ You can also provide the adapter type by using symbols like
1583
+
1584
+ [source,ruby]
1585
+ ----
1586
+ require 'lutaml/model'
1587
+
1588
+ Lutaml::Model::Config.configure do |config|
1589
+ config.xml_adapter_type = :nokogiri # can be one of [:nokogiri, :ox, :oga]
1590
+ config.yaml_adapter_type = :standard_yaml
1591
+ config.json_adapter_type = :standard_json # can be one of [:standard_json, :multi_json]
1592
+ config.toml_adapter_type = :toml_rb # can be one of [:toml_rb, :tomlib]
1593
+ end
1594
+ ----
1595
+
1596
+ NOTE: By default `yaml_adapter_type` and `json_adapter_type` are set to `:standard_yaml` and `:standard_json` respectively.
1562
1597
 
1563
1598
  === XML
1564
1599
 
@@ -1679,6 +1714,396 @@ Lutaml::Model::Config.configure do |config|
1679
1714
  end
1680
1715
  ----
1681
1716
 
1717
+
1718
+ == Comparison with Shale
1719
+
1720
+ Lutaml::Model is a serialization library that is similar to Shale, but with some
1721
+ differences in implementation.
1722
+
1723
+ [cols="a,a,a,a",options="header"]
1724
+ |===
1725
+ | Feature | Lutaml::Model | Shale | Notes
1726
+
1727
+ | Data model definition
1728
+ |
1729
+ 3 types:
1730
+
1731
+ * <<define-through-inheritance,Inherit from `Lutaml::Model::Serializable`>>
1732
+ * <<define-through-inclusion,Include `Lutaml::Model::Serialize`>>
1733
+ * <<separate-serialization-model,Separate serialization model class>>
1734
+ |
1735
+ 2 types:
1736
+
1737
+ * Inherit from `Shale::Mapper`
1738
+ * Custom model class
1739
+ |
1740
+
1741
+ | Value types
1742
+ | `Lutaml::Model::Type` includes: `Integer`, `String`, `Float`, `Boolean`, `Date`, `DateTime`, `Time`, `Hash`.
1743
+ | `Shale::Type` includes: `Integer`, `String`, `Float`, `Boolean`, `Date`, `Time`.
1744
+ | Lutaml::Model supports the additional value types `DateTime` and `Hash`.
1745
+
1746
+ | Configuration
1747
+ | `Lutaml::Model::Config`
1748
+ | `Shale.{type}_adapter`
1749
+ | Lutaml::Model uses a configuration block to set the serialization adapters.
1750
+
1751
+ | Custom serialization methods
1752
+ | `:with`, on individual attributes
1753
+ | `:using`, on entire object/document
1754
+ | Lutaml::Model uses the `:with` keyword for custom serialization methods.
1755
+
1756
+ | Serialization formats
1757
+ | XML, YAML, JSON, TOML
1758
+ | XML, YAML, JSON, TOML, CSV
1759
+ | Lutaml::Model does not support CSV.
1760
+
1761
+ | Adapter support
1762
+ | XML (Nokogiri, Ox, Oga), YAML, JSON (JSON, MultiJson), TOML (Toml-rb, Tomlib)
1763
+ | XML (Nokogiri, Ox), YAML, JSON (JSON, MultiJson), TOML (Toml-rb, Tomlib), CSV
1764
+ | Lutaml::Model does not support CSV.
1765
+
1766
+ 4+h| XML features
1767
+
1768
+ | XML mixed content support
1769
+ | Yes. Supports the following kind of XML through <<mixed-content,mixed content>> support.
1770
+
1771
+ [source,xml]
1772
+ ----
1773
+ <description>My name is
1774
+ <bold>John Doe</bold>,
1775
+ and I'm <i>28</i>
1776
+ years old</description>
1777
+ ----
1778
+ | No. Shale's `map_content` only supports the first text node.
1779
+ |
1780
+
1781
+ | XML namespace inheritance
1782
+ | Yes. Supports the <<namespace-inherit,`inherit`>> option to inherit the
1783
+ namespace from the root element.
1784
+ | No.
1785
+ |
1786
+
1787
+ 4+h| Attribute features
1788
+
1789
+ | Attribute delegation
1790
+ | `:delegate` option to delegate attribute mappings to a model.
1791
+ | `:receiver` option to delegate attribute mappings to a model.
1792
+ |
1793
+
1794
+ | Enumerations
1795
+ | Yes. Supports enumerations as value types through the
1796
+ <<attribute-enumeration,`values:` option>>.
1797
+ | No.
1798
+ | Lutaml::Model supports enumerations as value types.
1799
+
1800
+ | Attribute extraction
1801
+ | Yes. Supports <<attribute-extraction,attribute extraction>> from key-value
1802
+ data models.
1803
+ | No.
1804
+ | Lutaml::Model supports attribute extraction from key-value data models.
1805
+
1806
+
1807
+ |===
1808
+
1809
+
1810
+ [[migrate-from-shale]]
1811
+ == Migration steps from Shale
1812
+
1813
+ The following sections provide a guide for migrating from Shale to Lutaml::Model.
1814
+
1815
+ === Step 1: Replace inheritance class
1816
+
1817
+ `Lutaml::Model` uses `Lutaml::Model::Serializable` as the base inheritance class.
1818
+
1819
+ [source,ruby]
1820
+ ----
1821
+ class Example < Lutaml::Model::Serializable
1822
+ # ...
1823
+ end
1824
+ ----
1825
+
1826
+ [NOTE]
1827
+ ====
1828
+ `Lutaml::Model` also supports an inclusion method as in the following example,
1829
+ which is not supported by Shale. This is useful for cases where you want to
1830
+ include the serialization methods in a class that already inherits from another
1831
+ class.
1832
+
1833
+ [source,ruby]
1834
+ ----
1835
+ class Example
1836
+ include Lutaml::Model::Serialize
1837
+ # ...
1838
+ end
1839
+ ----
1840
+ ====
1841
+
1842
+ Shale uses `Shale::Mapper` as the base inheritance class.
1843
+
1844
+ [source,ruby]
1845
+ ----
1846
+ class Example < Shale::Mapper
1847
+ # ...
1848
+ end
1849
+ ----
1850
+
1851
+ Actions:
1852
+
1853
+ * Replace mentions of `Shale::Mapper` with `Lutaml::Model::Serializable`.
1854
+ * Potentially replace inheritance with inclusion for suitable cases.
1855
+
1856
+
1857
+ === Step 2: Replace value type definitions
1858
+
1859
+ Value types in `Lutaml::Model` are under the `Lutaml::Model::Type` module.
1860
+
1861
+ [source,ruby]
1862
+ ----
1863
+ class Example < Lutaml::Model::Serializable
1864
+ attribute :length, Lutaml::Model::Type::Integer
1865
+ attribute :description, Lutaml::Model::Type::String
1866
+ end
1867
+ ----
1868
+
1869
+ [NOTE]
1870
+ ====
1871
+ `Lutaml::Model` also supports specifying predefined value types as strings or
1872
+ symbols, which is not supported by Shale.
1873
+
1874
+ [source,ruby]
1875
+ ----
1876
+ class Example < Lutaml::Model::Serializable
1877
+ attribute :length, :integer
1878
+ attribute :description, "String"
1879
+ end
1880
+ ----
1881
+ ====
1882
+
1883
+ Value types in Shale are under the `Shale::Type` module.
1884
+
1885
+ [source,ruby]
1886
+ ----
1887
+ class Example < Shale::Mapper
1888
+ attribute :length, Shale::Type::Integer
1889
+ attribute :description, Shale::Type::String
1890
+ end
1891
+ ----
1892
+
1893
+ Action:
1894
+
1895
+ * Replace mentions of `Shale::Type` with `Lutaml::Model::Type`.
1896
+ * Potentially replace value type definitions with strings or symbols.
1897
+
1898
+
1899
+ === Step 3: Configure serialization adapters
1900
+
1901
+ `Lutaml::Model` uses a configuration block to set the serialization adapters.
1902
+
1903
+ [source,ruby]
1904
+ ----
1905
+ require 'lutaml/model/xml_adapter/nokogiri_adapter'
1906
+ Lutaml::Model::Config.configure do |config|
1907
+ config.xml_adapter = Lutaml::Model::XmlAdapter::NokogiriAdapter
1908
+ end
1909
+ ----
1910
+
1911
+ The equivalent for Shale is this:
1912
+
1913
+ [source,ruby]
1914
+ ----
1915
+ require 'shale/adapter/nokogiri'
1916
+ Shale.xml_adapter = Shale::Adapter::Nokogiri
1917
+ ----
1918
+
1919
+
1920
+ Here are places that this code may reside at:
1921
+
1922
+ * If your code is a standalone Ruby script, this code will be present in your code.
1923
+ * If your code is organized in a Ruby gem, this code will be specified somewhere referenced by `lib/your_gem_name.rb`.
1924
+ * If your code contains tests or specs, they will be in the test setup file, e.g. RSpec `spec/spec_helper.rb`.
1925
+
1926
+ Actions:
1927
+
1928
+ * Replace the Shale configuration block with the `Lutaml::Model::Config`
1929
+ configuration block.
1930
+
1931
+ * Replace the Shale adapter with the `Lutaml::Model` adapter.
1932
+
1933
+
1934
+
1935
+ === Step 4: Rewrite custom serialization methods
1936
+
1937
+ There is an implementation difference between Lutaml::Model and Shale for custom
1938
+ serialization methods.
1939
+
1940
+ Custom serialization methods in `Lutaml::Model` map to individual attributes.
1941
+
1942
+ For custom serialization methods, Lutaml::Model uses the `:with` keyword
1943
+ instead of the `:using` keyword used by Shale.
1944
+
1945
+ [source,ruby]
1946
+ ----
1947
+ class Example < Lutaml::Model::Serializable
1948
+ attribute :name, :string
1949
+ attribute :size, :integer
1950
+ attribute :color, :string
1951
+ attribute :description, :string
1952
+
1953
+ json do
1954
+ map "name", to: :name, with: { to: :name_to_json, from: :name_from_json }
1955
+ map "size", to: :size
1956
+ map "color", to: :color,
1957
+ with: { to: :color_to_json, from: :color_from_json }
1958
+ map "description", to: :description,
1959
+ with: { to: :description_to_json, from: :description_from_json }
1960
+ end
1961
+
1962
+ xml do
1963
+ root "CustomSerialization"
1964
+ map_element "Name", to: :name,
1965
+ with: { to: :name_to_xml, from: :name_from_xml }
1966
+ map_attribute "Size", to: :size
1967
+ map_element "Color", to: :color,
1968
+ with: { to: :color_to_xml, from: :color_from_xml }
1969
+ map_content to: :description,
1970
+ with: { to: :description_to_xml,
1971
+ from: :description_from_xml }
1972
+ end
1973
+
1974
+ def name_to_json(_model, value)
1975
+ "JSON Masterpiece: #{value}"
1976
+ end
1977
+
1978
+ def name_from_json(_model, doc)
1979
+ doc["name"].sub(/^JSON Masterpiece: /, "")
1980
+ end
1981
+
1982
+ def color_to_json(_model, value)
1983
+ value.upcase
1984
+ end
1985
+
1986
+ def color_from_json(_model, doc)
1987
+ doc["color"].downcase
1988
+ end
1989
+
1990
+ def description_to_json(_model, value)
1991
+ "JSON Description: #{value}"
1992
+ end
1993
+
1994
+ def description_from_json(_model, doc)
1995
+ doc["description"].sub(/^JSON Description: /, "")
1996
+ end
1997
+
1998
+ def name_to_xml(_model, value)
1999
+ "XML Masterpiece: #{value}"
2000
+ end
2001
+
2002
+ def name_from_xml(_model, value)
2003
+ value.sub(/^XML Masterpiece: /, "")
2004
+ end
2005
+
2006
+ def color_to_xml(_model, value)
2007
+ value.upcase
2008
+ end
2009
+
2010
+ def color_from_xml(_model, value)
2011
+ value.downcase
2012
+ end
2013
+
2014
+ def description_to_xml(_model, value)
2015
+ "XML Description: #{value}"
2016
+ end
2017
+
2018
+ def description_from_xml(_model, value)
2019
+ value.sub(/^XML Description: /, "")
2020
+ end
2021
+ end
2022
+ ----
2023
+
2024
+ Custom serialization methods in Shale do not map to specific attributes, but
2025
+ allow the user to specify where the data goes.
2026
+
2027
+ [source,ruby]
2028
+ ----
2029
+ class Example < Shale::Mapper
2030
+ attribute :name, Shale::Type::String
2031
+ attribute :size, Shale::Type::Integer
2032
+ attribute :color, Shale::Type::String
2033
+ attribute :description, Shale::Type::String
2034
+
2035
+ json do
2036
+ map "name", using: { from: :name_from_json, to: :name_to_json }
2037
+ map "size", to: :size
2038
+ map "color", using: { from: :color_from_json, to: :color_to_json }
2039
+ map "description", to: :description, using: { from: :description_from_json, to: :description_to_json }
2040
+ end
2041
+
2042
+ xml do
2043
+ root "CustomSerialization"
2044
+ map_element "Name", using: { from: :name_from_xml, to: :name_to_xml }
2045
+ map_attribute "Size", to: :size
2046
+ map_element "Color", using: { from: :color_from_xml, to: :color_to_xml }
2047
+ map_content to: :description, using: { from: :description_from_xml, to: :description_to_xml }
2048
+ end
2049
+
2050
+ def name_to_json(model, doc)
2051
+ doc['name'] = "JSON Masterpiece: #{model.name}"
2052
+ end
2053
+
2054
+ def name_from_json(model, value)
2055
+ model.name = value.sub(/^JSON Masterpiece: /, "")
2056
+ end
2057
+
2058
+ def color_to_json(model, doc)
2059
+ doc['color'] = model.color.upcase
2060
+ end
2061
+
2062
+ def color_from_json(model, doc)
2063
+ model.color = doc['color'].downcase
2064
+ end
2065
+
2066
+ def description_to_json(model, doc)
2067
+ doc['description'] = "JSON Description: #{model.description}"
2068
+ end
2069
+
2070
+ def description_from_json(model, doc)
2071
+ model.description = doc['description'].sub(/^JSON Description: /, "")
2072
+ end
2073
+
2074
+ def name_from_xml(model, node)
2075
+ model.name = node.text.sub(/^XML Masterpiece: /, "")
2076
+ end
2077
+
2078
+ def name_to_xml(model, parent, doc)
2079
+ name_element = doc.create_element('Name')
2080
+ doc.add_text(name_element, model.street.to_s)
2081
+ doc.add_element(parent, name_element)
2082
+ end
2083
+ end
2084
+ ----
2085
+
2086
+ NOTE: There are cases where the Shale implementation of custom methods work
2087
+ differently from the Lutaml::Model implementation. In these cases, you will need
2088
+ to adjust the custom methods accordingly.
2089
+
2090
+ Actions:
2091
+
2092
+ * Replace the `using` keyword with the `with` keyword.
2093
+ * Adjust the custom methods.
2094
+
2095
+
2096
+ == About LutaML
2097
+
2098
+ The name "LutaML" is pronounced as "Looh-tah-mel".
2099
+
2100
+ The name "LutaML" comes from the Latin word for clay, "Lutum", and "ML"
2101
+ for "Markup Language". Just as clay can be molded and modeled into beautiful and
2102
+ practical end products, the Lutaml::Model gem is used for data modeling,
2103
+ allowing you to shape and structure your data into useful forms.
2104
+
2105
+
2106
+
1682
2107
  == License and Copyright
1683
2108
 
1684
2109
  This project is licensed under the BSD 2-clause License.
@@ -3,11 +3,92 @@ module Lutaml
3
3
  module Config
4
4
  extend self
5
5
 
6
- attr_accessor :xml_adapter, :json_adapter, :yaml_adapter, :toml_adapter
6
+ # Default values are set for these so the readers are defined below
7
+ attr_writer :json_adapter, :yaml_adapter
8
+
9
+ attr_accessor :xml_adapter, :toml_adapter
10
+
11
+ AVAILABLE_FORMATS = %i[xml json yaml toml].freeze
7
12
 
8
13
  def configure
9
14
  yield self
10
15
  end
16
+
17
+ # This will generate the following methods
18
+ #
19
+ # xml_adapter_type=
20
+ # @params:
21
+ # one of [:nokogiri, :ox, :oga]
22
+ # @example
23
+ # Lutaml::Model::Config.xml_adapter = :nokogiri
24
+ #
25
+ # json_adapter_type=
26
+ # @params:
27
+ # one of [:standard_json, :multi_json]
28
+ # if not set, :standard_json will be used by default
29
+ # @example
30
+ # Lutaml::Model::Config.json_adapter = :standard_json
31
+ #
32
+ # yaml_adapter_type=
33
+ # @params:
34
+ # one of [:standard_yaml]
35
+ # if not set, :standard_yaml will be used by default
36
+ # @example
37
+ # Lutaml::Model::Config.yaml_adapter = :standard_yaml
38
+ #
39
+ # toml_adapter_type=
40
+ # @params
41
+ # one of [:tomlib, :toml_rb]
42
+ # @example
43
+ # Lutaml::Model::Config.toml_adapter = :tomlib
44
+ AVAILABLE_FORMATS.each do |adapter_name|
45
+ define_method(:"#{adapter_name}_adapter_type=") do |type_name|
46
+ adapter = "#{adapter_name}_adapter"
47
+ type = "#{type_name}_adapter"
48
+
49
+ begin
50
+ adapter_file = File.join(adapter, type)
51
+ require_relative adapter_file
52
+ rescue LoadError
53
+ raise(
54
+ Lutaml::Model::UnknownAdapterTypeError.new(
55
+ adapter_name,
56
+ type_name,
57
+ ),
58
+ cause: nil,
59
+ )
60
+ end
61
+
62
+ instance_variable_set(
63
+ :"@#{adapter}",
64
+ Lutaml::Model.const_get(to_class_name(adapter))
65
+ .const_get(to_class_name(type)),
66
+ )
67
+ end
68
+ end
69
+
70
+ # Return JSON adapter. By default StandardJsonAdapter is used
71
+ #
72
+ # @example
73
+ # Lutaml::Model::Config.json_adapter
74
+ # # => Lutaml::Model::YamlAdapter::StandardJsonAdapter
75
+ def json_adapter
76
+ @json_adapter || Lutaml::Model::JsonAdapter::StandardJsonAdapter
77
+ end
78
+
79
+ # Return YAML adapter. By default StandardYamlAdapter is used
80
+ #
81
+ # @example
82
+ # Lutaml::Model::Config.yaml_adapter
83
+ # # => Lutaml::Model::YamlAdapter::StandardYamlAdapter
84
+ def yaml_adapter
85
+ @yaml_adapter || Lutaml::Model::YamlAdapter::StandardYamlAdapter
86
+ end
87
+
88
+ # @api private
89
+ def to_class_name(str)
90
+ str.to_s.split("_").map(&:capitalize).join
91
+ end
11
92
  end
12
93
  end
13
94
  end
@@ -0,0 +1,16 @@
1
+ module Lutaml
2
+ module Model
3
+ class UnknownAdapterTypeError < Error
4
+ def initialize(adapter_name, type_name)
5
+ @adapter_name = adapter_name
6
+ @type_name = type_name
7
+
8
+ super()
9
+ end
10
+
11
+ def to_s
12
+ "Unknown type: `#{@type_name}` for `#{@adapter_name}` adapter"
13
+ end
14
+ end
15
+ end
16
+ end
@@ -6,3 +6,4 @@ module Lutaml
6
6
  end
7
7
 
8
8
  require_relative "error/invalid_value_error"
9
+ require_relative "error/unknown_adapter_type_error"
@@ -13,8 +13,6 @@ require_relative "comparable_model"
13
13
  module Lutaml
14
14
  module Model
15
15
  module Serialize
16
- FORMATS = %i[xml json yaml toml].freeze
17
-
18
16
  include ComparableModel
19
17
 
20
18
  def self.included(base)
@@ -73,7 +71,7 @@ module Lutaml
73
71
  attr.options[:values].include?(value)
74
72
  end
75
73
 
76
- FORMATS.each do |format|
74
+ Lutaml::Model::Config::AVAILABLE_FORMATS.each do |format|
77
75
  define_method(format) do |&block|
78
76
  klass = format == :xml ? XmlMapping : KeyValueMapping
79
77
  mappings[format] = klass.new
@@ -210,9 +208,7 @@ module Lutaml
210
208
  else
211
209
  # TODO: This code is problematic because Type.cast does not know
212
210
  # about all the types.
213
- Lutaml::Model::Type.cast(
214
- v, attr_rule.type
215
- )
211
+ Lutaml::Model::Type.cast(v, attr_rule.type)
216
212
  end
217
213
  end
218
214
  elsif value.is_a?(Hash) && attr_rule.type != Lutaml::Model::Type::Hash
@@ -276,7 +272,7 @@ module Lutaml
276
272
  end.public_send(:[]=, path.last.to_s, child_obj.send(attr_name))
277
273
  end
278
274
  end
279
- # hash[mapping.name] ||= {}
275
+
280
276
  hash[map_key] = map_value
281
277
  end
282
278
 
@@ -337,6 +333,8 @@ module Lutaml
337
333
  mapping_hash.item_order = doc.item_order
338
334
  mapping_hash.ordered = mappings_for(:xml).mixed_content? || mixed_content
339
335
 
336
+ mapping_from = []
337
+
340
338
  mappings.each_with_object(mapping_hash) do |rule, hash|
341
339
  attr = attributes[rule.to]
342
340
  raise "Attribute '#{rule.to}' not found in #{self}" unless attr
@@ -372,8 +370,22 @@ module Lutaml
372
370
  value = attr.type.cast(value) unless is_content_mapping
373
371
  end
374
372
 
373
+ mapping_from << rule if rule.custom_methods[:from]
374
+
375
375
  hash[rule.to] = value
376
376
  end
377
+
378
+ mapping_from.each do |rule|
379
+ value = if rule.name.nil?
380
+ mapping_hash[rule.to].join("\n").strip
381
+ else
382
+ mapping_hash[rule.to]
383
+ end
384
+
385
+ mapping_hash[rule.to] = new.send(rule.custom_methods[:from], mapping_hash, value)
386
+ end
387
+
388
+ mapping_hash
377
389
  end
378
390
 
379
391
  def ensure_utf8(value)
@@ -385,7 +397,9 @@ module Lutaml
385
397
  when Hash
386
398
  value.transform_keys do |k|
387
399
  ensure_utf8(k)
388
- end.transform_values { |v| ensure_utf8(v) }
400
+ end.transform_values do |v|
401
+ ensure_utf8(v)
402
+ end
389
403
  else
390
404
  value
391
405
  end
@@ -416,14 +430,14 @@ module Lutaml
416
430
  end
417
431
 
418
432
  def key_exist?(hash, key)
419
- hash.key?(key) || hash.key?(key.to_sym) || hash.key?(key.to_s)
433
+ hash.key?(key.to_sym) || hash.key?(key.to_s)
420
434
  end
421
435
 
422
436
  def key_value(hash, key)
423
- hash[key] || hash[key.to_sym] || hash[key.to_s]
437
+ hash[key.to_sym] || hash[key.to_s]
424
438
  end
425
439
 
426
- FORMATS.each do |format|
440
+ Lutaml::Model::Config::AVAILABLE_FORMATS.each do |format|
427
441
  define_method(:"to_#{format}") do |options = {}|
428
442
  validate
429
443
  adapter = Lutaml::Model::Config.public_send(:"#{format}_adapter")
@@ -1,8 +1,5 @@
1
1
  require "date"
2
2
  require "bigdecimal"
3
- require "securerandom"
4
- require "uri"
5
- require "ipaddr"
6
3
 
7
4
  module Lutaml
8
5
  module Model
@@ -20,12 +17,6 @@ module Lutaml
20
17
  Boolean
21
18
  Decimal
22
19
  Hash
23
- Uuid
24
- Symbol
25
- Binary
26
- Url
27
- IpAddress
28
- Json
29
20
  ).each do |t|
30
21
  class_eval <<~HEREDOC, __FILE__, __LINE__ + 1
31
22
  class #{t} # class Integer
@@ -72,18 +63,6 @@ module Lutaml
72
63
  BigDecimal(value.to_s)
73
64
  when "Hash"
74
65
  normalize_hash(Hash(value))
75
- when "Uuid"
76
- UUID_REGEX.match?(value) ? value : SecureRandom.uuid
77
- when "Symbol"
78
- value.to_sym
79
- when "Binary"
80
- value.force_encoding("BINARY")
81
- when "Url"
82
- URI.parse(value.to_s)
83
- when "IpAddress"
84
- IPAddr.new(value.to_s)
85
- when "Json"
86
- Json.cast(value)
87
66
  else
88
67
  value
89
68
  end
@@ -107,8 +86,6 @@ module Lutaml
107
86
  value.to_s("F")
108
87
  when "Hash"
109
88
  Hash(value)
110
- when "Json"
111
- value.to_json
112
89
  else
113
90
  value.to_s
114
91
  end
@@ -145,4 +122,3 @@ end
145
122
 
146
123
  require_relative "type/time_without_date"
147
124
  require_relative "type/date_time"
148
- require_relative "type/json"
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.3.4"
5
+ VERSION = "0.3.6"
6
6
  end
7
7
  end
@@ -71,10 +71,14 @@ module Lutaml
71
71
  end
72
72
  end
73
73
 
74
- if xml_mapping.content_mapping
75
- text = element.send(xml_mapping.content_mapping.to)
74
+ if (content_rule = xml_mapping.content_mapping)
75
+ text = element.send(content_rule.to)
76
76
  text = text.join if text.is_a?(Array)
77
77
 
78
+ if content_rule.custom_methods[:to]
79
+ text = @root.send(content_rule.custom_methods[:to], @root, text)
80
+ end
81
+
78
82
  prefixed_xml.text text
79
83
  end
80
84
  end
@@ -130,6 +134,10 @@ module Lutaml
130
134
  end
131
135
 
132
136
  def add_to_xml(xml, value, attribute, rule)
137
+ if rule.custom_methods[:to]
138
+ value = @root.send(rule.custom_methods[:to], @root, value)
139
+ end
140
+
133
141
  if value && (attribute&.type&.<= Lutaml::Model::Serialize)
134
142
  handle_nested_elements(
135
143
  xml,
@@ -69,10 +69,14 @@ module Lutaml
69
69
  end
70
70
  end
71
71
 
72
- if xml_mapping.content_mapping
72
+ if (content_rule = xml_mapping.content_mapping)
73
73
  text = element.send(xml_mapping.content_mapping.to)
74
74
  text = text.join if text.is_a?(Array)
75
75
 
76
+ if content_rule.custom_methods[:to]
77
+ text = @root.send(content_rule.custom_methods[:to], @root, text)
78
+ end
79
+
76
80
  el.text text
77
81
  end
78
82
  end
@@ -113,6 +117,10 @@ module Lutaml
113
117
  end
114
118
 
115
119
  def add_to_xml(xml, value, attribute, rule)
120
+ if rule.custom_methods[:to]
121
+ value = @root.send(rule.custom_methods[:to], @root, value)
122
+ end
123
+
116
124
  if value && (attribute&.type&.<= Lutaml::Model::Serialize)
117
125
  handle_nested_elements(
118
126
  xml,
data/lib/lutaml/model.rb CHANGED
@@ -3,8 +3,8 @@
3
3
  require_relative "model/version"
4
4
  require_relative "model/type"
5
5
  require_relative "model/serializable"
6
- require_relative "model/json_adapter"
7
- require_relative "model/yaml_adapter"
6
+ require_relative "model/json_adapter/standard_json_adapter"
7
+ require_relative "model/yaml_adapter/standard_yaml_adapter"
8
8
  require_relative "model/xml_adapter"
9
9
  require_relative "model/toml_adapter"
10
10
  require_relative "model/error"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lutaml-model
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.4
4
+ version: 0.3.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-08-16 00:00:00.000000000 Z
11
+ date: 2024-08-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -56,6 +56,7 @@ files:
56
56
  - lib/lutaml/model/config.rb
57
57
  - lib/lutaml/model/error.rb
58
58
  - lib/lutaml/model/error/invalid_value_error.rb
59
+ - lib/lutaml/model/error/unknown_adapter_type_error.rb
59
60
  - lib/lutaml/model/json_adapter.rb
60
61
  - lib/lutaml/model/json_adapter/json_document.rb
61
62
  - lib/lutaml/model/json_adapter/json_object.rb
@@ -80,7 +81,6 @@ files:
80
81
  - lib/lutaml/model/toml_adapter/tomlib_adapter.rb
81
82
  - lib/lutaml/model/type.rb
82
83
  - lib/lutaml/model/type/date_time.rb
83
- - lib/lutaml/model/type/json.rb
84
84
  - lib/lutaml/model/type/time_without_date.rb
85
85
  - lib/lutaml/model/version.rb
86
86
  - lib/lutaml/model/xml_adapter.rb
@@ -1,34 +0,0 @@
1
- require "json"
2
-
3
- module Lutaml
4
- module Model
5
- module Type
6
- # JSON representation
7
- class Json
8
- attr_reader :value
9
-
10
- def initialize(value)
11
- @value = value
12
- end
13
-
14
- def to_json(*_args)
15
- @value.to_json
16
- end
17
-
18
- def ==(other)
19
- @value == (other.is_a?(::Hash) ? other : other.value)
20
- end
21
-
22
- def self.cast(value)
23
- return value if value.is_a?(self) || value.nil?
24
-
25
- new(::JSON.parse(value))
26
- end
27
-
28
- def self.serialize(value)
29
- value.to_json
30
- end
31
- end
32
- end
33
- end
34
- end