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.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +19 -0
  3. data/Gemfile.lock +153 -0
  4. data/Guardfile +70 -0
  5. data/README.md +603 -13
  6. data/Rakefile +42 -0
  7. data/lib/generators/mobility/install_generator.rb +45 -0
  8. data/lib/generators/mobility/templates/create_string_translations.rb +15 -0
  9. data/lib/generators/mobility/templates/create_text_translations.rb +15 -0
  10. data/lib/mobility.rb +203 -2
  11. data/lib/mobility/active_model.rb +6 -0
  12. data/lib/mobility/active_model/attribute_methods.rb +27 -0
  13. data/lib/mobility/active_model/backend_resetter.rb +26 -0
  14. data/lib/mobility/active_record.rb +39 -0
  15. data/lib/mobility/active_record/backend_resetter.rb +26 -0
  16. data/lib/mobility/active_record/model_translation.rb +14 -0
  17. data/lib/mobility/active_record/string_translation.rb +7 -0
  18. data/lib/mobility/active_record/text_translation.rb +7 -0
  19. data/lib/mobility/active_record/translation.rb +14 -0
  20. data/lib/mobility/attributes.rb +210 -0
  21. data/lib/mobility/backend.rb +152 -0
  22. data/lib/mobility/backend/active_model.rb +7 -0
  23. data/lib/mobility/backend/active_model/dirty.rb +84 -0
  24. data/lib/mobility/backend/active_record.rb +13 -0
  25. data/lib/mobility/backend/active_record/column.rb +52 -0
  26. data/lib/mobility/backend/active_record/column/query_methods.rb +40 -0
  27. data/lib/mobility/backend/active_record/hash_valued.rb +58 -0
  28. data/lib/mobility/backend/active_record/hstore.rb +36 -0
  29. data/lib/mobility/backend/active_record/hstore/query_methods.rb +53 -0
  30. data/lib/mobility/backend/active_record/jsonb.rb +43 -0
  31. data/lib/mobility/backend/active_record/jsonb/query_methods.rb +53 -0
  32. data/lib/mobility/backend/active_record/key_value.rb +126 -0
  33. data/lib/mobility/backend/active_record/key_value/query_methods.rb +63 -0
  34. data/lib/mobility/backend/active_record/query_methods.rb +36 -0
  35. data/lib/mobility/backend/active_record/serialized.rb +93 -0
  36. data/lib/mobility/backend/active_record/serialized/query_methods.rb +32 -0
  37. data/lib/mobility/backend/active_record/table.rb +197 -0
  38. data/lib/mobility/backend/active_record/table/query_methods.rb +91 -0
  39. data/lib/mobility/backend/cache.rb +110 -0
  40. data/lib/mobility/backend/column.rb +52 -0
  41. data/lib/mobility/backend/dirty.rb +28 -0
  42. data/lib/mobility/backend/fallbacks.rb +89 -0
  43. data/lib/mobility/backend/hstore.rb +21 -0
  44. data/lib/mobility/backend/jsonb.rb +21 -0
  45. data/lib/mobility/backend/key_value.rb +71 -0
  46. data/lib/mobility/backend/null.rb +24 -0
  47. data/lib/mobility/backend/orm_delegator.rb +33 -0
  48. data/lib/mobility/backend/sequel.rb +14 -0
  49. data/lib/mobility/backend/sequel/column.rb +40 -0
  50. data/lib/mobility/backend/sequel/column/query_methods.rb +24 -0
  51. data/lib/mobility/backend/sequel/dirty.rb +54 -0
  52. data/lib/mobility/backend/sequel/hash_valued.rb +51 -0
  53. data/lib/mobility/backend/sequel/hstore.rb +36 -0
  54. data/lib/mobility/backend/sequel/hstore/query_methods.rb +42 -0
  55. data/lib/mobility/backend/sequel/jsonb.rb +43 -0
  56. data/lib/mobility/backend/sequel/jsonb/query_methods.rb +42 -0
  57. data/lib/mobility/backend/sequel/key_value.rb +139 -0
  58. data/lib/mobility/backend/sequel/key_value/query_methods.rb +48 -0
  59. data/lib/mobility/backend/sequel/query_methods.rb +22 -0
  60. data/lib/mobility/backend/sequel/serialized.rb +133 -0
  61. data/lib/mobility/backend/sequel/serialized/query_methods.rb +20 -0
  62. data/lib/mobility/backend/sequel/table.rb +149 -0
  63. data/lib/mobility/backend/sequel/table/query_methods.rb +48 -0
  64. data/lib/mobility/backend/serialized.rb +53 -0
  65. data/lib/mobility/backend/table.rb +93 -0
  66. data/lib/mobility/backend_resetter.rb +44 -0
  67. data/lib/mobility/configuration.rb +31 -0
  68. data/lib/mobility/core_ext/nil.rb +10 -0
  69. data/lib/mobility/core_ext/object.rb +19 -0
  70. data/lib/mobility/core_ext/string.rb +16 -0
  71. data/lib/mobility/instance_methods.rb +34 -0
  72. data/lib/mobility/orm.rb +4 -0
  73. data/lib/mobility/sequel.rb +26 -0
  74. data/lib/mobility/sequel/backend_resetter.rb +26 -0
  75. data/lib/mobility/sequel/column_changes.rb +29 -0
  76. data/lib/mobility/sequel/model_translation.rb +20 -0
  77. data/lib/mobility/sequel/string_translation.rb +7 -0
  78. data/lib/mobility/sequel/text_translation.rb +7 -0
  79. data/lib/mobility/sequel/translation.rb +53 -0
  80. data/lib/mobility/translates.rb +75 -0
  81. data/lib/mobility/wrapper.rb +31 -0
  82. metadata +152 -12
  83. data/.gitignore +0 -9
  84. data/.rspec +0 -2
  85. data/.travis.yml +0 -5
  86. data/bin/console +0 -14
  87. data/bin/setup +0 -8
  88. data/mobility.gemspec +0 -32
