mobility 0.8.8 → 1.0.0.alpha
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/CHANGELOG.md +56 -0
- data/Gemfile +52 -16
- data/Gemfile.lock +113 -52
- data/Guardfile +23 -1
- data/README.md +184 -92
- data/Rakefile +6 -4
- data/lib/mobility.rb +40 -166
- data/lib/mobility/active_record/translation.rb +1 -1
- data/lib/mobility/arel/nodes/pg_ops.rb +1 -1
- data/lib/mobility/backend.rb +19 -41
- data/lib/mobility/backends.rb +20 -0
- data/lib/mobility/backends/active_record.rb +4 -0
- data/lib/mobility/backends/active_record/column.rb +2 -0
- data/lib/mobility/backends/active_record/container.rb +4 -2
- data/lib/mobility/backends/active_record/hstore.rb +2 -0
- data/lib/mobility/backends/active_record/json.rb +2 -0
- data/lib/mobility/backends/active_record/jsonb.rb +2 -0
- data/lib/mobility/backends/active_record/key_value.rb +5 -3
- data/lib/mobility/backends/active_record/pg_hash.rb +1 -1
- data/lib/mobility/backends/active_record/serialized.rb +2 -0
- data/lib/mobility/backends/active_record/table.rb +5 -3
- data/lib/mobility/backends/column.rb +0 -6
- data/lib/mobility/backends/container.rb +2 -1
- data/lib/mobility/backends/hash.rb +39 -0
- data/lib/mobility/backends/hstore.rb +0 -1
- data/lib/mobility/backends/json.rb +0 -1
- data/lib/mobility/backends/jsonb.rb +0 -1
- data/lib/mobility/backends/key_value.rb +22 -14
- data/lib/mobility/backends/null.rb +2 -0
- data/lib/mobility/backends/sequel.rb +3 -0
- data/lib/mobility/backends/sequel/column.rb +2 -0
- data/lib/mobility/backends/sequel/container.rb +3 -1
- data/lib/mobility/backends/sequel/hstore.rb +2 -0
- data/lib/mobility/backends/sequel/json.rb +2 -0
- data/lib/mobility/backends/sequel/jsonb.rb +3 -1
- data/lib/mobility/backends/sequel/key_value.rb +8 -6
- data/lib/mobility/backends/sequel/serialized.rb +2 -0
- data/lib/mobility/backends/sequel/table.rb +5 -2
- data/lib/mobility/backends/serialized.rb +1 -3
- data/lib/mobility/backends/table.rb +14 -6
- data/lib/mobility/pluggable.rb +36 -0
- data/lib/mobility/plugin.rb +260 -0
- data/lib/mobility/plugins.rb +26 -25
- data/lib/mobility/plugins/active_model.rb +17 -0
- data/lib/mobility/plugins/active_model/cache.rb +26 -0
- data/lib/mobility/plugins/active_model/dirty.rb +310 -54
- data/lib/mobility/plugins/active_record.rb +34 -0
- data/lib/mobility/plugins/active_record/backend.rb +25 -0
- data/lib/mobility/plugins/active_record/cache.rb +28 -0
- data/lib/mobility/plugins/active_record/dirty.rb +72 -101
- data/lib/mobility/plugins/active_record/query.rb +48 -34
- data/lib/mobility/plugins/active_record/uniqueness_validation.rb +60 -0
- data/lib/mobility/plugins/attribute_methods.rb +28 -20
- data/lib/mobility/plugins/attributes.rb +70 -0
- data/lib/mobility/plugins/backend.rb +138 -0
- data/lib/mobility/plugins/backend_reader.rb +34 -0
- data/lib/mobility/plugins/cache.rb +59 -24
- data/lib/mobility/plugins/default.rb +22 -17
- data/lib/mobility/plugins/dirty.rb +12 -33
- data/lib/mobility/plugins/fallbacks.rb +51 -43
- data/lib/mobility/plugins/fallthrough_accessors.rb +26 -25
- data/lib/mobility/plugins/locale_accessors.rb +25 -35
- data/lib/mobility/plugins/presence.rb +28 -21
- data/lib/mobility/plugins/query.rb +8 -17
- data/lib/mobility/plugins/reader.rb +50 -0
- data/lib/mobility/plugins/sequel.rb +34 -0
- data/lib/mobility/plugins/sequel/backend.rb +25 -0
- data/lib/mobility/plugins/sequel/cache.rb +24 -0
- data/lib/mobility/plugins/sequel/dirty.rb +45 -32
- data/lib/mobility/plugins/sequel/query.rb +21 -6
- data/lib/mobility/plugins/writer.rb +44 -0
- data/lib/mobility/translations.rb +95 -0
- data/lib/mobility/version.rb +12 -1
- data/lib/rails/generators/mobility/templates/initializer.rb +95 -77
- metadata +51 -51
- metadata.gz.sig +0 -0
- data/lib/mobility/active_model.rb +0 -4
- data/lib/mobility/active_model/backend_resetter.rb +0 -26
- data/lib/mobility/active_record.rb +0 -23
- data/lib/mobility/active_record/backend_resetter.rb +0 -26
- data/lib/mobility/active_record/uniqueness_validator.rb +0 -60
- data/lib/mobility/attributes.rb +0 -324
- data/lib/mobility/backend/orm_delegator.rb +0 -44
- data/lib/mobility/backend_resetter.rb +0 -50
- data/lib/mobility/configuration.rb +0 -138
- data/lib/mobility/fallbacks.rb +0 -28
- data/lib/mobility/interface.rb +0 -0
- data/lib/mobility/loaded.rb +0 -4
- data/lib/mobility/plugins/active_record/attribute_methods.rb +0 -38
- data/lib/mobility/plugins/cache/translation_cacher.rb +0 -40
- data/lib/mobility/sequel.rb +0 -9
- data/lib/mobility/sequel/backend_resetter.rb +0 -23
- data/lib/mobility/translates.rb +0 -73
@@ -1,6 +1,4 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
require "mobility/backend_resetter"
|
3
|
-
require "mobility/plugins/fallthrough_accessors"
|
4
2
|
|
5
3
|
module Mobility
|
6
4
|
module Plugins
|
@@ -20,40 +18,21 @@ details.
|
|
20
18
|
|
21
19
|
=end
|
22
20
|
module Dirty
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
# @param [Boolean] option Value of option
|
27
|
-
# @raise [ArgumentError] if model class does not support dirty tracking
|
28
|
-
def apply(attributes, option)
|
29
|
-
if option
|
30
|
-
FallthroughAccessors.apply(attributes, true)
|
31
|
-
include_dirty_module(attributes.backend_class, attributes.model_class, *attributes.names)
|
32
|
-
end
|
33
|
-
end
|
21
|
+
extend Plugin
|
22
|
+
|
23
|
+
default true
|
34
24
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
Plugins::ActiveRecord::Dirty
|
43
|
-
else
|
44
|
-
require "mobility/plugins/active_model/dirty"
|
45
|
-
Plugins::ActiveModel::Dirty
|
46
|
-
end
|
47
|
-
elsif Loaded::Sequel && model_class < ::Sequel::Model
|
48
|
-
require "mobility/plugins/sequel/dirty"
|
49
|
-
Plugins::Sequel::Dirty
|
50
|
-
else
|
51
|
-
raise ArgumentError, "#{model_class} does not support Dirty module."
|
52
|
-
end
|
53
|
-
backend_class.include dirty_module
|
54
|
-
model_class.include dirty_module.const_get(:MethodsBuilder).new(*attribute_names)
|
25
|
+
requires :backend, include: :before
|
26
|
+
requires :fallthrough_accessors
|
27
|
+
|
28
|
+
initialize_hook do
|
29
|
+
if options[:dirty] && !options[:fallthrough_accessors]
|
30
|
+
warn 'The Dirty plugin depends on Fallthrough Accessors being enabled, '\
|
31
|
+
'but fallthrough_accessors option is falsey'
|
55
32
|
end
|
56
33
|
end
|
57
34
|
end
|
35
|
+
|
36
|
+
register_plugin(:dirty, Dirty)
|
58
37
|
end
|
59
38
|
end
|
@@ -8,15 +8,14 @@ module Mobility
|
|
8
8
|
Falls back to one or more alternative locales in case no value is defined for a
|
9
9
|
given locale.
|
10
10
|
|
11
|
-
For +fallbacks: true+, Mobility will use
|
12
|
-
|
13
|
-
|
14
|
-
configured (see {Mobility::Configuration}).
|
11
|
+
For +fallbacks: true+, Mobility will use an instance of
|
12
|
+
+I18n::Locale::Fallbacks+, but this can be configured by overriding
|
13
|
+
+generate_fallbacks+ in the translations class.
|
15
14
|
|
16
15
|
If a hash is passed to the +fallbacks+ option, a new fallbacks instance will be
|
17
16
|
created for the model with the hash defining additional fallbacks. To set a
|
18
|
-
default value for this hash,
|
19
|
-
|
17
|
+
default value for this hash, pass this value to the plugin in your Mobility
|
18
|
+
configuration.
|
20
19
|
|
21
20
|
In addition, fallbacks are disabled in certain situations. To explicitly disable
|
22
21
|
fallbacks when reading and writing, you can pass the <tt>fallback: false</tt>
|
@@ -108,58 +107,67 @@ the current locale was +nil+.
|
|
108
107
|
#=> nil
|
109
108
|
post.title_fr
|
110
109
|
#=> nil
|
110
|
+
=end
|
111
|
+
module Fallbacks
|
112
|
+
extend Plugin
|
111
113
|
|
112
|
-
|
113
|
-
|
114
|
-
# ...
|
115
|
-
config.default_options[:fallbacks] = { :'fr' => 'en' }
|
116
|
-
# ...
|
117
|
-
end
|
118
|
-
|
119
|
-
class Post
|
120
|
-
# Post will fallback from French to English by default
|
121
|
-
translates :title, fallbacks: true
|
122
|
-
end
|
114
|
+
default true
|
115
|
+
requires :backend, include: :before
|
123
116
|
|
124
|
-
=end
|
125
|
-
class Fallbacks < Module
|
126
117
|
# Applies fallbacks plugin to attributes. Completely disables fallbacks
|
127
118
|
# on model if option is +false+.
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
attributes.backend_class.include(new(option)) unless option == false
|
119
|
+
included_hook do |_, backend_class|
|
120
|
+
fallbacks = options[:fallbacks]
|
121
|
+
backend_class.include(BackendReader.new(fallbacks, method(:generate_fallbacks))) unless fallbacks == false
|
132
122
|
end
|
133
123
|
|
134
|
-
|
135
|
-
|
124
|
+
private
|
125
|
+
|
126
|
+
def generate_fallbacks(fallbacks)
|
127
|
+
fallbacks_class = I18n.respond_to?(:fallbacks) ? I18nFallbacks : I18n::Locale::Fallbacks
|
128
|
+
fallbacks_class.new(fallbacks)
|
136
129
|
end
|
137
130
|
|
138
|
-
|
131
|
+
class I18nFallbacks < ::I18n::Locale::Fallbacks
|
132
|
+
def [](locale)
|
133
|
+
super | I18n.fallbacks[locale]
|
134
|
+
end
|
135
|
+
end
|
139
136
|
|
140
|
-
|
141
|
-
|
142
|
-
|
137
|
+
class BackendReader < Module
|
138
|
+
def initialize(fallbacks_option, fallbacks_generator)
|
139
|
+
@fallbacks_generator = fallbacks_generator
|
140
|
+
define_read(convert_option_to_fallbacks(fallbacks_option))
|
141
|
+
end
|
143
142
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
143
|
+
private
|
144
|
+
|
145
|
+
def define_read(fallbacks)
|
146
|
+
define_method :read do |locale, fallback: true, **options|
|
147
|
+
return super(locale, options) if !fallback || options[:locale]
|
149
148
|
|
150
|
-
|
149
|
+
locales = fallback == true ? fallbacks[locale] : [locale, *fallback]
|
150
|
+
locales.each do |fallback_locale|
|
151
|
+
value = super(fallback_locale, options)
|
152
|
+
return value if Util.present?(value)
|
153
|
+
end
|
154
|
+
|
155
|
+
super(locale, options)
|
156
|
+
end
|
151
157
|
end
|
152
|
-
end
|
153
158
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
159
|
+
def convert_option_to_fallbacks(option)
|
160
|
+
if option.is_a?(::Hash)
|
161
|
+
@fallbacks_generator[option]
|
162
|
+
elsif option == true
|
163
|
+
@fallbacks_generator[{}]
|
164
|
+
else
|
165
|
+
::Hash.new { [] }
|
166
|
+
end
|
161
167
|
end
|
162
168
|
end
|
163
169
|
end
|
170
|
+
|
171
|
+
register_plugin(:fallbacks, Fallbacks)
|
164
172
|
end
|
165
173
|
end
|
@@ -18,42 +18,41 @@ This is a less efficient (but more open-ended) implementation of locale
|
|
18
18
|
accessors, for use in cases where the locales to be used are not known when the
|
19
19
|
model class is generated.
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
"title in #{Mobility.locale}"
|
25
|
-
end
|
26
|
-
include Mobility::FallthroughAccessors.new("title")
|
27
|
-
end
|
21
|
+
=end
|
22
|
+
module FallthroughAccessors
|
23
|
+
extend Plugin
|
28
24
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
post.title_fr
|
34
|
-
#=> "title in fr"
|
25
|
+
default true
|
26
|
+
|
27
|
+
requires :reader
|
28
|
+
requires :writer
|
35
29
|
|
36
|
-
=end
|
37
|
-
class FallthroughAccessors < Module
|
38
30
|
# Apply fallthrough accessors plugin to attributes.
|
39
|
-
# @param [
|
31
|
+
# @param [Translations] translations
|
40
32
|
# @param [Boolean] option
|
41
|
-
|
42
|
-
|
33
|
+
initialize_hook do
|
34
|
+
if options[:fallthrough_accessors] && options[:reader] && options[:writer]
|
35
|
+
define_fallthrough_accessors(names)
|
36
|
+
end
|
43
37
|
end
|
44
38
|
|
45
|
-
|
46
|
-
def initialize(*attributes)
|
47
|
-
method_name_regex = /\A(#{attributes.join('|')})_([a-z]{2}(_[a-z]{2})?)(=?|\??)\z/.freeze
|
39
|
+
private
|
48
40
|
|
49
|
-
|
41
|
+
def define_fallthrough_accessors(*names)
|
42
|
+
method_name_regex = /\A(#{names.join('|')})_([a-z]{2}(_[a-z]{2})?)(=?|\??)\z/.freeze
|
43
|
+
|
44
|
+
define_method :method_missing do |method_name, *args, &block|
|
50
45
|
if method_name =~ method_name_regex
|
51
|
-
|
46
|
+
attribute_method = "#{$1}#{$4}"
|
52
47
|
locale, suffix = $2.split('_')
|
53
48
|
locale = "#{locale}-#{suffix.upcase}" if suffix
|
54
|
-
|
49
|
+
if $4 == '=' # writer
|
50
|
+
public_send(attribute_method, args[0], **(args[1] || {}), locale: locale)
|
51
|
+
else # reader
|
52
|
+
public_send(attribute_method, **(args[0] || {}), locale: locale)
|
53
|
+
end
|
55
54
|
else
|
56
|
-
super(method_name, *
|
55
|
+
super(method_name, *args, &block)
|
57
56
|
end
|
58
57
|
end
|
59
58
|
|
@@ -62,5 +61,7 @@ model class is generated.
|
|
62
61
|
end
|
63
62
|
end
|
64
63
|
end
|
64
|
+
|
65
|
+
register_plugin :fallthrough_accessors, FallthroughAccessors
|
65
66
|
end
|
66
67
|
end
|
@@ -13,47 +13,33 @@ If no locales are passed as an option to the initializer,
|
|
13
13
|
+Mobility.available_locales+ (i.e. +I18n.available_locales+, or Rails-set
|
14
14
|
available locales for a Rails application) will be used by default.
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
"title in #{Mobility.locale}"
|
20
|
-
end
|
21
|
-
include Mobility::Plugins::LocaleAccessors.new("title", locales: [:en, :fr])
|
22
|
-
end
|
16
|
+
=end
|
17
|
+
module LocaleAccessors
|
18
|
+
extend Plugin
|
23
19
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
post.title_fr
|
29
|
-
#=> "title in fr"
|
20
|
+
default true
|
21
|
+
|
22
|
+
requires :reader
|
23
|
+
requires :writer
|
30
24
|
|
31
|
-
=end
|
32
|
-
class LocaleAccessors < Module
|
33
25
|
# Apply locale accessors plugin to attributes.
|
34
|
-
# @param [
|
26
|
+
# @param [Translations] translations
|
35
27
|
# @param [Boolean] option
|
36
|
-
|
37
|
-
if
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
# @param [Array<Symbol>] Locales
|
45
|
-
def initialize(*attribute_names, locales:)
|
46
|
-
attribute_names.each do |name|
|
47
|
-
locales.each do |locale|
|
48
|
-
define_reader(name, locale)
|
49
|
-
define_writer(name, locale)
|
28
|
+
initialize_hook do |*names|
|
29
|
+
if locales = options[:locale_accessors]
|
30
|
+
locales = Mobility.available_locales if locales == true
|
31
|
+
names.each do |name|
|
32
|
+
locales.each do |locale|
|
33
|
+
define_locale_reader(name, locale) if options[:reader]
|
34
|
+
define_locale_writer(name, locale) if options[:writer]
|
35
|
+
end
|
50
36
|
end
|
51
37
|
end
|
52
38
|
end
|
53
39
|
|
54
40
|
private
|
55
41
|
|
56
|
-
def
|
42
|
+
def define_locale_reader(name, locale)
|
57
43
|
warning_message = "locale passed as option to locale accessor will be ignored"
|
58
44
|
normalized_locale = Mobility.normalize_locale(locale)
|
59
45
|
|
@@ -61,18 +47,20 @@ available locales for a Rails application) will be used by default.
|
|
61
47
|
def #{name}_#{normalized_locale}(options = {})
|
62
48
|
return super() if options.delete(:super)
|
63
49
|
warn "#{warning_message}" if options[:locale]
|
64
|
-
#{name}(**options, locale:
|
50
|
+
#{name}(**options, locale: #{locale.inspect})
|
65
51
|
end
|
52
|
+
EOM
|
66
53
|
|
54
|
+
module_eval <<-EOM, __FILE__, __LINE__ + 1
|
67
55
|
def #{name}_#{normalized_locale}?(options = {})
|
68
56
|
return super() if options.delete(:super)
|
69
57
|
warn "#{warning_message}" if options[:locale]
|
70
|
-
#{name}?(**options, locale:
|
58
|
+
#{name}?(**options, locale: #{locale.inspect})
|
71
59
|
end
|
72
60
|
EOM
|
73
61
|
end
|
74
62
|
|
75
|
-
def
|
63
|
+
def define_locale_writer(name, locale)
|
76
64
|
warning_message = "locale passed as option to locale accessor will be ignored"
|
77
65
|
normalized_locale = Mobility.normalize_locale(locale)
|
78
66
|
|
@@ -80,10 +68,12 @@ available locales for a Rails application) will be used by default.
|
|
80
68
|
def #{name}_#{normalized_locale}=(value, options = {})
|
81
69
|
return super(value) if options.delete(:super)
|
82
70
|
warn "#{warning_message}" if options[:locale]
|
83
|
-
public_send(:#{name}=, value, **options, locale:
|
71
|
+
public_send(:#{name}=, value, **options, locale: #{locale.inspect})
|
84
72
|
end
|
85
73
|
EOM
|
86
74
|
end
|
87
75
|
end
|
76
|
+
|
77
|
+
register_plugin(:locale_accessors, LocaleAccessors)
|
88
78
|
end
|
89
79
|
end
|
@@ -6,43 +6,50 @@ module Mobility
|
|
6
6
|
=begin
|
7
7
|
|
8
8
|
Applies presence filter to values fetched from backend and to values set on
|
9
|
-
backend.
|
9
|
+
backend.
|
10
10
|
|
11
11
|
@note For performance reasons, the presence plugin filters only for empty
|
12
12
|
strings, not other values continued "blank" like empty arrays.
|
13
13
|
|
14
14
|
=end
|
15
15
|
module Presence
|
16
|
+
extend Plugin
|
17
|
+
|
18
|
+
default true
|
19
|
+
requires :backend, include: :before
|
20
|
+
|
16
21
|
# Applies presence plugin to attributes.
|
17
|
-
|
18
|
-
|
19
|
-
def self.apply(attributes, option)
|
20
|
-
attributes.backend_class.include(self) if option
|
22
|
+
included_hook do |_, backend_class|
|
23
|
+
backend_class.include(BackendMethods) if options[:presence]
|
21
24
|
end
|
22
25
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
options
|
29
|
-
|
26
|
+
module BackendMethods
|
27
|
+
# @!group Backend Accessors
|
28
|
+
# @!macro backend_reader
|
29
|
+
# @option options [Boolean] presence
|
30
|
+
# *false* to disable presence filter.
|
31
|
+
def read(locale, **options)
|
32
|
+
options.delete(:presence) == false ? super : Presence[super]
|
33
|
+
end
|
30
34
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
35
|
+
# @!macro backend_writer
|
36
|
+
# @option options [Boolean] presence
|
37
|
+
# *false* to disable presence filter.
|
38
|
+
def write(locale, value, **options)
|
39
|
+
if options.delete(:presence) == false
|
40
|
+
super
|
41
|
+
else
|
42
|
+
super(locale, Presence[value], options)
|
43
|
+
end
|
39
44
|
end
|
45
|
+
# @!endgroup
|
40
46
|
end
|
41
|
-
# @!endgroup
|
42
47
|
|
43
48
|
def self.[](value)
|
44
49
|
(value == "") ? nil : value
|
45
50
|
end
|
46
51
|
end
|
52
|
+
|
53
|
+
register_plugin(:presence, Presence)
|
47
54
|
end
|
48
55
|
end
|
@@ -3,29 +3,20 @@ module Mobility
|
|
3
3
|
module Plugins
|
4
4
|
=begin
|
5
5
|
|
6
|
-
@see {Mobility::Plugins::ActiveRecord::Query}
|
6
|
+
@see {Mobility::Plugins::ActiveRecord::Query} or {Mobility::Plugins::Sequel::Query}.
|
7
7
|
|
8
8
|
=end
|
9
9
|
module Query
|
10
|
-
|
11
|
-
def apply(attributes, option)
|
12
|
-
if option
|
13
|
-
include_query_module(attributes)
|
14
|
-
end
|
15
|
-
end
|
10
|
+
extend Plugin
|
16
11
|
|
17
|
-
|
12
|
+
default :i18n
|
13
|
+
requires :backend, include: :before
|
18
14
|
|
19
|
-
|
20
|
-
|
21
|
-
require "mobility/plugins/active_record/query"
|
22
|
-
ActiveRecord::Query.apply(attributes)
|
23
|
-
elsif Loaded::Sequel && attributes.model_class < ::Sequel::Model
|
24
|
-
require "mobility/plugins/sequel/query"
|
25
|
-
Sequel::Query.apply(attributes)
|
26
|
-
end
|
27
|
-
end
|
15
|
+
def query_method
|
16
|
+
(options[:query] == true) ? self.class.defaults[:query] : options[:query]
|
28
17
|
end
|
29
18
|
end
|
19
|
+
|
20
|
+
register_plugin(:query, Query)
|
30
21
|
end
|
31
22
|
end
|