neo4j 5.2.15 → 6.0.0.alpha.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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +17 -19
  3. data/Gemfile +1 -1
  4. data/lib/neo4j.rb +4 -1
  5. data/lib/neo4j/active_node.rb +6 -15
  6. data/lib/neo4j/active_node/has_n.rb +52 -21
  7. data/lib/neo4j/active_node/has_n/association.rb +1 -1
  8. data/lib/neo4j/active_node/id_property.rb +1 -1
  9. data/lib/neo4j/active_node/id_property/accessor.rb +1 -1
  10. data/lib/neo4j/active_node/labels.rb +7 -101
  11. data/lib/neo4j/active_node/labels/index.rb +87 -0
  12. data/lib/neo4j/active_node/persistence.rb +3 -2
  13. data/lib/neo4j/active_node/query.rb +1 -1
  14. data/lib/neo4j/active_node/query/query_proxy.rb +7 -9
  15. data/lib/neo4j/active_node/query/query_proxy_eager_loading.rb +0 -1
  16. data/lib/neo4j/active_node/query/query_proxy_link.rb +12 -4
  17. data/lib/neo4j/active_node/query/query_proxy_methods.rb +4 -6
  18. data/lib/neo4j/active_node/query/query_proxy_unpersisted.rb +4 -8
  19. data/lib/neo4j/active_node/unpersisted.rb +12 -10
  20. data/lib/neo4j/active_node/validations.rb +2 -2
  21. data/lib/neo4j/active_rel.rb +7 -4
  22. data/lib/neo4j/active_rel/persistence.rb +13 -4
  23. data/lib/neo4j/active_rel/query.rb +8 -0
  24. data/lib/neo4j/active_rel/related_node.rb +1 -27
  25. data/lib/neo4j/errors.rb +2 -0
  26. data/lib/neo4j/schema/operation.rb +91 -0
  27. data/lib/neo4j/shared.rb +3 -3
  28. data/lib/neo4j/shared/callbacks.rb +2 -7
  29. data/lib/neo4j/shared/{declared_property_manager.rb → declared_properties.rb} +34 -2
  30. data/lib/neo4j/shared/declared_property.rb +19 -0
  31. data/lib/neo4j/shared/declared_property/index.rb +37 -0
  32. data/lib/neo4j/shared/initialize.rb +2 -2
  33. data/lib/neo4j/shared/persistence.rb +3 -25
  34. data/lib/neo4j/shared/property.rb +24 -10
  35. data/lib/neo4j/shared/type_converters.rb +131 -6
  36. data/lib/neo4j/tasks/migration.rake +3 -3
  37. data/lib/neo4j/type_converters.rb +1 -1
  38. data/lib/neo4j/version.rb +1 -1
  39. data/neo4j.gemspec +2 -2
  40. metadata +13 -10
data/lib/neo4j/errors.rb CHANGED
@@ -7,4 +7,6 @@ module Neo4j
7
7
  # Raised when Neo4j.rb cannot find record by given id.
8
8
  class RecordNotFound < Neo4jrbError
9
9
  end
10
+
11
+ class InvalidPropertyOptionsError < Neo4jrbError; end
10
12
  end
