philosophal 1.2.0 → 2.0.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 +25 -1
- data/README.md +39 -0
- data/lib/philosophal/convertor.rb +9 -12
- data/lib/philosophal/loaders/json_loader.rb +4 -9
- data/lib/philosophal/properties/instance_methods.rb +76 -0
- data/lib/philosophal/properties/schema.rb +48 -0
- data/lib/philosophal/properties.rb +66 -88
- data/lib/philosophal/property.rb +162 -53
- data/lib/philosophal/transform.rb +8 -6
- data/lib/philosophal/types/array_of_type.rb +4 -3
- data/lib/philosophal/types/boolean_type.rb +1 -1
- data/lib/philosophal/types/hash_of_type.rb +8 -6
- data/lib/philosophal.rb +15 -17
- data/sig/manifest.yaml +5 -0
- data/sig/philosophal/convertor.rbs +35 -0
- data/sig/philosophal/errors/argument_error.rbs +4 -0
- data/sig/philosophal/errors/type_error.rbs +4 -0
- data/sig/philosophal/loaders/errors.rbs +6 -0
- data/sig/philosophal/loaders/json_loader.rbs +29 -0
- data/sig/philosophal/loaders.rbs +4 -0
- data/sig/philosophal/properties/instance_methods.rbs +26 -0
- data/sig/philosophal/properties/schema.rbs +47 -0
- data/sig/philosophal/properties.rbs +59 -0
- data/sig/philosophal/property.rbs +98 -0
- data/sig/philosophal/transform.rbs +7 -0
- data/sig/philosophal/types/any_type.rbs +9 -0
- data/sig/philosophal/types/array_of_type.rbs +23 -0
- data/sig/philosophal/types/boolean_type.rbs +11 -0
- data/sig/philosophal/types/hash_of_type.rbs +25 -0
- data/sig/philosophal/types.rbs +11 -0
- data/sig/philosophal.rbs +12 -0
- metadata +43 -14
- data/.rubocop.yml +0 -12
- data/.ruby-version +0 -1
- data/.tool-versions +0 -1
- data/Rakefile +0 -12
- data/VERSION +0 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d588fe0288b3b4d953de700a9218a607b4e7fc23d3946a8e34b4a1f90a7f1265
|
|
4
|
+
data.tar.gz: cf23c471421f8661992e575bfef9edb21e2a659962c0f54ec1618f0047b23b87
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ae95691e4be606dbca97f8c3a1e3ccf96b34a310b95a5f07567cc8e2c80f37491ae92e03ecc4d60cfc851c0147d8b63ea0b9bb4cc16f64b1c32e06b1fdb33ed4
|
|
7
|
+
data.tar.gz: 3db4c56bea72d05e0e7a2d75a65842d567e6f2c33f8a37ffcdf2a82374c404c6cc37ce3dec376e94f004b55d3dd6182de8a15518f962d7f70f7aa26effceeb39
|
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,28 @@
|
|
|
1
|
-
## [
|
|
1
|
+
## [2.0.0] - 2026-11-03
|
|
2
|
+
|
|
3
|
+
- Add: new `track_changes:` option for `cprop`, which defines `prop_was` (the value at the last `changes_applied`) and `prop_changed?` (the value differs from it)
|
|
4
|
+
- Add: `changes_applied` records the current values of the tracked properties as the reference
|
|
5
|
+
- Add: `changed_properties` returns a hash of the tracked properties changed since the last `changes_applied`, with their new value
|
|
6
|
+
- Add: RBS type signatures (`sig/`), shipped with the gem
|
|
7
|
+
- Fix: **ArrayOf** and **HashOf** no longer accept `nil` or `false` entries of the wrong type without converting them
|
|
8
|
+
- Fix: converting to **ArrayOf** or **HashOf** no longer modifies the given array or hash
|
|
9
|
+
- Perf: the reader of a property without default is now a plain `attr_reader`
|
|
10
|
+
- Perf: writers, and readers falling back to their default, no longer look up their property in the schema
|
|
11
|
+
- Breaking: redeclaring in a subclass, with a default and `override: false`, a property no longer applies the subclass default or type (the parent methods are kept)
|
|
12
|
+
- Perf: fewer lookups and allocations when converting, loading JSON, `to_json_hash`, `changes_applied` and `changed_properties`
|
|
13
|
+
- Fix: `Properties::Schema` now defines `each`, so its Enumerable methods (`map`, `select`, `find`...) work and yield the properties
|
|
14
|
+
|
|
15
|
+
## [1.3.0] - 2026-10-19
|
|
16
|
+
|
|
17
|
+
- Breaking: minimum required Ruby version is now **3.3** (was 3.1)
|
|
18
|
+
- Breaking: assigning `nil` no longer converts it (e.g. to `0` or `""`); the property is reset and its reader returns the default value
|
|
19
|
+
- Fix: converting a Numeric to **DateTime** no longer requires ActiveSupport (`Time.zone`)
|
|
20
|
+
- Fix: properties declared in a subclass no longer leak into the parent class
|
|
21
|
+
- Fix: a property set to `false` is no longer replaced by its default value, and predicate methods (`prop?`) take the default into account
|
|
22
|
+
- Fix: **ArrayOf** and **HashOf** instances are now really shared for the same subtypes, and **HashOf** accepts anonymous classes
|
|
23
|
+
- Fix: descriptions are no longer evaluated as Ruby code (parentheses or `#{...}` in a description broke or ran code)
|
|
24
|
+
- Fix: `json.load` no longer modifies the given hash or array, and accepts frozen hashes
|
|
25
|
+
- Fix: `override: true` now also replaces a method defined in the class itself before `cprop`
|
|
2
26
|
|
|
3
27
|
## [1.0.0] - 2025-03-24
|
|
4
28
|
|
data/README.md
CHANGED
|
@@ -28,3 +28,42 @@ maxime.age = '40'
|
|
|
28
28
|
puts maxime.inspect
|
|
29
29
|
#<Person:0x00007da7088eed88 @first_name="maxime", @last_name="DÉSÉCOT", @age=40>
|
|
30
30
|
````
|
|
31
|
+
|
|
32
|
+
### Tracking changes
|
|
33
|
+
|
|
34
|
+
With `track_changes: true`, a property also gets `<name>_was` and `<name>_changed?`.
|
|
35
|
+
Call `changes_applied` once the object is in a known state (built, loaded, saved...):
|
|
36
|
+
`<name>_was` returns the value at that moment, and `<name>_changed?` tells whether the value differs from it.
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
class Article
|
|
40
|
+
extend Philosophal::Properties
|
|
41
|
+
|
|
42
|
+
cprop :title, String, track_changes: true
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
article = Article.new
|
|
46
|
+
article.title = 'Draft'
|
|
47
|
+
article.title_changed? # => true (no reference yet)
|
|
48
|
+
article.changes_applied
|
|
49
|
+
article.title_changed? # => false
|
|
50
|
+
article.title = 'Review'
|
|
51
|
+
article.title = 'Final'
|
|
52
|
+
article.title_was # => "Draft"
|
|
53
|
+
article.title_changed? # => true
|
|
54
|
+
article.title = 'Draft'
|
|
55
|
+
article.title_changed? # => false
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`changed_properties` returns the tracked properties that changed, with their new value:
|
|
59
|
+
|
|
60
|
+
```ruby
|
|
61
|
+
article.title = 'Final'
|
|
62
|
+
article.changed_properties # => { title: "Final" }
|
|
63
|
+
article.changes_applied
|
|
64
|
+
article.changed_properties # => {}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Before the first `changes_applied`, the reference is "never assigned": every assigned property is changed.
|
|
68
|
+
`<name>_was` returns the stored value, without falling back to the default.
|
|
69
|
+
`json.load` does not call `changes_applied`: call it on the loaded objects if they must start unchanged.
|
|
@@ -87,7 +87,7 @@ module Philosophal
|
|
|
87
87
|
return obj.to_datetime if obj.respond_to?(:to_datetime)
|
|
88
88
|
return DateTime.new(*obj) if obj.is_a?(Array)
|
|
89
89
|
return DateTime.parse(obj) if obj.is_a?(String)
|
|
90
|
-
return Time.
|
|
90
|
+
return Time.at(obj).to_datetime if obj.is_a?(Numeric)
|
|
91
91
|
|
|
92
92
|
raise Philosophal::TypeError
|
|
93
93
|
end
|
|
@@ -102,9 +102,9 @@ module Philosophal
|
|
|
102
102
|
return false if obj.zero?
|
|
103
103
|
|
|
104
104
|
elsif obj.is_a?(String)
|
|
105
|
-
|
|
106
|
-
return true if
|
|
107
|
-
return false if
|
|
105
|
+
# casecmp compares ASCII only, which is enough for 'true' and 'false', and allocates no string.
|
|
106
|
+
return true if obj.casecmp('true') == 0 # rubocop:disable Style/NumericPredicate
|
|
107
|
+
return false if obj.casecmp('false') == 0 # rubocop:disable Style/NumericPredicate
|
|
108
108
|
|
|
109
109
|
end
|
|
110
110
|
raise Philosophal::TypeError
|
|
@@ -120,11 +120,8 @@ module Philosophal
|
|
|
120
120
|
subtype_convert_method = convert_method_for(subtype)
|
|
121
121
|
raise Philosophal::TypeError unless subtype_convert_method
|
|
122
122
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
send(subtype_convert_method, item)
|
|
126
|
-
end
|
|
127
|
-
end
|
|
123
|
+
# map, not map!: Array#to_a returns the given array itself, which must not be modified.
|
|
124
|
+
convert_to_array(obj).map { |item| send(subtype_convert_method, item) }
|
|
128
125
|
end
|
|
129
126
|
|
|
130
127
|
def convert_to_hash_of(obj, subtype)
|
|
@@ -134,9 +131,9 @@ module Philosophal
|
|
|
134
131
|
value_convert_method = convert_method_for(subtype[:value_type])
|
|
135
132
|
raise Philosophal::TypeError unless value_convert_method
|
|
136
133
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
134
|
+
# A new hash is built: Hash#to_h returns the given hash itself, which must not be modified.
|
|
135
|
+
convert_to_hash(obj).to_h do |key, value|
|
|
136
|
+
[send(key_convert_method, key), send(value_convert_method, value)]
|
|
140
137
|
end
|
|
141
138
|
end
|
|
142
139
|
end
|
|
@@ -27,7 +27,7 @@ module Philosophal
|
|
|
27
27
|
def load_from_array(array)
|
|
28
28
|
raise LoadError, 'invalid json parameter' if array.find { |hash| !hash.is_a?(Hash) }
|
|
29
29
|
|
|
30
|
-
array.map
|
|
30
|
+
array.map do |hash|
|
|
31
31
|
load_from_hash(hash)
|
|
32
32
|
end
|
|
33
33
|
end
|
|
@@ -35,23 +35,18 @@ module Philosophal
|
|
|
35
35
|
def load_from_hash(hash)
|
|
36
36
|
obj = @klass.new
|
|
37
37
|
|
|
38
|
-
hash.
|
|
38
|
+
hash.each do |key, value|
|
|
39
39
|
setter = setter_from_key(key)
|
|
40
40
|
next unless obj.respond_to?(setter)
|
|
41
41
|
|
|
42
|
-
obj.public_send(setter,
|
|
42
|
+
obj.public_send(setter, value)
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
obj
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
def setter_from_key(key)
|
|
49
|
-
|
|
50
|
-
property = @klass.philosophal_properties[real_key]
|
|
51
|
-
|
|
52
|
-
return :"#{property.name}=" if property
|
|
53
|
-
|
|
54
|
-
:"#{key}="
|
|
49
|
+
@klass.philosophal_properties.json_setters[key] || :"#{key}="
|
|
55
50
|
end
|
|
56
51
|
|
|
57
52
|
def load_from_json(str)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Philosophal
|
|
4
|
+
module Properties
|
|
5
|
+
module InstanceMethods
|
|
6
|
+
def cprop?(property_name, klass)
|
|
7
|
+
property = self.class.philosophal_properties.properties_index[property_name.to_sym]
|
|
8
|
+
return false unless property
|
|
9
|
+
|
|
10
|
+
klass = klass.class unless klass.is_a?(Class)
|
|
11
|
+
property.type == klass
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def philosophal_inspect(light = false)
|
|
15
|
+
keys_map = philosophal_inspect_map
|
|
16
|
+
|
|
17
|
+
if light
|
|
18
|
+
keys_map.reject! do |_, v|
|
|
19
|
+
v.nil? || (v.respond_to?(:empty?) && v.empty?)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
keys_str = keys_map.map { |k, v| [k, v.inspect].join(': ') }.join(', ')
|
|
24
|
+
"#{self.class}:#{format('0x00%x', object_id << 1)} #{keys_str}"
|
|
25
|
+
end
|
|
26
|
+
alias cprop_inspect philosophal_inspect
|
|
27
|
+
|
|
28
|
+
def philosophal_inspect_map
|
|
29
|
+
self.class.philosophal_properties.to_h do |property|
|
|
30
|
+
[
|
|
31
|
+
property.name,
|
|
32
|
+
send(property.name)
|
|
33
|
+
]
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
alias cprop_inspect_map philosophal_inspect_map
|
|
37
|
+
|
|
38
|
+
def philosophal_values
|
|
39
|
+
self.class.philosophal_properties.properties_index.values
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Properties declared with track_changes: true whose value changed since the last changes_applied,
|
|
43
|
+
# with their new value.
|
|
44
|
+
def changed_properties
|
|
45
|
+
self.class.philosophal_properties.tracked.each_with_object({}) do |property, changes|
|
|
46
|
+
next unless send(property.changed_method_name)
|
|
47
|
+
|
|
48
|
+
changes[property.name] = send(property.name)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Records the current values as the reference of the tracked properties: none of them is changed anymore.
|
|
53
|
+
# The values are duplicated, so modifying one in place is still seen as a change.
|
|
54
|
+
def changes_applied
|
|
55
|
+
self.class.philosophal_properties.tracked.each do |property|
|
|
56
|
+
instance_variable_set(property.was_variable_name, instance_variable_get(property.variable_name).dup)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
self
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def to_json_hash
|
|
63
|
+
self.class.philosophal_properties.json_properties.to_h do |property|
|
|
64
|
+
[
|
|
65
|
+
property.json_name,
|
|
66
|
+
send(property.name)
|
|
67
|
+
]
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def to_json(*_args)
|
|
72
|
+
to_json_hash.to_json
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -10,6 +10,14 @@ module Philosophal
|
|
|
10
10
|
@mutex = Mutex.new
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
+
# Called by #dup: a subclass gets its own index, so its properties do not leak into the parent.
|
|
14
|
+
def initialize_copy(source)
|
|
15
|
+
super
|
|
16
|
+
@properties_index = source.properties_index.dup
|
|
17
|
+
@mutex = Mutex.new
|
|
18
|
+
clear_caches
|
|
19
|
+
end
|
|
20
|
+
|
|
13
21
|
attr_reader :properties_index
|
|
14
22
|
|
|
15
23
|
def [](key)
|
|
@@ -20,9 +28,18 @@ module Philosophal
|
|
|
20
28
|
@properties_index.keys
|
|
21
29
|
end
|
|
22
30
|
|
|
31
|
+
# Yields each property in declaration order; Enumerable builds on it.
|
|
32
|
+
def each(&)
|
|
33
|
+
return enum_for(:each) { @properties_index.size } unless block_given?
|
|
34
|
+
|
|
35
|
+
@properties_index.each_value(&)
|
|
36
|
+
self
|
|
37
|
+
end
|
|
38
|
+
|
|
23
39
|
def <<(value)
|
|
24
40
|
@mutex.synchronize do
|
|
25
41
|
@properties_index[value.name] = value
|
|
42
|
+
clear_caches
|
|
26
43
|
end
|
|
27
44
|
|
|
28
45
|
self
|
|
@@ -47,6 +64,37 @@ module Philosophal
|
|
|
47
64
|
[property.json_name, key]
|
|
48
65
|
end
|
|
49
66
|
end
|
|
67
|
+
|
|
68
|
+
# Maps each JSON name to the writer of its property.
|
|
69
|
+
def json_setters
|
|
70
|
+
return @json_setters if defined?(@json_setters)
|
|
71
|
+
|
|
72
|
+
@json_setters = @properties_index.each_value.to_h do |property|
|
|
73
|
+
[property.json_name, property.writer_method_name]
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Properties declared with json: (exported by to_json_hash).
|
|
78
|
+
def json_properties
|
|
79
|
+
return @json_properties if defined?(@json_properties)
|
|
80
|
+
|
|
81
|
+
@json_properties = @properties_index.each_value.select(&:json)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Properties declared with track_changes: true.
|
|
85
|
+
def tracked
|
|
86
|
+
return @tracked if defined?(@tracked)
|
|
87
|
+
|
|
88
|
+
@tracked = @properties_index.each_value.select(&:track_changes?)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
def clear_caches
|
|
94
|
+
%i[@immutables @mutables @json_names_h @json_setters @json_properties @tracked].each do |cache|
|
|
95
|
+
remove_instance_variable(cache) if instance_variable_defined?(cache)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
50
98
|
end
|
|
51
99
|
end
|
|
52
100
|
end
|
|
@@ -3,36 +3,22 @@
|
|
|
3
3
|
module Philosophal
|
|
4
4
|
module Properties
|
|
5
5
|
autoload :Schema, 'philosophal/properties/schema'
|
|
6
|
+
autoload :InstanceMethods, 'philosophal/properties/instance_methods'
|
|
6
7
|
autoload :JSONLoader, 'philosophal/loaders/json_loader'
|
|
7
8
|
|
|
8
9
|
include Philosophal::Types
|
|
9
10
|
|
|
10
|
-
def cprop(name, type, default: nil, transform: nil, immutable: false, description: nil, json: false
|
|
11
|
-
|
|
11
|
+
def cprop(name, type, default: nil, transform: nil, immutable: false, description: nil, json: false, override: true,
|
|
12
|
+
track_changes: false)
|
|
13
|
+
__check_philosophal_options__(name, transform:, immutable:, override:, description:, json:, track_changes:)
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
unless Philosophal::Types::BooleanType::TRUE_FALSE_SET.include?(immutable)
|
|
18
|
-
raise Philosophal::ArgumentError, 'immutable param must be true or false.'
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
if description
|
|
22
|
-
raise Philosophal::ArgumentError, 'description param must be a string.' unless description.is_a?(String)
|
|
23
|
-
|
|
24
|
-
description.freeze unless description.frozen?
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
if json
|
|
28
|
-
unless json.is_a?(TrueClass) || json.is_a?(String) || json.is_a?(Symbol)
|
|
29
|
-
raise Philosophal::ArgumentError, 'json param must be true or a String or a Symbol.'
|
|
30
|
-
end
|
|
15
|
+
default.freeze unless default.is_a?(Proc)
|
|
16
|
+
description&.freeze
|
|
17
|
+
json = json.to_sym if json.is_a?(String)
|
|
31
18
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
property = __philosophal_property_class__.new(name:, type:, default:, transform:, immutable:, description:, json:)
|
|
19
|
+
property = __philosophal_property_class__.new(
|
|
20
|
+
name:, type:, default:, transform:, immutable:, description:, json:, override:, track_changes:
|
|
21
|
+
)
|
|
36
22
|
|
|
37
23
|
philosophal_properties << property
|
|
38
24
|
__define_philosophal_methods__(property)
|
|
@@ -76,84 +62,76 @@ module Philosophal
|
|
|
76
62
|
end
|
|
77
63
|
|
|
78
64
|
def __define_philosophal_methods__(new_property)
|
|
65
|
+
__set_philosophal_property_constant__(new_property)
|
|
79
66
|
code = __generate_philosophal_methods__(new_property)
|
|
80
67
|
__philosophal_extension__.module_eval(code)
|
|
81
68
|
end
|
|
82
69
|
|
|
70
|
+
# The constant is replaced when the property is redeclared in the same class.
|
|
71
|
+
def __set_philosophal_property_constant__(property)
|
|
72
|
+
extension = __philosophal_extension__
|
|
73
|
+
constant_name = property.constant_name
|
|
74
|
+
extension.send(:remove_const, constant_name) if extension.const_defined?(constant_name, false)
|
|
75
|
+
extension.const_set(constant_name, property)
|
|
76
|
+
extension.private_constant(constant_name)
|
|
77
|
+
end
|
|
78
|
+
|
|
83
79
|
def __philosophal_extension__
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def philosophal_inspect(light = false)
|
|
97
|
-
keys_map = philosophal_inspect_map
|
|
98
|
-
|
|
99
|
-
if light
|
|
100
|
-
keys_map.reject! do |_, v|
|
|
101
|
-
v.nil? || (v.respond_to?(:empty?) && v.empty?)
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
keys_str = keys_map.map { |k, v| [k, v.inspect].join(': ') }.join(', ')
|
|
106
|
-
"#{self.class}:#{format('0x00%x', object_id << 1)} #{keys_str}"
|
|
107
|
-
end
|
|
108
|
-
alias cprop_inspect philosophal_inspect
|
|
109
|
-
|
|
110
|
-
def philosophal_inspect_map
|
|
111
|
-
philosophal_values.to_h do |property|
|
|
112
|
-
[
|
|
113
|
-
property.name,
|
|
114
|
-
send(property.name)
|
|
115
|
-
]
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
alias cprop_inspect_map philosophal_inspect_map
|
|
119
|
-
|
|
120
|
-
def philosophal_values
|
|
121
|
-
self.class.philosophal_properties.properties_index.values
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
def to_json_hash
|
|
125
|
-
philosophal_values.select(&:json).to_h do |property|
|
|
126
|
-
[
|
|
127
|
-
property.json_name,
|
|
128
|
-
send(property.name)
|
|
129
|
-
]
|
|
130
|
-
end
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
def to_json(*_args)
|
|
134
|
-
to_json_hash.to_json
|
|
135
|
-
end
|
|
136
|
-
end
|
|
80
|
+
@__philosophal_extension__ ||= Module.new { include Philosophal::Properties::InstanceMethods }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def __generate_philosophal_methods__(new_property)
|
|
84
|
+
buffer = +''
|
|
85
|
+
|
|
86
|
+
new_property.method_generators.each do |method_name, generator|
|
|
87
|
+
next unless new_property.override? || !method_defined?(method_name)
|
|
88
|
+
|
|
89
|
+
__remove_philosophal_own_method__(method_name) if new_property.override?
|
|
90
|
+
new_property.public_send(generator, buffer)
|
|
137
91
|
end
|
|
92
|
+
|
|
93
|
+
# puts buffer
|
|
94
|
+
return buffer if buffer.empty?
|
|
95
|
+
|
|
96
|
+
+"# frozen_string_philosophal: true\n" << buffer
|
|
138
97
|
end
|
|
139
98
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
99
|
+
# Generated methods live in an included module, so a method defined in the class itself
|
|
100
|
+
# would hide them: it is removed to let the property override it. The method generated by a
|
|
101
|
+
# previous declaration of the property is removed too, so redefining it raises no warning.
|
|
102
|
+
def __remove_philosophal_own_method__(method_name)
|
|
103
|
+
[self, __philosophal_extension__].each do |owner|
|
|
104
|
+
next unless owner.method_defined?(method_name, false) || owner.private_method_defined?(method_name, false)
|
|
105
|
+
|
|
106
|
+
owner.remove_method(method_name)
|
|
146
107
|
end
|
|
108
|
+
end
|
|
147
109
|
|
|
148
|
-
|
|
110
|
+
def __check_philosophal_options__(name, transform:, immutable:, override:, description:, json:, track_changes:)
|
|
111
|
+
__check_philosophal_transform_option__(name, transform)
|
|
112
|
+
__check_philosophal_boolean_option__(:override, override)
|
|
113
|
+
__check_philosophal_boolean_option__(:immutable, immutable)
|
|
114
|
+
__check_philosophal_boolean_option__(:track_changes, track_changes)
|
|
149
115
|
|
|
150
|
-
|
|
116
|
+
if description && !description.is_a?(String)
|
|
117
|
+
raise Philosophal::ArgumentError, 'description param must be a string.'
|
|
118
|
+
end
|
|
151
119
|
|
|
152
|
-
|
|
120
|
+
return if !json || json == true || json.is_a?(String) || json.is_a?(Symbol)
|
|
153
121
|
|
|
154
|
-
|
|
122
|
+
raise Philosophal::ArgumentError, 'json param must be true or a String or a Symbol.'
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def __check_philosophal_transform_option__(name, transform)
|
|
126
|
+
return if !transform || transform.is_a?(Proc) || transform.is_a?(Symbol)
|
|
127
|
+
|
|
128
|
+
raise Philosophal::ArgumentError, "transform param must be a Symbol object or a Proc (#{name})."
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def __check_philosophal_boolean_option__(option_name, value)
|
|
132
|
+
return if Philosophal::Types::BooleanType::TRUE_FALSE_SET.include?(value)
|
|
155
133
|
|
|
156
|
-
|
|
134
|
+
raise Philosophal::ArgumentError, "#{option_name} param must be true or false."
|
|
157
135
|
end
|
|
158
136
|
end
|
|
159
137
|
end
|