mobility 0.0.1 → 0.1.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/Gemfile +19 -0
- data/Gemfile.lock +153 -0
- data/Guardfile +70 -0
- data/README.md +603 -13
- data/Rakefile +42 -0
- data/lib/generators/mobility/install_generator.rb +45 -0
- data/lib/generators/mobility/templates/create_string_translations.rb +15 -0
- data/lib/generators/mobility/templates/create_text_translations.rb +15 -0
- data/lib/mobility.rb +203 -2
- data/lib/mobility/active_model.rb +6 -0
- data/lib/mobility/active_model/attribute_methods.rb +27 -0
- data/lib/mobility/active_model/backend_resetter.rb +26 -0
- data/lib/mobility/active_record.rb +39 -0
- data/lib/mobility/active_record/backend_resetter.rb +26 -0
- data/lib/mobility/active_record/model_translation.rb +14 -0
- data/lib/mobility/active_record/string_translation.rb +7 -0
- data/lib/mobility/active_record/text_translation.rb +7 -0
- data/lib/mobility/active_record/translation.rb +14 -0
- data/lib/mobility/attributes.rb +210 -0
- data/lib/mobility/backend.rb +152 -0
- data/lib/mobility/backend/active_model.rb +7 -0
- data/lib/mobility/backend/active_model/dirty.rb +84 -0
- data/lib/mobility/backend/active_record.rb +13 -0
- data/lib/mobility/backend/active_record/column.rb +52 -0
- data/lib/mobility/backend/active_record/column/query_methods.rb +40 -0
- data/lib/mobility/backend/active_record/hash_valued.rb +58 -0
- data/lib/mobility/backend/active_record/hstore.rb +36 -0
- data/lib/mobility/backend/active_record/hstore/query_methods.rb +53 -0
- data/lib/mobility/backend/active_record/jsonb.rb +43 -0
- data/lib/mobility/backend/active_record/jsonb/query_methods.rb +53 -0
- data/lib/mobility/backend/active_record/key_value.rb +126 -0
- data/lib/mobility/backend/active_record/key_value/query_methods.rb +63 -0
- data/lib/mobility/backend/active_record/query_methods.rb +36 -0
- data/lib/mobility/backend/active_record/serialized.rb +93 -0
- data/lib/mobility/backend/active_record/serialized/query_methods.rb +32 -0
- data/lib/mobility/backend/active_record/table.rb +197 -0
- data/lib/mobility/backend/active_record/table/query_methods.rb +91 -0
- data/lib/mobility/backend/cache.rb +110 -0
- data/lib/mobility/backend/column.rb +52 -0
- data/lib/mobility/backend/dirty.rb +28 -0
- data/lib/mobility/backend/fallbacks.rb +89 -0
- data/lib/mobility/backend/hstore.rb +21 -0
- data/lib/mobility/backend/jsonb.rb +21 -0
- data/lib/mobility/backend/key_value.rb +71 -0
- data/lib/mobility/backend/null.rb +24 -0
- data/lib/mobility/backend/orm_delegator.rb +33 -0
- data/lib/mobility/backend/sequel.rb +14 -0
- data/lib/mobility/backend/sequel/column.rb +40 -0
- data/lib/mobility/backend/sequel/column/query_methods.rb +24 -0
- data/lib/mobility/backend/sequel/dirty.rb +54 -0
- data/lib/mobility/backend/sequel/hash_valued.rb +51 -0
- data/lib/mobility/backend/sequel/hstore.rb +36 -0
- data/lib/mobility/backend/sequel/hstore/query_methods.rb +42 -0
- data/lib/mobility/backend/sequel/jsonb.rb +43 -0
- data/lib/mobility/backend/sequel/jsonb/query_methods.rb +42 -0
- data/lib/mobility/backend/sequel/key_value.rb +139 -0
- data/lib/mobility/backend/sequel/key_value/query_methods.rb +48 -0
- data/lib/mobility/backend/sequel/query_methods.rb +22 -0
- data/lib/mobility/backend/sequel/serialized.rb +133 -0
- data/lib/mobility/backend/sequel/serialized/query_methods.rb +20 -0
- data/lib/mobility/backend/sequel/table.rb +149 -0
- data/lib/mobility/backend/sequel/table/query_methods.rb +48 -0
- data/lib/mobility/backend/serialized.rb +53 -0
- data/lib/mobility/backend/table.rb +93 -0
- data/lib/mobility/backend_resetter.rb +44 -0
- data/lib/mobility/configuration.rb +31 -0
- data/lib/mobility/core_ext/nil.rb +10 -0
- data/lib/mobility/core_ext/object.rb +19 -0
- data/lib/mobility/core_ext/string.rb +16 -0
- data/lib/mobility/instance_methods.rb +34 -0
- data/lib/mobility/orm.rb +4 -0
- data/lib/mobility/sequel.rb +26 -0
- data/lib/mobility/sequel/backend_resetter.rb +26 -0
- data/lib/mobility/sequel/column_changes.rb +29 -0
- data/lib/mobility/sequel/model_translation.rb +20 -0
- data/lib/mobility/sequel/string_translation.rb +7 -0
- data/lib/mobility/sequel/text_translation.rb +7 -0
- data/lib/mobility/sequel/translation.rb +53 -0
- data/lib/mobility/translates.rb +75 -0
- data/lib/mobility/wrapper.rb +31 -0
- metadata +152 -12
- data/.gitignore +0 -9
- data/.rspec +0 -2
- data/.travis.yml +0 -5
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/mobility.gemspec +0 -32
@@ -0,0 +1,48 @@
|
|
1
|
+
module Mobility
|
2
|
+
module Backend
|
3
|
+
class Sequel::KeyValue::QueryMethods < Sequel::QueryMethods
|
4
|
+
def initialize(attributes, **options)
|
5
|
+
super
|
6
|
+
attributes_extractor = @attributes_extractor
|
7
|
+
association_name, translations_class = options[:association_name], options[:class_name]
|
8
|
+
|
9
|
+
define_method :"join_#{association_name}" do |*attributes, **options|
|
10
|
+
attributes.inject(self) do |relation, attribute|
|
11
|
+
join_type = options[:outer_join] ? :left_outer : :inner
|
12
|
+
relation.join_table(join_type,
|
13
|
+
translations_class.table_name,
|
14
|
+
{
|
15
|
+
key: attribute.to_s,
|
16
|
+
locale: Mobility.locale.to_s,
|
17
|
+
translatable_type: model.name,
|
18
|
+
translatable_id: ::Sequel[:"#{model.table_name}"][:id]
|
19
|
+
},
|
20
|
+
table_alias: "#{attribute}_#{association_name}")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# TODO: find a better way to do this that doesn't involve overriding
|
25
|
+
# a private method...
|
26
|
+
define_method :_filter_or_exclude do |invert, clause, *cond, &block|
|
27
|
+
if i18n_keys = attributes_extractor.call(cond.first)
|
28
|
+
cond = cond.first.dup
|
29
|
+
i18n_nulls = i18n_keys.select { |key| cond[key].nil? }
|
30
|
+
i18n_keys.each { |attr| cond[::Sequel[:"#{attr}_#{association_name}"][:value]] = cond.delete(attr) }
|
31
|
+
super(invert, clause, cond, &block).
|
32
|
+
send("join_#{association_name}", *(i18n_keys - i18n_nulls)).
|
33
|
+
send("join_#{association_name}", *i18n_nulls, outer_join: true)
|
34
|
+
else
|
35
|
+
super(invert, clause, *cond, &block)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
private :_filter_or_exclude
|
39
|
+
|
40
|
+
attributes.each do |attribute|
|
41
|
+
define_method :"first_by_#{attribute}" do |value|
|
42
|
+
where(attribute => value).select_all(model.table_name).first
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Mobility
|
2
|
+
module Backend
|
3
|
+
module Sequel
|
4
|
+
=begin
|
5
|
+
|
6
|
+
Defines query method overrides to handle translated attributes for Sequel
|
7
|
+
models. For details see backend-specific subclasses.
|
8
|
+
|
9
|
+
=end
|
10
|
+
class QueryMethods < Module
|
11
|
+
# @param [Array<String>] attributes Translated attributes
|
12
|
+
# @param [Hash] options Backend options
|
13
|
+
def initialize(attributes, **options)
|
14
|
+
@attributes = attributes.map! &:to_sym
|
15
|
+
@attributes_extractor = lambda do |cond|
|
16
|
+
cond.is_a?(Hash) && (cond.keys & attributes).presence
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
module Mobility
|
2
|
+
module Backend
|
3
|
+
=begin
|
4
|
+
|
5
|
+
Implements {Mobility::Backend::Serialized} backend for Sequel models, using the
|
6
|
+
Sequel serialization plugin.
|
7
|
+
|
8
|
+
@see http://sequel.jeremyevans.net/rdoc-plugins/classes/Sequel/Plugins/Serialization.html Sequel serialization plugin
|
9
|
+
@see http://sequel.jeremyevans.net/rdoc-plugins/classes/Sequel/Plugins/SerializationModificationDetection.html Sequel serialization_modification_detection plugin
|
10
|
+
|
11
|
+
@example Define attribute with serialized backend
|
12
|
+
class Post < Sequel::Model
|
13
|
+
include Mobility
|
14
|
+
translates :title, backend: :serialized, format: :yaml
|
15
|
+
end
|
16
|
+
|
17
|
+
@example Read and write attribute translations
|
18
|
+
post = Post.create(title: "foo")
|
19
|
+
post.title
|
20
|
+
#=> "foo"
|
21
|
+
Mobility.locale = :ja
|
22
|
+
post.title = "あああ"
|
23
|
+
post.save
|
24
|
+
post.deserialized_values[:title] # get deserialized value
|
25
|
+
#=> {:en=>"foo", :ja=>"あああ"}
|
26
|
+
post.title_before_mobility # get serialized value
|
27
|
+
#=> "---\n:en: foo\n:ja: \"あああ\"\n"
|
28
|
+
|
29
|
+
=end
|
30
|
+
class Sequel::Serialized
|
31
|
+
include Backend
|
32
|
+
|
33
|
+
autoload :QueryMethods, 'mobility/backend/sequel/serialized/query_methods'
|
34
|
+
|
35
|
+
# @!group Backend Accessors
|
36
|
+
#
|
37
|
+
# @!macro backend_reader
|
38
|
+
def read(locale, **options)
|
39
|
+
translations[locale]
|
40
|
+
end
|
41
|
+
|
42
|
+
# @!macro backend_reader
|
43
|
+
def write(locale, value, **options)
|
44
|
+
translations[locale] = value
|
45
|
+
end
|
46
|
+
# @!endgroup
|
47
|
+
|
48
|
+
# @!group Backend Configuration
|
49
|
+
# @option options [Symbol] format (:yaml) Serialization format
|
50
|
+
# @raise [ArgumentError] if a format other than +:yaml+ or +:json+ is passed in
|
51
|
+
def self.configure!(options)
|
52
|
+
options[:format] ||= :yaml
|
53
|
+
options[:format] = options[:format].downcase.to_sym
|
54
|
+
raise ArgumentError, "Serialized backend only supports yaml or json formats." unless [:yaml, :json].include?(options[:format])
|
55
|
+
end
|
56
|
+
# @!endgroup
|
57
|
+
|
58
|
+
setup do |attributes, options|
|
59
|
+
format = options[:format]
|
60
|
+
plugin :serialization
|
61
|
+
plugin :serialization_modification_detection
|
62
|
+
|
63
|
+
attributes.each do |_attribute|
|
64
|
+
attribute = _attribute.to_sym
|
65
|
+
self.serialization_map[attribute] = Serialized.serializer_for(format)
|
66
|
+
self.deserialization_map[attribute] = Serialized.deserializer_for(format)
|
67
|
+
end
|
68
|
+
|
69
|
+
method_overrides = Module.new do
|
70
|
+
define_method :initialize_set do |values|
|
71
|
+
attributes.each { |attribute| send(:"#{attribute}_before_mobility=", {}.send(:"to_#{format}")) }
|
72
|
+
super(values)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
include method_overrides
|
76
|
+
|
77
|
+
extension = Module.new do
|
78
|
+
define_method :i18n do
|
79
|
+
@mobility_scope ||= super().with_extend(QueryMethods.new(attributes, options))
|
80
|
+
end
|
81
|
+
end
|
82
|
+
extend extension
|
83
|
+
|
84
|
+
include SerializationModificationDetectionFix
|
85
|
+
end
|
86
|
+
|
87
|
+
# Returns deserialized column value
|
88
|
+
# @return [Hash]
|
89
|
+
def translations
|
90
|
+
_attribute = attribute.to_sym
|
91
|
+
if model.deserialized_values.has_key?(_attribute)
|
92
|
+
model.deserialized_values[_attribute]
|
93
|
+
elsif model.frozen?
|
94
|
+
deserialize_value(_attribute, serialized_value)
|
95
|
+
else
|
96
|
+
model.deserialized_values[_attribute] = deserialize_value(_attribute, serialized_value)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# @!group Cache Methods
|
101
|
+
# @return [Hash]
|
102
|
+
def new_cache
|
103
|
+
translations
|
104
|
+
end
|
105
|
+
|
106
|
+
# @return [Boolean]
|
107
|
+
def write_to_cache?
|
108
|
+
true
|
109
|
+
end
|
110
|
+
# @!endgroup
|
111
|
+
|
112
|
+
# @note The original serialization_modification_detection plugin sets
|
113
|
+
# +@original_deserialized_values+ to be +@deserialized_values+, which
|
114
|
+
# doesn't work. Setting it to a new empty hash seems to work better.
|
115
|
+
module SerializationModificationDetectionFix
|
116
|
+
def after_save
|
117
|
+
super()
|
118
|
+
@original_deserialized_values = {}
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
def deserialize_value(column, value)
|
125
|
+
model.send(:deserialize_value, column, value)
|
126
|
+
end
|
127
|
+
|
128
|
+
def serialized_value
|
129
|
+
model.send("#{attribute}_before_mobility")
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Mobility
|
2
|
+
module Backend
|
3
|
+
class Sequel::Serialized::QueryMethods < Sequel::QueryMethods
|
4
|
+
def initialize(attributes, **options)
|
5
|
+
super
|
6
|
+
attributes_extractor = @attributes_extractor
|
7
|
+
cond_checker = @cond_checker = lambda do |cond|
|
8
|
+
if i18n_keys = attributes_extractor.call(cond)
|
9
|
+
raise ArgumentError,
|
10
|
+
"You cannot query on mobility attributes translated with the Serialized backend (#{i18n_keys.join(", ")})."
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
define_method :where do |*cond, &block|
|
15
|
+
cond_checker.call(cond.first) || super(*cond, &block)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
module Mobility
|
2
|
+
module Backend
|
3
|
+
=begin
|
4
|
+
|
5
|
+
Implements the {Mobility::Backend::Table} backend for Sequel models.
|
6
|
+
|
7
|
+
=end
|
8
|
+
class Sequel::Table
|
9
|
+
include Backend
|
10
|
+
|
11
|
+
autoload :QueryMethods, 'mobility/backend/sequel/table/query_methods'
|
12
|
+
|
13
|
+
# @return [Symbol] name of the association method
|
14
|
+
attr_reader :association_name
|
15
|
+
|
16
|
+
# @!macro backend_constructor
|
17
|
+
# @option options [Symbol] association_name Name of association
|
18
|
+
def initialize(model, attribute, **options)
|
19
|
+
super
|
20
|
+
@association_name = options[:association_name]
|
21
|
+
end
|
22
|
+
|
23
|
+
# @!group Backend Accessors
|
24
|
+
# @!macro backend_reader
|
25
|
+
def read(locale, **options)
|
26
|
+
translation_for(locale).send(attribute)
|
27
|
+
end
|
28
|
+
|
29
|
+
# @!macro backend_reader
|
30
|
+
def write(locale, value, **options)
|
31
|
+
translation_for(locale).tap { |t| t.send("#{attribute}=", value) }.send(attribute)
|
32
|
+
end
|
33
|
+
|
34
|
+
# @!group Backend Configuration
|
35
|
+
# @option options [Symbol] association_name (:mobility_model_translations) Name of association method
|
36
|
+
# @option options [Symbol] table_name Name of translation table
|
37
|
+
# @option options [Symbol] foreign_key Name of foreign key
|
38
|
+
# @option options [Symbol] subclass_name Name of subclass to append to model class to generate translation class
|
39
|
+
# @raise [CacheRequired] if cache option is false
|
40
|
+
def self.configure!(options)
|
41
|
+
raise CacheRequired, "Cache required for Sequel::Table backend" if options[:cache] == false
|
42
|
+
table_name = options[:model_class].table_name
|
43
|
+
options[:table_name] ||= :"#{table_name.to_s.singularize}_translations"
|
44
|
+
options[:foreign_key] ||= table_name.to_s.downcase.singularize.camelize.foreign_key
|
45
|
+
if (association_name = options[:association_name]).present?
|
46
|
+
options[:subclass_name] ||= association_name.to_s.singularize.camelize
|
47
|
+
else
|
48
|
+
options[:association_name] = :mobility_model_translations
|
49
|
+
options[:subclass_name] ||= :Translation
|
50
|
+
end
|
51
|
+
%i[table_name foreign_key association_name subclass_name].each { |key| options[key] = options[key].to_sym }
|
52
|
+
end
|
53
|
+
# @!endgroup
|
54
|
+
|
55
|
+
setup do |attributes, options|
|
56
|
+
association_name = options[:association_name]
|
57
|
+
subclass_name = options[:subclass_name]
|
58
|
+
|
59
|
+
cache_accessor_name = :"__#{association_name}_cache"
|
60
|
+
|
61
|
+
attr_accessor cache_accessor_name
|
62
|
+
|
63
|
+
translation_class =
|
64
|
+
if self.const_defined?(subclass_name, false)
|
65
|
+
const_get(subclass_name, false)
|
66
|
+
else
|
67
|
+
const_set(subclass_name, Class.new(::Sequel::Model(options[:table_name]))).tap do |klass|
|
68
|
+
klass.include ::Mobility::Sequel::ModelTranslation
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
one_to_many association_name,
|
73
|
+
class: translation_class.name,
|
74
|
+
key: options[:foreign_key],
|
75
|
+
reciprocal: :translated_model
|
76
|
+
|
77
|
+
translation_class.many_to_one :translated_model,
|
78
|
+
class: name,
|
79
|
+
key: options[:foreign_key],
|
80
|
+
reciprocal: association_name
|
81
|
+
|
82
|
+
plugin :association_dependencies, association_name => :destroy
|
83
|
+
|
84
|
+
callback_methods = Module.new do
|
85
|
+
define_method :after_save do
|
86
|
+
super()
|
87
|
+
send(cache_accessor_name).each_value do |translation|
|
88
|
+
translation.id ? translation.save : send("add_#{association_name.to_s.singularize}", translation)
|
89
|
+
end if send(cache_accessor_name)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
include callback_methods
|
93
|
+
|
94
|
+
extension = Module.new do
|
95
|
+
define_method :i18n do
|
96
|
+
@mobility_scope ||= super().with_extend(QueryMethods.new(attributes, options))
|
97
|
+
end
|
98
|
+
end
|
99
|
+
extend extension
|
100
|
+
|
101
|
+
include Mobility::Sequel::ColumnChanges.new(attributes)
|
102
|
+
end
|
103
|
+
|
104
|
+
# @!group Cache Methods
|
105
|
+
# @return [Table::TranslationsCache]
|
106
|
+
def new_cache
|
107
|
+
reset_model_cache unless model_cache
|
108
|
+
model_cache.for(attribute)
|
109
|
+
end
|
110
|
+
|
111
|
+
# @return [Boolean]
|
112
|
+
def write_to_cache?
|
113
|
+
true
|
114
|
+
end
|
115
|
+
|
116
|
+
def clear_cache
|
117
|
+
model_cache.clear if model_cache
|
118
|
+
end
|
119
|
+
# @!endgroup
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def translation_for(locale)
|
124
|
+
translation = translations.find { |t| t.locale == locale.to_s }
|
125
|
+
translation ||= translation_class.new(locale: locale)
|
126
|
+
translation
|
127
|
+
end
|
128
|
+
|
129
|
+
def translations
|
130
|
+
model.send(association_name)
|
131
|
+
end
|
132
|
+
|
133
|
+
def translation_class
|
134
|
+
@translation_class ||= options[:model_class].const_get(options[:subclass_name])
|
135
|
+
end
|
136
|
+
|
137
|
+
def model_cache
|
138
|
+
model.send(:"__#{association_name}_cache")
|
139
|
+
end
|
140
|
+
|
141
|
+
def reset_model_cache
|
142
|
+
model.send(:"__#{association_name}_cache=",
|
143
|
+
Table::TranslationsCache.new { |locale| translation_for(locale) })
|
144
|
+
end
|
145
|
+
|
146
|
+
class CacheRequired < ::StandardError; end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Mobility
|
2
|
+
module Backend
|
3
|
+
class Sequel::Table::QueryMethods < Sequel::QueryMethods
|
4
|
+
def initialize(attributes, **options)
|
5
|
+
super
|
6
|
+
association_name = options[:association_name]
|
7
|
+
@association_name = association_name
|
8
|
+
foreign_key = options[:foreign_key]
|
9
|
+
attributes_extractor = @attributes_extractor
|
10
|
+
translation_class = options[:model_class].const_get(options[:subclass_name])
|
11
|
+
@translation_class = translation_class
|
12
|
+
table_name = options[:table_name]
|
13
|
+
|
14
|
+
define_method :"join_#{association_name}" do |**options|
|
15
|
+
return self if (@__mobility_table_joined || []).include?(table_name)
|
16
|
+
(@__mobility_table_joined ||= []) << table_name
|
17
|
+
join_type = options[:outer_join] ? :left_outer : :inner
|
18
|
+
join_table(join_type,
|
19
|
+
translation_class.table_name,
|
20
|
+
{
|
21
|
+
locale: Mobility.locale.to_s,
|
22
|
+
foreign_key => ::Sequel[model.table_name][:id]
|
23
|
+
})
|
24
|
+
end
|
25
|
+
|
26
|
+
# See note in AR Table QueryMethods class about limitations of
|
27
|
+
# query methods on translated attributes when searching on nil values.
|
28
|
+
#
|
29
|
+
define_method :_filter_or_exclude do |invert, clause, *cond, &block|
|
30
|
+
if i18n_keys = attributes_extractor.call(cond.first)
|
31
|
+
cond = cond.first.dup
|
32
|
+
outer_join = i18n_keys.all? { |key| cond[key].nil? }
|
33
|
+
i18n_keys.each { |attr| cond[::Sequel[translation_class.table_name][attr]] = cond.delete(attr) }
|
34
|
+
super(invert, clause, cond, &block).send("join_#{association_name}", outer_join: outer_join)
|
35
|
+
else
|
36
|
+
super(invert, clause, *cond, &block)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
attributes.each do |attribute|
|
41
|
+
define_method :"first_by_#{attribute}" do |value|
|
42
|
+
where(attribute => value).select_all(model.table_name).first
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Mobility
|
2
|
+
module Backend
|
3
|
+
=begin
|
4
|
+
|
5
|
+
Stores translations as serialized attributes in a single text column. This
|
6
|
+
implies that the translated values are not searchable, and thus this backend is
|
7
|
+
not recommended unless specific constraints prevent use of other solutions.
|
8
|
+
|
9
|
+
To use this backend, ensure that the model table has a text column on its table
|
10
|
+
with the same name as the translated attribute.
|
11
|
+
|
12
|
+
==Backend Options
|
13
|
+
|
14
|
+
===+format+
|
15
|
+
|
16
|
+
Format for serialization. Either +:yaml+ (default) or +:json+.
|
17
|
+
|
18
|
+
@see Mobility::Backend::ActiveRecord::Serialized
|
19
|
+
@see Mobility::Backend::Sequel::Serialized
|
20
|
+
|
21
|
+
=end
|
22
|
+
module Serialized
|
23
|
+
include OrmDelegator
|
24
|
+
|
25
|
+
class << self
|
26
|
+
def serializer_for(format)
|
27
|
+
lambda do |obj|
|
28
|
+
return if obj.nil?
|
29
|
+
if obj.is_a? Hash
|
30
|
+
obj = obj.inject({}) do |translations, (locale, value)|
|
31
|
+
translations[locale] = value.to_s if value.present?
|
32
|
+
translations
|
33
|
+
end
|
34
|
+
else
|
35
|
+
raise ArgumentError, "Attribute is supposed to be a Hash, but was a #{obj.class}. -- #{obj.inspect}"
|
36
|
+
end
|
37
|
+
|
38
|
+
obj.send("to_#{format}")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def deserializer_for(format)
|
43
|
+
case format
|
44
|
+
when :yaml
|
45
|
+
lambda { |v| YAML.load(v) }
|
46
|
+
when :json
|
47
|
+
lambda { |v| JSON.parse(v, symbolize_names: true) }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|