@@ -0,0 +1,91 @@
1
+ module Neo4j
2
+ module Schema
3
+ class Operation
4
+ attr_reader :label_name, :property, :options
5
+
6
+ def initialize(label_name, property, options = default_options)
7
+ @label_name = label_name.to_sym
8
+ @property = property.to_sym
9
+ @options = options
10
+ end
11
+
12
+ def self.incompatible_operation_classes
13
+ []
14
+ end
15
+
16
+ def create!
17
+ drop_incompatible!
18
+ return if exist?
19
+ label_object.send(:"create_#{type}", property, options)
20
+ end
21
+
22
+ def label_object
23
+ @label_object ||= Neo4j::Label.create(label_name)
24
+ end
25
+
26
+ def incompatible_operation_classes
27
+ self.class.incompatible_operation_classes
28
+ end
29
+
30
+ def drop!
31
+ label_object.send(:"drop_#{type}", property, options)
32
+ end
33
+
34
+ def drop_incompatible!
35
+ incompatible_operation_classes.each do |clazz|
36
+ operation = clazz.new(label_name, property)
37
+ operation.drop! if operation.exist?
38
+ end
39
+ end
40
+
41
+ def exist?
42
+ fail 'Abstract class, not implemented'
43
+ end
44
+
45
+ def default_options
46
+ {}
47
+ end
48
+
49
+ def type
50
+ fail 'Abstract class, not implemented'
51
+ end
52
+ end
53
+
54
+ class ExactIndexOperation < Neo4j::Schema::Operation
55
+ def self.incompatible_operation_classes
56
+ [UniqueConstraintOperation]
57
+ end
58
+
59
+ def type
60
+ 'index'
61
+ end
62
+
63
+ def exist?
64
+ label_object.indexes[:property_keys].include?([property])
65
+ end
66
+ end
67
+
68
+ class UniqueConstraintOperation < Neo4j::Schema::Operation
69
+ def self.incompatible_operation_classes
70
+ [ExactIndexOperation]
71
+ end
72
+
73
+ def type
74
+ 'constraint'
75
+ end
76
+
77
+ def create!
78
+ return if exist?
79
+ super
80
+ end
81
+
82
+ def exist?
83
+ Neo4j::Label.constraint?(label_name, property)
84
+ end
85
+
86
+ def default_options
87
+ {type: :unique}
88
+ end
89
+ end
90
+ end
91
+ end
data/lib/neo4j/shared.rb CHANGED
@@ -28,15 +28,15 @@ module Neo4j
28
28
 
29
29
  included do
30
30
  self.include_root_in_json = Neo4j::Config.include_root_in_json
31
- @_declared_property_manager ||= Neo4j::Shared::DeclaredPropertyManager.new(self)
31
+ @_declared_properties ||= Neo4j::Shared::DeclaredProperties.new(self)
32
32
 
33
33
  def self.i18n_scope
34
34
  :neo4j
35
35
  end
36
36
  end
37
37
 
38
- def declared_property_manager
39
- self.class.declared_property_manager
38
+ def declared_properties
39
+ self.class.declared_properties
40
40
  end
41
41
  end
42
42
  end
@@ -9,13 +9,8 @@ module Neo4j
9
9
 
10
10
  included do
11
11
  include ActiveModel::Validations::Callbacks
12
- # after_find is triggered by the `find` method defined in lib/neo4j/active_node/id_property.rb
13
12
  define_model_callbacks :initialize, :find, only: :after
14
- define_model_callbacks :save, :create, :update, :destroy, :touch
15
- end
16
-
17
- def initialize(args = nil)
18
- run_callbacks(:initialize) { super }
13
+ define_model_callbacks :save, :create, :update, :destroy
19
14
  end
20
15
 
21
16
  def destroy #:nodoc:
@@ -30,7 +25,7 @@ module Neo4j
30
25
  tx.close if tx
31
26
  end
32
27
 
33
- def touch #:nodoc:
28
+ def touch(*) #:nodoc:
34
29
  run_callbacks(:touch) { super }
35
30
  end
36
31
 
@@ -5,17 +5,26 @@ module Neo4j::Shared
5
5
  # a way of separating behavior from the general Active{obj} modules.
6
6
  #
7
7
  # See Neo4j::Shared::DeclaredProperty for definitions of the property objects themselves.
8
- class DeclaredPropertyManager
8
+ class DeclaredProperties
9
9
  include Neo4j::Shared::TypeConverters
10
10
 
11
11
  attr_reader :klass
12
+ delegate :each, :each_pair, :each_key, :each_value, to: :registered_properties
12
13
 
13
14
  # Each class that includes Neo4j::ActiveNode or Neo4j::ActiveRel gets one instance of this class.
14
- # @param [#declared_property_manager] klass An object that has the #declared_property_manager method.
15
+ # @param [#declared_properties] klass An object that has the #declared_properties method.
15
16
  def initialize(klass)
16
17
  @klass = klass
17
18
  end
18
19
 
20
+ def [](key)
21
+ registered_properties[key.to_sym]
22
+ end
23
+
24
+ def property?(key)
25
+ registered_properties.key?(key.to_sym)
26
+ end
27
+
19
28
  # @param [Neo4j::Shared::DeclaredProperty] property An instance of DeclaredProperty, created as the result of calling