data/Rakefile CHANGED
@@ -1,6 +1,48 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rspec/core/rake_task"
3
+ require "yaml"
3
4
 
4
5
  RSpec::Core::RakeTask.new(:spec)
5
6
 
6
7
  task :default => :spec
8
+
9
+ task :load_path do
10
+ %w(lib spec).each do |path|
11
+ $LOAD_PATH.unshift(File.expand_path("../#{path}", __FILE__))
12
+ end
13
+ end
14
+
15
+ namespace :db do
16
+ desc "Create the database"
17
+ task :create => :load_path do
18
+ require "database"
19
+ driver = Mobility::Test::Database.driver
20
+ config = Mobility::Test::Database.config[driver]
21
+ commands = {
22
+ "mysql" => "mysql -u #{config['username']} -e 'create database #{config["database"]};' >/dev/null",
23
+ "postgres" => "psql -c 'create database #{config['database']};' -U #{config['username']} >/dev/null"
24
+ }
25
+ %x{#{commands[driver] || true}}
26
+ end
27
+
28
+ desc "Drop the database"
29
+ task :drop => :load_path do
30
+ require "database"
31
+ driver = Mobility::Test::Database.driver
32
+ config = Mobility::Test::Database.config[driver]
33
+ commands = {
34
+ "mysql" => "mysql -u #{config['username']} -e 'drop database #{config["database"]};' >/dev/null",
35
+ "postgres" => "psql -c 'drop database #{config['database']};' -U #{config['username']} >/dev/null"
36
+ }
37
+ %x{#{commands[driver] || true}}
38
+ end
39
+
40
+ desc "Set up the database schema"
41
+ task :up => :load_path do
42
+ require "spec_helper"
43
+ Mobility::Test::Schema.up
44
+ end
45
+
46
+ desc "Drop and recreate the database schema"
47
+ task :reset => [:drop, :create]
48
+ end
@@ -0,0 +1,45 @@
1
+ require "rails/generators"
2
+ require "rails/generators/active_record"
3
+
4
+ module Mobility
5
+ class InstallGenerator < ::Rails::Generators::Base
6
+ include ::Rails::Generators::Migration
7
+
8
+ desc "Generates migrations to add translations tables."
9
+
10
+ source_root File.expand_path("../templates", __FILE__)
11
+ class_option(
12
+ :without_tables,
13
+ type: :boolean,
14
+ default: false,
15
+ desc: "Skip creating translations tables."
16
+ )
17
+
18
+ def create_migration_file
19
+ add_mobility_migration("create_text_translations") unless options.without_tables?
20
+ add_mobility_migration("create_string_translations") unless options.without_tables?
21
+ end
22
+
23
+ def create_initializer
24
+ create_file(
25
+ "config/initializers/mobility.rb",
26
+ "Mobility.config.default_backend = :key_value"
27
+ )
28
+ end
29
+
30
+ def self.next_migration_number(dirname)
31
+ ::ActiveRecord::Generators::Base.next_migration_number(dirname)
32
+ end
33
+
34
+ protected
35
+
36
+ def add_mobility_migration(template)
37
+ migration_dir = File.expand_path("db/migrate")
38
+ if self.class.migration_exists?(migration_dir, template)
39
+ ::Kernel.warn "Migration already exists: #{template}"
40
+ else
41
+ migration_template "#{template}.rb", "db/migrate/#{template}.rb"
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,15 @@
1
+ class CreateStringTranslations < ActiveRecord::Migration
2
+
3
+ def change
4
+ create_table :mobility_string_translations do |t|
5
+ t.string :locale
6
+ t.string :key
7
+ t.string :value
8
+ t.integer :translatable_id
9
+ t.string :translatable_type
10
+ t.timestamps
11
+ end
12
+ add_index :mobility_string_translations, [:translatable_id, :translatable_type, :locale, :key], unique: true, name: :index_mobility_string_translations_on_keys
13
+ add_index :mobility_string_translations, [:translatable_id, :translatable_type], name: :index_mobility_string_translations_on_translatable
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ class CreateTextTranslations < ActiveRecord::Migration
2
+
3
+ def change
4
+ create_table :mobility_text_translations do |t|
5
+ t.string :locale
6
+ t.string :key
7
+ t.text :value
8
+ t.integer :translatable_id
9
+ t.string :translatable_type
10
+ t.timestamps
11
+ end
12
+ add_index :mobility_text_translations, [:translatable_id, :translatable_type, :locale, :key], unique: true, name: :index_mobility_text_translations_on_keys
13
+ add_index :mobility_text_translations, [:translatable_id, :translatable_type], name: :index_mobility_text_translations_on_translatable
14
+ end
15
+ end
data/lib/mobility.rb CHANGED
@@ -1,5 +1,206 @@
1
- require "mobility/version"
1
+ require 'i18n'
2
+ require 'request_store'
3
+ require 'mobility/version'
2
4
 
5
+ %w[object nil string].each do |type|
6
+ begin
7
+ require "active_support/core_ext/#{type}"
8
+ rescue LoadError
9
+ require "mobility/core_ext/#{type}"
10
+ end
11
+ end
12
+
13
+ =begin
14
+
15
+ Mobility is a gem for storing and retrieving localized data through attributes
16
+ on a class. The {Mobility} module includes all necessary methods and modules to
17
+ support defining backend accessors on a class.
18
+
19
+ To enable Mobility on a class, simply include or extend the {Mobility} module,
20
+ and define any attribute accessors using {Translates#mobility_accessor} (aliased to the
21
+ value of {Mobility.accessor_method}, which defaults to +translates+).
22
+
23
+ class MyClass
24
+ include Mobility
25
+ translates :title, backend: :key_value
26
+ end
27
+
28
+ When defining this module, Mobility attempts to +require+ various gems (for
29
+ example, +active_record+ and +sequel+) to evaluate which are loaded. Loaded
30
+ gems are tracked with dynamic subclasses of the {Loaded} module and referenced
31
+ in backends to define gem-dependent behavior.
32
+
33
+ =end
3
34
  module Mobility
4
- # Your code goes here...
35
+ autoload :Attributes, "mobility/attributes"
36
+ autoload :Backend, "mobility/backend"
37
+ autoload :BackendResetter, "mobility/backend_resetter"
38
+ autoload :Configuration, "mobility/configuration"
39
+ autoload :InstanceMethods, "mobility/instance_methods"
40
+ autoload :Translates, "mobility/translates"
41
+ autoload :Wrapper, "mobility/wrapper"
42
+
43
+ require "mobility/orm"
44
+
45
+ # General error for version compatibility conflicts
46
+ class VersionNotSupportedError < ArgumentError; end
47
+
48
+ begin
49
+ require "active_record"
50
+ raise VersionNotSupportedError, "Mobility is only compatible with ActiveRecord 5.0 and greater" if ::ActiveRecord::VERSION::MAJOR < 5
51
+ autoload :ActiveModel, "mobility/active_model"
52
+ autoload :ActiveRecord, "mobility/active_record"
53
+ Loaded::ActiveRecord = true
54
+ rescue LoadError
55
+ Loaded::ActiveRecord = false
56
+ end
57
+
58
+ begin
59
+ require "rails"
60
+ autoload :InstallGenerator, "generators/mobility/install_generator"
61
+ Loaded::Rails = true
62
+ rescue LoadError
63
+ class InstallGenerator; end
64
+ Loaded::Rails = false
65
+ end
66
+
67
+ begin
68
+ require "sequel"
69
+ raise VersionNotSupportedError, "Mobility is only compatible with Sequel 4.0 and greater" if ::Sequel::MAJOR < 4
70
+ require "sequel/extensions/inflector"
71
+ require "sequel/plugins/dirty"
72
+ autoload :Sequel, "mobility/sequel"
73
+ Loaded::Sequel = true
74
+ rescue LoadError
75
+ Loaded::Sequel = false
76
+ end
77
+
78
+ class << self
79
+ def extended(model_class)
80
+ return if model_class.respond_to? :mobility_accessor
81
+ model_class.class_eval do
82
+ def self.mobility
83
+ @mobility ||= Mobility::Wrapper.new(self)
84
+ end
85
+ def self.translated_attribute_names
86
+ mobility.translated_attribute_names
87
+ end
88
+
89
+ class << self
90
+ include Translates
91
+ if translates = Mobility.config.accessor_method
92
+ alias_method translates, :mobility_accessor
93
+ end
94
+ end
95
+ end
96
+
97
+ model_class.include(InstanceMethods)
98
+
99
+ if Loaded::ActiveRecord
100
+ model_class.include(ActiveRecord) if model_class < ::ActiveRecord::Base
101
+ model_class.include(ActiveModel::AttributeMethods) if model_class.ancestors.include?(::ActiveModel::AttributeMethods)
102
+ end
103
+
104
+ if Loaded::Sequel
105
+ model_class.include(Sequel) if model_class < ::Sequel::Model
106
+ end
107
+ end
108
+
109
+ # Extends model with this class so that +include Mobility+ is equivalent to
110
+ # +extend Mobility+
111
+ # @param model_class
112
+ def included(model_class)
113
+ model_class.extend self
114
+ end
115
+
116
+ # @!group Locale Accessors
117
+ # @return [Symbol] Mobility locale
118
+ def locale
119
+ read_locale || I18n.locale
120
+ end
121
+
122
+ # Sets Mobility locale
123
+ # @param [Symbol] locale Locale to set
124
+ # @raise [InvalidLocale] if locale is nil or not in +I18n.available_locales
125
+ # @return [Symbol] Locale
126
+ def locale=(locale)
127
+ set_locale(locale)
128
+ end
129
+
130
+ # Sets Mobility locale around block
131
+ # @param [Symbol] locale Locale to set in block
132
+ # @yield [Symbol] Locale
133
+ def with_locale(locale)
134
+ previous_locale = read_locale
135
+ begin
136
+ set_locale(locale)
137
+ yield(locale)
138
+ ensure
139
+ set_locale(previous_locale)
140
+ end
141
+ end
142
+ # @!endgroup
143
+
144
+ # @return [RequestStore] Request store
145
+ def storage
146
+ RequestStore.store
147
+ end
148
+
149
+ # @!group Configuration Methods
150
+ # @return [Mobility::Configuration] Mobility configuration
151
+ def config
152
+ storage[:mobility_configuration] ||= Mobility::Configuration.new
153
+ end
154
+
155
+ # (see Mobility::Configuration#accessor_method)
156
+ # @!method accessor_method
157
+
158
+ # (see Mobility::Configuration#default_fallbacks)
159
+ # @!method default_fallbacks
160
+
161
+ # (see Mobility::Configuration#default_backend)
162
+ # @!method default_backend
163
+
164
+ # (see Mobility::Configuration#default_accessor_locales)
165
+ # @!method default_accessor_locales
166
+ %w[accessor_method default_fallbacks default_backend default_accessor_locales].each do |method_name|
167
+ define_method method_name do
168
+ config.public_send(method_name)
169
+ end
170
+ end
171
+
172
+ # Configure Mobility
173
+ # @yield [Mobility::Configuration] Mobility configuration
174
+ def configure
175
+ yield config
176
+ end
177
+ # @!endgroup
178
+
179
+ # Return normalized locale
180
+ # @param [String,Symbol] locale
181
+ # @return [String] Normalized locale
182
+ # @example
183
+ # Mobility.normalize_locale(:ja)
184
+ # #=> "ja"
185
+ # Mobility.normalize_locale("pt-BR")
186
+ # #=> "pt_br"
187
+ def normalize_locale(locale)
188
+ "#{locale.to_s.downcase.sub("-", "_")}"
189
+ end
190
+
191
+ protected
192
+
193
+ def read_locale
194
+ storage[:mobility_locale]
195
+ end
196
+
197
+ def set_locale(locale)
198
+ locale = locale.to_sym if locale
199
+ raise Mobility::InvalidLocale.new(locale) unless I18n.available_locales.include?(locale) || locale.nil?
200
+ storage[:mobility_locale] = locale
201
+ end
202
+ end
203
+
204
+ class BackendRequired < ArgumentError; end
205
+ class InvalidLocale < I18n::InvalidLocale; end
5
206
  end
@@ -0,0 +1,6 @@
1
+ module Mobility
2
+ module ActiveModel
3
+ autoload :AttributeMethods, "mobility/active_model/attribute_methods"
4
+ autoload :BackendResetter, "mobility/active_model/backend_resetter"
5
+ end
6
+ end
@@ -0,0 +1,27 @@
1
+ module Mobility
2
+ module ActiveModel
3
+ =begin
4
+
5
+ Included into model if model has +ActiveModel::AttributeMethods+ among its
6
+ ancestors.
7
+
8
+ =end
9
+ module AttributeMethods
10
+ delegate :translated_attribute_names, to: :class
11
+
12
+ # Adds translated attributes to +attributes+.
13
+ # @return [Array<String>] Model attributes
14
+ def attributes
15
+ super.merge(translated_attributes)
16
+ end
17
+
18
+ # Translated attributes defined on model.
19
+ # @return [Array<String>] Translated attributes
20
+ def translated_attributes
21
+ translated_attribute_names.inject({}) do |attributes, name|
22
+ attributes.merge(name.to_s => send(name))
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,26 @@
1
+ module Mobility
2
+ module ActiveModel
3
+ =begin
4
+
5
+ Backend resetter for ActiveModel models. Adds hook to reset backend when
6
+ +changes_applied+ or +clear_changes_information+ methods are called on model.
7
+
8
+ =end
9
+ class BackendResetter < Mobility::BackendResetter
10
+
11
+ # (see Mobility::BackendResetter#initialize)
12
+ def initialize(attributes)
13
+ super
14
+
15
+ model_reset_method = @model_reset_method
16
+
17
+ %i[changes_applied clear_changes_information].each do |method|
18
+ define_method method do
19
+ super()
20
+ instance_eval &model_reset_method
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,39 @@
1
+ module Mobility
2
+ =begin
3
+
4
+ Module loading ActiveRecord-specific classes for Mobility models.
5
+
6
+ =end
7
+ module ActiveRecord
8
+ autoload :BackendResetter, "mobility/active_record/backend_resetter"
9
+ autoload :ModelTranslation, "mobility/active_record/model_translation"
10
+ autoload :StringTranslation, "mobility/active_record/string_translation"
11
+ autoload :TextTranslation, "mobility/active_record/text_translation"
12
+ autoload :Translation, "mobility/active_record/translation"
13
+
14
+ def changes_applied
15
+ @previously_changed = changes
16
+ super
17
+ end
18
+
19
+ def clear_changes_information
20
+ @previously_changed = ActiveSupport::HashWithIndifferentAccess.new
21
+ super
22
+ end
23
+
24
+ def previous_changes
25
+ super.merge(@previously_changed ||= ActiveSupport::HashWithIndifferentAccess.new)
26
+ end
27
+
28
+ def self.included(model_class)
29
+ model_class.extend(ClassMethods)
30
+ end
31
+
32
+ module ClassMethods
33
+ # @return [ActiveRecord::Relation] relation extended with Mobility query methods.
34
+ def i18n
35
+ all
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,26 @@
1
+ module Mobility
2
+ module ActiveRecord
3
+ =begin
4
+
5
+ Backend resetter for ActiveRecord models. Adds hook on +reload+ event to
6
+ {Mobility::ActiveModel::BackendResetter}.
7
+
8
+ =end
9
+ class BackendResetter < Mobility::ActiveModel::BackendResetter
10
+
11
+ # @param [Class] model_class Class of model to which backend resetter will be applied
12
+ def included(model_class)
13
+ model_reset_method = @model_reset_method
14
+
15
+ model_class.class_eval do
16
+ mod = Module.new do
17
+ define_method :reload do
18
+ super().tap { instance_eval &model_reset_method }
19
+ end
20
+ end
21
+ include mod
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end