20
29
  # #property on an ActiveNode or ActiveRel class. The DeclaredProperty has specifics about the property, but registration
21
30
  # makes the management object aware of it. This is necessary for type conversion, defaults, and inclusion in the nil and string hashes.
@@ -27,6 +36,18 @@ module Neo4j::Shared
27
36
  declared_property_defaults[property.name] = property.default_value if !property.default_value.nil?
28
37
  end
29
38
 
39
+ def index_or_fail!(key, id_property_name, type = :exact)
40
+ return if key == id_property_name
41
+ fail "Cannot index undeclared property #{key}" unless property?(key)
42
+ registered_properties[key].index!(type)
43
+ end
44
+
45
+ def constraint_or_fail!(key, id_property_name, type = :unique)
46
+ return if key == id_property_name
47
+ fail "Cannot constraint undeclared property #{property}" unless property?(key)
48
+ registered_properties[key].constraint!(type)
49
+ end
50
+
30
51
  # The :default option in Neo4j::ActiveNode#property class method allows for setting a default value instead of
31
52
  # nil on declared properties. This holds those values.
32
53
  def declared_property_defaults
@@ -37,6 +58,10 @@ module Neo4j::Shared
37
58
  @_registered_properties ||= {}
38
59
  end
39
60
 
61
+ def indexed_properties
62
+ registered_properties.select { |_, p| p.index_or_constraint? }
63
+ end
64
+
40
65
  # During object wrap, a hash is needed that contains each declared property with a nil value.
41
66
  # The active_attr dependency is capable of providing this but it is expensive and calculated on the fly
42
67
  # each time it is called. Rather than rely on that, we build this progressively as properties are registered.
@@ -113,6 +138,13 @@ module Neo4j::Shared
113
138
  @upstream_primitives ||= {}
114
139
  end
115
140
 
141
+ EXCLUDED_TYPES = [Array, Range, Regexp]
142
+ def value_for_where(key, value)
143
+ return value unless prop = registered_properties[key]
144
+ return value_for_db(key, value) if prop.typecaster && prop.typecaster.convert_type == value.class
145
+ EXCLUDED_TYPES.include?(value.class) ? value : value_for_db(key, value)
146
+ end
147
+
116
148
  def value_for_db(key, value)
117
149
  return value unless registered_properties[key]
118
150
  convert_property(key, value, :to_db)
@@ -2,6 +2,7 @@ module Neo4j::Shared
2
2
  # Contains methods related to the management
3
3
  class DeclaredProperty
4
4
  class IllegalPropertyError < StandardError; end
5
+ include Neo4j::Shared::DeclaredProperty::Index
5
6
 
6
7
  ILLEGAL_PROPS = %w(from_node to_node start_node end_node)
7
8
  attr_reader :name, :name_string, :name_sym, :options, :magic_typecaster
@@ -11,6 +12,7 @@ module Neo4j::Shared
11
12
  @name = @name_sym = name
12
13
  @name_string = name.to_s
13
14
  @options = options
15
+ fail_invalid_options!
14
16
  end
15
17
 
16
18
  def register
@@ -29,8 +31,25 @@ module Neo4j::Shared
29
31
  options[:default]
30
32
  end
31
33
 
34
+ def fail_invalid_options!
35
+ case
36
+ when index?(:exact) && constraint?(:unique)
37
+ fail Neo4j::InvalidPropertyOptionsError,
38
+ "#Uniqueness constraints also provide exact indexes, cannot set both options on property #{name}"
39
+ end
40
+ end
41
+
32
42
  private
33
43
 
44
+ def option_with_value!(key, value)
45
+ options[key] = value
46
+ fail_invalid_options!
47
+ end
48
+
49
+ def option_with_value?(key, value)
50
+ options[key] == value
51
+ end
52
+
34
53
  # Tweaks properties
35
54
  def register_magic_properties
36
55
  options[:type] ||= Neo4j::Config.timestamp_type if timestamp_prop?
@@ -0,0 +1,37 @@
1
+ module Neo4j::Shared
2
+ class DeclaredProperty
3
+ # None of these methods interact with the database. They only keep track of property settings in models.
4
+ # It could (should?) handle the actual indexing/constraining, but that's TBD.
5
+ module Index
6
+ def index_or_constraint?
7
+ index?(:exact) || constraint?(:unique)
8
+ end
9
+
10
+ def index?(type = :exact)
11
+ options.key?(:index) && options[:index] == type
12
+ end
13
+
14
+ def constraint?(type = :unique)
15
+ options.key?(:constraint) && options[:constraint] == type
16
+ end
17
+
18
+ def index!(type = :exact)
19
+ fail Neo4j::InvalidPropertyOptionsError, "Unable to set index on constrainted property #{name}" if constraint?(:unique)
20
+ options[:index] = type
21
+ end
22
+
23
+ def constraint!(type = :unique)
24
+ fail Neo4j::InvalidPropertyOptionsError, "Unable to set constraint on indexed property #{name}" if index?(:exact)
25
+ options[:constraint] = type
26
+ end
27
+
28
+ def unindex!(type = :exact)
29
+ options.delete(:index) if index?(type)
30
+ end
31
+
32
+ def unconstraint!(type = :unique)
33
+ options.delete(:constraint) if constraint?(type)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -15,12 +15,12 @@ module Neo4j::Shared
15
15
  @attributes ||= Hash[self.class.attributes_nil_hash]
16
16
  stringify_attributes!(@attributes, properties)
17
17
  self.default_properties = properties if respond_to?(:default_properties=)
18
- self.class.declared_property_manager.convert_properties_to(self, :ruby, @attributes)
18
+ self.class.declared_properties.convert_properties_to(self, :ruby, @attributes)
19
19
  end
20
20
 
21
21
  def stringify_attributes!(attr, properties)
22
22
  properties.each_pair do |k, v|
23
- key = self.class.declared_property_manager.string_key(k)
23
+ key = self.class.declared_properties.string_key(k)
24
24
  attr[key.freeze] = v
25
25
  end
26
26
  end
@@ -82,11 +82,6 @@ module Neo4j::Shared
82
82
  end
83
83
  end
84
84
 
85
- def touch
86
- fail 'Cannot touch on a new record object' unless persisted?
87
- update_attribute!(:updated_at, Time.now) if respond_to?(:updated_at=)
88
- end
89
-
90
85
  # Returns +true+ if the record is persisted, i.e. it's not a new record and it was not destroyed
91
86
  def persisted?
92
87
  !new_record? && !destroyed?
@@ -111,26 +106,9 @@ module Neo4j::Shared
111
106
 
112
107
  # Returns +true+ if the object was destroyed.
113
108
  def destroyed?
114
- @_deleted || _destroyed_double_check?
109
+ @_deleted
115
110
  end
116
111
 
117
- # These two methods should be removed in 6.0.0
118
- def _destroyed_double_check?
119
- if _active_record_destroyed_behavior?
120
- false
121
- else
122
- (!new_record? && !exist?)
123
- end
124
- end
125
-
126
- def _active_record_destroyed_behavior?
127
- fail 'Remove this workaround in 6.0.0' if Neo4j::VERSION >= '6.0.0'
128
-
129
- !!Neo4j::Config[:_active_record_destroyed_behavior]
130
- end
131
- # End of two methods which should be removed in 6.0.0
132
-
133
-
134
112
  # @return [Hash] all defined and none nil properties
135
113
  def props
136
114
  attributes.reject { |_, v| v.nil? }.symbolize_keys
@@ -193,7 +171,7 @@ module Neo4j::Shared
193
171
  private
194
172
 
195
173
  def props_for_db(props_hash)
196
- self.class.declared_property_manager.convert_properties_to(self, :db, props_hash)
174
+ self.class.declared_properties.convert_properties_to(self, :db, props_hash)
197
175
  end
198
176
 
199
177
  def model_cache_key
@@ -222,7 +200,7 @@ module Neo4j::Shared
222
200
  end
223
201
 
224
202
  def inject_defaults!(properties)
225
- self.class.declared_property_manager.declared_property_defaults.each_pair do |k, v|
203
+ self.class.declared_properties.declared_property_defaults.each_pair do |k, v|
226
204
  properties[k.to_sym] = v if send(k).nil?
227
205
  end
228
206
  properties
@@ -108,10 +108,10 @@ module Neo4j::Shared
108
108
  module ClassMethods
109
109
  extend Forwardable
110
110
 
111
- def_delegators :declared_property_manager, :serialized_properties, :serialized_properties=, :serialize, :declared_property_defaults
111
+ def_delegators :declared_properties, :serialized_properties, :serialized_properties=, :serialize, :declared_property_defaults
112
112
 
113
113
  def inherited(other)
114
- self.declared_property_manager.registered_properties.each_pair do |prop_key, prop_def|
114
+ self.declared_properties.registered_properties.each_pair do |prop_key, prop_def|
115
115
  other.property(prop_key, prop_def.options)
116
116
  end
117
117
  super
@@ -146,22 +146,36 @@ module Neo4j::Shared
146
146
  # property :name, constraint: :unique
147
147
  # end
148
148
  def property(name, options = {})
149
+ build_property(name, options) do |prop|
150
+ attribute(name, prop.options)
151
+ end
152
+ end
153
+
154
+ # @param [Symbol] name The property name
155
+ # @param [ActiveAttr::AttributeDefinition] active_attr A cloned AttributeDefinition to reuse
156
+ # @param [Hash] options An options hash to use in the new property definition
157
+ def inherit_property(name, active_attr, options = {})
158
+ build_property(name, options) do |prop|
159
+ attributes[prop.name.to_s] = active_attr
160
+ end
161
+ end
162
+
163
+ def build_property(name, options)
149
164
  prop = DeclaredProperty.new(name, options)
150
165
  prop.register
151
- declared_property_manager.register(prop)
152
-
153
- attribute(name, prop.options)
166
+ declared_properties.register(prop)
167
+ yield prop
154
168
  constraint_or_index(name, options)
155
169
  end
156
170
 
157
171
  def undef_property(name)
158
- declared_property_manager.unregister(name)
172
+ declared_properties.unregister(name)
159
173
  attribute_methods(name).each { |method| undef_method(method) }
160
174
  undef_constraint_or_index(name)
161
175
  end
162
176
 
163
- def declared_property_manager
164
- @_declared_property_manager ||= DeclaredPropertyManager.new(self)
177
+ def declared_properties
178
+ @_declared_properties ||= DeclaredProperties.new(self)
165
179
  end
166
180
 
167
181
  def attribute!(name, options = {})
@@ -176,7 +190,7 @@ module Neo4j::Shared
176
190
  # @return [Hash] A frozen hash of all model properties with nil values. It is used during node loading and prevents
177
191
  # an extra call to a slow dependency method.
178
192
  def attributes_nil_hash
179
- declared_property_manager.attributes_nil_hash
193
+ declared_properties.attributes_nil_hash
180
194
  end
181
195
 
182
196
  private
@@ -188,7 +202,7 @@ module Neo4j::Shared
188
202
  constraint(name, type: :unique)
189
203
  elsif options[:index]
190
204
  fail "unknown index type #{options[:index]}, only :exact supported" if options[:index] != :exact
191
- index(name, options) if options[:index] == :exact
205
+ index(name) if options[:index] == :exact
192
206
  end
193
207
  end
194
208
  end
@@ -1,9 +1,128 @@
1
1
  require 'date'
2
+ require 'bigdecimal'
3
+ require 'bigdecimal/util'
4
+ require 'active_support/core_ext/big_decimal/conversions'
2
5
 
3
6
  module Neo4j::Shared
4
7
  module TypeConverters
8
+ class BaseConverter
9
+ class << self
10
+ def converted?(value)
11
+ value.is_a?(db_type)
12
+ end
13
+ end
14
+ end
15
+
16
+ class IntegerConverter < BaseConverter
17
+ class << self
18
+ def convert_type
19
+ Integer
20
+ end
21
+
22
+ def db_type
23
+ Integer
24
+ end
25
+
26
+ def to_db(value)
27
+ value.to_i
28
+ end
29
+
30
+ alias_method :to_ruby, :to_db
31
+ end
32
+ end
33
+
34
+ class FloatConverter < BaseConverter
35
+ class << self
36
+ def convert_type
37
+ Float
38
+ end
39
+
40
+ def db_type
41
+ Float
42
+ end
43
+
44
+ def to_db(value)
45
+ value.to_f
46
+ end
47
+ alias_method :to_ruby, :to_db
48
+ end
49
+ end
50
+
51
+ class BigDecimalConverter < BaseConverter
52
+ class << self
53
+ def convert_type
54
+ BigDecimal
55
+ end
56
+
57
+ def db_type
58
+ BigDecimal
59
+ end
60
+
61
+ def to_db(value)
62
+ case value
63
+ when Rational
64
+ value.to_f.to_d
65
+ when respond_to?(:to_d)
66
+ value.to_d
67
+ else
68
+ BigDecimal.new(value.to_s)
69
+ end
70
+ end
71
+ alias_method :to_ruby, :to_db
72
+ end
73
+ end
74
+
75
+ class StringConverter < BaseConverter
76
+ class << self
77
+ def convert_type
78
+ String
79
+ end
80
+
81
+ def db_type
82
+ String
83
+ end
84
+
85
+ def to_db(value)
86
+ value.to_s
87
+ end
88
+ alias_method :to_ruby, :to_db
89
+ end
90
+ end
91
+
92
+ class BooleanConverter < BaseConverter
93
+ FALSE_VALUES = %w(n N no No NO false False FALSE off Off OFF f F)
94
+
95
+ class << self
96
+ def converted?(value)
97
+ convert_type.include?(value)
98
+ end
99
+
100
+ def convert_type
101
+ [true, false]
102
+ end
103
+
104
+ def db_type
105
+ ActiveAttr::Typecasting::Boolean
106
+ end
107
+
108
+ def to_db(value)
109
+ return false if FALSE_VALUES.include?(value)
110
+ case value
111
+ when TrueClass, FalseClass
112
+ value
113
+ when Numeric, /^\-?[0-9]/
114
+ !value.to_f.zero?
115
+ else
116
+ value.present?
117
+ end
118
+ end
119
+
120
+ alias_method :to_ruby, :to_db
121
+ end
122
+ end
123
+
5
124
  # Converts Date objects to Java long types. Must be timezone UTC.
6
- class DateConverter
125
+ class DateConverter < BaseConverter
7
126
  class << self
8
127
  def convert_type
9
128
  Date
@@ -24,7 +143,7 @@ module Neo4j::Shared
24
143
  end
25
144
 
26
145
  # Converts DateTime objects to and from Java long types. Must be timezone UTC.
27
- class DateTimeConverter
146
+ class DateTimeConverter < BaseConverter
28
147
  class << self
29
148
  def convert_type
30
149
  DateTime
@@ -61,7 +180,7 @@ module Neo4j::Shared
61
180
  end
62
181
  end
63
182
 
64
- class TimeConverter
183
+ class TimeConverter < BaseConverter
65
184
  class << self
66
185
  def convert_type
67
186
  Time
@@ -95,7 +214,7 @@ module Neo4j::Shared
95
214
  end
96
215
 
97
216
  # Converts hash to/from YAML
98
- class YAMLConverter
217
+ class YAMLConverter < BaseConverter
99
218
  class << self
100
219
  def convert_type
101
220
  Hash
@@ -116,7 +235,7 @@ module Neo4j::Shared
116
235
  end
117
236
 
118
237
  # Converts hash to/from JSON
119
- class JSONConverter
238
+ class JSONConverter < BaseConverter
120
239
  class << self
121
240
  def convert_type
122
241
  JSON
@@ -159,6 +278,7 @@ module Neo4j::Shared
159
278
  private
160
279
 
161
280
  def converted_property(type, value, converter)
281
+ return nil if value.nil?
162
282
  TypeConverters.converters[type].nil? ? value : TypeConverters.to_other(converter, value, type)
163
283
  end
164
284
 
@@ -209,7 +329,12 @@ module Neo4j::Shared
209
329
  # @param [#convert_type] found_converter An object that responds to #convert_type, hinting that it is a type converter.
210
330
  # @param value The value for conversion.
211
331
  def formatted_for_db?(found_converter, value)
212
- found_converter.respond_to?(:db_type) && value.is_a?(found_converter.db_type)
332
+ return false unless found_converter.respond_to?(:db_type)
333
+ if found_converter.respond_to?(:converted)
334
+ found_converter.converted?(value)
335
+ else
336
+ value.is_a?(found_converter.db_type)
337
+ end
213
338
  end
214
339
 
215
340
  def register_converter(converter)