shioyama / mobility Goto Github PK
View Code? Open in Web Editor NEWPluggable Ruby translation framework
License: MIT License
Pluggable Ruby translation framework
License: MIT License
Example:
[53] ruby 2.4.1(main)> i = Dictionaries::Industry.find 2
[54] ruby 2.4.1(main)> i.categories
=> [#<Dictionaries::Category:0x007f44acaae4a0 id: 13, industry_id: 2, slug: "uplotnitelnaya-tehnika">,
#<Dictionaries::Category:0x007f44acaaddc0 id: 16, industry_id: 2, slug: "dorozhnye-frezy">]
[55] ruby 2.4.1(main)> i.categories.i18n
=> [#<Dictionaries::Category:0x005560c2095a88 id: 13, industry_id: 2, slug: "uplotnitelnaya-tehnika">,
#<Dictionaries::Category:0x005560c20952e0 id: 16, industry_id: 2, slug: "dorozhnye-frezy">]
[56] ruby 2.4.1(main)>
[57] ruby 2.4.1(main)>
[58] ruby 2.4.1(main)> i = Dictionaries::Industry.find 5
[59] ruby 2.4.1(main)> i.categories
=> [#<Dictionaries::Category:0x007f44ac7a5880 id: 36, industry_id: 5, slug: "cazhalki">,
#<Dictionaries::Category:0x007f44ac7a56a0 id: 37, industry_id: 5, slug: "zhatki">,
#<Dictionaries::Category:0x007f44ac79aea8 id: 55, industry_id: 5, slug: "uborochnaya-tehnika-dlya-sena-furazha">]
[60] ruby 2.4.1(main)> i.categories.i18n
=> [#<Dictionaries::Category:0x005560c2095a88 id: 13, industry_id: 2, slug: "uplotnitelnaya-tehnika">,
#<Dictionaries::Category:0x005560c20952e0 id: 16, industry_id: 2, slug: "dorozhnye-frezy">]
[61] ruby 2.4.1(main)>
Under Rails 5.0, update_attribute only works for the key_value/table backends if the dirty plugin is enabled.
Under Rails 5.1, update_attribute never works for the key_value/table backends (even if the dirty plugin is enabled).
See #93 for tests that reproduce this bug.
Behaviour in 0.1.14
[4] pry(main)> User.first
User Load (1.0ms) SELECT "users".* FROM "users" ORDER BY "users"."created_at" ASC LIMIT $1 [["LIMIT", 1]]
=> #<User id: "8ec92fd5-5926-4657-9b32-8cf8382de795", email: "[email protected]", created_at: "2017-05-16 10:19:37", updated_at: "2017-05-16 10:19:37", first_name: nil, last_name: nil, guest: false>
Behaviour in 0.1.15
and 0.1.16
[4] pry(main)> User.first
NoMethodError: undefined method `[]' for nil:NilClass
from /Users/<user>/.rvm/gems/ruby-2.4.1/gems/activerecord-5.1.0/lib/active_record/relation/delegation.rb:5:in `relation_delegate_class'
Coming from this line: https://github.com/rails/rails/blob/v5.1.0/activerecord/lib/active_record/relation/delegation.rb#L5
I wanted to upgrade the gem, because I was getting a lot of these deprecation warnings:
DEPRECATION WARNING: The behavior of `changed_attributes` inside of after callbacks will be changing in the next version of Rails. The new return value will reflect the behavior of calling the method after `save` returned (e.g. the opposite of what it returns now). To maintain the current behavior, use `saved_changes.transform_values(&:first)` instead. (called from create_guest_user at /Users/<user>/Desktop/<project>/app/models/user.rb:28)
line causing them is just user.save!
, and a colleague thought that they might be coming from mobility. Seems like this is related rails/rails#28640
Hello,
first of all, thanks for this great gem!
I would like to know, if there is a way to simply assign and save a hash to an attribute in such a way that this hash will NOT be stored under the current locales key?
The reason I want to do that is to assign multiple locale values using a nested form and update multiple translations at once.
I use a JSONB column to store translations.
# I18n.locale = :en
# It should set the attribute simply to the given hash
some_post.description = {en: 'Hello World', de: 'Hallo Welt'}
some_post.save
# Actual JSONB column value:
{'en': {'en': 'Hello World', 'de': 'Hallo Welt'}}
# Expected JSONB column value:
{'en': 'Hello World', 'de': 'Hallo Welt'}
Not really sure that it is a Mobility issue, it's more likely a compatibility bug with strip_attributes. If an attribute was previously set via #write_attribute
(or the #[]=
alias), when AR persists changes, this attribute is stored as nil (instead of a JSONB object { locale => attribute_value }
).
I use mobility
and strip_attributes for my project. Recently I started to notice that sometimes when the user updates a record with columns backed by the Mobility's JSONB backend, some of these columns' values disappear (corresponding Postgres JSONB field is set to NULL, which results in losing all available translations for that field). After some debugging I found out that it happens when an attribute's value contains leading or trailing whitespace and the model's attributes are stripped via strip_attributes
.
strip_attributes
basically registers a before_validation
hook that updates an attribute via #[]=
if the stripped value differs from the original value, hence the reproducible example:
begin
require 'bundler/inline'
rescue LoadError => e
$stderr.puts 'Bundler version 1.10 or later is required.'
raise e
end
gemfile(true) do
source 'https://rubygems.org'
gem 'activerecord', '5.1.0'
gem 'mobility', '0.2.2'
gem 'pg', '0.18.4'
gem 'rspec', '3.6.0', require: 'rspec/autorun'
gem 'pry-byebug'
end
DB = ENV.fetch('MOBILITY_TEST_DB', 'mobility-test').freeze
ActiveRecord::Base.establish_connection(adapter: 'postgresql', database: DB)
ActiveRecord::Base.logger = Logger.new($stdout)
I18n.locale = I18n.default_locale = :en
I18n.available_locales = %i[en ru]
Mobility.configure do |config|
config.default_backend = :jsonb
config.accessor_method = :translates
end
ActiveRecord::Schema.define do
create_table :articles, force: true do |t|
t.jsonb :title, default: {}
t.jsonb :title_strip, default: {}
end
end
class Article < ActiveRecord::Base
extend Mobility
translates :title, :title_strip
STRIP_ATTRIBUTES = %i[title_strip].freeze
before_validation do |record|
record.attributes.slice(*STRIP_ATTRIBUTES.map(&:to_s)).each do |attr, value|
next unless value.respond_to?(:strip)
stripped_value = value.strip
record[attr] = stripped_value if value != stripped_value
end
end
end
RSpec.describe Article do
let(:params) { Hash[title: title, title_strip: title] }
subject(:article) { Article.create!(params) }
describe '#create!' do
context "when title doesn't have leading or trailing space" do
let(:title) { 'title without leading or trailing space' }
it { expect(article.title).to eq(title) }
it { expect(article.title_strip).to eq(title) }
end
context 'when title has leading space' do
let(:title) { ' title with leading space' }
it { expect(article.title).to eq(title) }
it { expect(article.title_strip).to eq(title.strip) }
end
end
end
The tests should pass.
The last test fails.
How I can mitigate the problem:
strip_attributes
via the :except
option (this is what I've done so far).How it can be fixed:
strip_attributes
use attribute accessor methods for writing to a model (use record.public_send("#{attr}=", value)
instead of record[attr] = value
;I have a text field :content
.
Usually, SimpleForm correctly displays a textarea for it:
= f.input :content
When translating the field using Mobility, it displays a single line input!
If you need a demo Rails application, I can set up one for you.
The translated_attribute_names
instance method was extracted into the attribute_methods
plugin, although it is still defined on the class. It does not need to be in the plugin; if we were to extract it there, there should anyway be a deprecation phase.
Here is the line in the plugin.
@jmuheim reported this in #97.
If I call post.translated_attribute_names
I should get the translated attribute names for the model (it should delegate to Post.translated_attribute_names
.
I get a NoMethodError
.
Just move the delegate
into Mobility::InstanceMethods
.
Given the following scenario
class Model
translates :title
end
m1 = Model.create(title: 'Title')
m2 = m1.dup
m2.title = 'Changed title' # This changes m1.title
m1.title # => 'Changed title'
m1.title_backend == m2.title_backend # => true
When you update a translation (table backend), the cache key is not being invalidated.
There's two approaches I see of fixing this:
touch: true
when setting up the belongs_to
relationship.The advantage of this approach is it is quite simple to implement, and makes the model behave like any of the backends that store the translation within the model.
The disadvantage of this approach is that if someone is using the updated_at
for something more than cache invalidation, this might not match their expectation.
Globalize has touch
as an option. I've made a branch of mobility using this approach, which I'll use for now on my app.
This is the approach that globalize takes.
One possible issue I see with their implementation is that it constructs the cache_key
using only the translation based on the current locale. If you're only using methods like event.title
, this works fine. But if you access a translation in a different locale than the one you're in (e.g., when the locale is :en
, accessing event.title(:ja)
), the cache_key will be incorrect.
More generally, I see this approach as potentially having performance implications, as now you need to load the translations to calculate the cache_key.
Given the following scenario:
class Model
include Mobility
translates :title
end
class SubModel < Model
end
Model.translated_attribute_names => ["title"]
SubModel.translated_attribute_names => []
P.S. Is there already a method to get an array with the i18n_accessors? like ["title_en", "title_es", "title_nl"]
assuming those are the defined locales
rails generate mobility:translations project title:string description:string story:string
yields
class CreateProjectTitleAndDescriptionAndStoryTranslationsForMobilityTableBackend < ActiveRecord::Migration[5.1]
def change
add_column :project_translations, :title, :string
add_column :project_translations, :description, :string
add_column :project_translations, :story, :string
end
end
Which doesn't work if project_translations
table doesn't exist beforehand.
This should be either mentioned in the documentation explicitly or the generator should assume there's no such a table and create an appropriate migration.
Is there a way to make mobility work with the friendly_id gem?
I did got it working with the globalize gem, but it seems not to be working with mobility
#84 fixed this issue for the Table backend, but it is still broken for the KeyValue backend.
dup
does not duplicate translations. Suppose we have:
post = Post.new
post.title = "foo"
post.save
post.title
#=> "foo"
post_dup = post.dup
The title
of the dup'ed post should be "foo"
:
post_dup.title
#=> "foo"
The title
is nil, because the translations are not dup'ed:
post_dup.title
#=> nil
There is already a spec for this which is disabled for KeyValue backends (AR + Sequel), so if someone works on this, just remove that skip code and make the test pass.
Is it a way to use the i18n scope by default in every model that includes mobility?
Model.where(name: 'abc')
instead of
Model.i18n.where(name: 'abc')
Is there a way to get a list of the locales in which a certain attribute has a value? I'm using the postgres jsonb backend, and I know I could just do @model.read_attribute(:title).keys
, but that seems brittle and like there ought to be a way that doesn't require me to know about the json blob structure.
I'm currently working with mobility and using two locales en and zh-CN but I have plans for about 50 or more locales to be added. Normally fallback would work like this zh-CN -> zh -> en, but in a case where there is only a field in zh-CN and the en one is blank how can I get this kind of behavior for 50 plus locales without defining fallback chains?
Thanks
When Mobility is loaded for ActiveRecord, it includes Mobility::ActiveRecord
module
Line 86 in b001a29
If the ActiveRecord::Base
subclass that now includes Mobility
did something like the following before:
class MyModel < ActiveRecord::Base
def my_method
ActiveRecord::Base.uncached do
# some code
end
end
end
It will get an exception raised:
NameError:
uninitialized constant Mobility::ActiveRecord::Base
It will get an exception because ActiveRecord
is found on Mobility
module first (the original ActiveRecord
from rails is further down in the ancestors chain.
The fix on the user's side would be to qualify top level lookup with ::ActiveRecord
but I don't know how happy the users of Mobility would be if they had to do it.
Another would be to rename the ActiveRecord
module in Mobility, which would kind of diminish the descriptiveness.
Do you have other suggestions on how to approach this issue?
Some API changes in the latest Sequel are breaking querying stuff, so I've limited specs to 4.45.x until I have time to look into the issue.
The Default plugin can be used to set a default value to fallthrough to in case a translation would otherwise be nil. A Proc can be used in place of a fixed value, and the proc currently is passed the model and attribute name as keyword arguments.
However, the plugin does not pass the locale and accessor options to the proc, so if e.g. you wanted to set a default value in each locale, you wouldn't be able to do that. This seems like a very common use of this plugin so we should pass both the locale and the options hash as well.
The value fetched with a translation reader is cached even when the super: true
option is passed in.
The super: true
option allows you to bypass the Mobility accessor (reader or writer) and get or set using any original method defined with the same name.
The cache plugin caches all reads to the backend so that they do not need to be fetched for the same locale again while the model is loaded (and has not been reloaded/reset or saved).
post = Post.first
post.title(super: true)
#=> "foo"
# ^ This is the original value fo the `title` method
post.title
#=> "bar"
# ^ This is the value fetched from the backend
post = Post.first
post.title(super: true)
#=> "foo"
post.title
#=> "foo"
The second fetch without the super: true
option returns the same value, because it has been cached. We now cannot get the real value from the backend unless we reload the model.
Bypass cache in cache plugin when super: true
is passed in as an option.
Hi,
i was trying to make a search filed in my rails application but it seems not to work with the mobility tables.
This is my model function:
self.visible.where("LOWER(#{answer}) LIKE :term OR LOWER(#{question}) LIKE :term", term: "%#{term.downcase}%")
Rails console:
Faq Load (0.6ms) SELECT "faqs".* FROM "faqs" WHERE "faqs"."visible" = $1 AND (LOWER(answer_nl) LIKE '%kaas%' OR LOWER(question_nl) LIKE '%kaas%') [["visible", true]]
ActiveRecord::StatementInvalid: PG::UndefinedColumn: ERROR: column "answer_nl" does not exist
LINE 1: ...ROM "faqs" WHERE "faqs"."visible" = $1 AND (LOWER(answer_nl)...
It looks like he is not searching in the mobility tables.
Am i doing something wrong or do i need an other way to approach this?
Just some warnings popping up about Rails on Hanami + Sequel.
Hanami 1.0.0
Sequel 4.*
Mobility 0.2.0
/home/vagrant/.rvm/gems/ruby-2.3.3/gems/mobility-0.2.0/lib/mobility.rb:57: warning: already initialized constant Mobility::Loaded::Rails
/home/vagrant/.rvm/gems/ruby-2.3.3/gems/mobility-0.2.0/lib/mobility.rb:53: warning: previous definition of Rails was here
Using the jsonb backend with PostgreSQL, I am trying to find all records missing translations of the current locale. In ActiveRecord you can pass an array of values to perform an OR condition.
Product.i18n.where(name: ['', nil])
In a normal string column this would find all products whose name is empty or nil.
This looks for a product whose name matches ['', nil]
json data.
My current solution is this:
Product.i18n.where(name: '').or(Product.i18n.where(name: nil))
I understand that since this is a jsonb
data type ActiveRecord treats the query condition differently, however it would be nice if Mobility kept the behavior of a string column. BTW great gem!
Setting default_fallbacks
configuration option doesn't have any (visible) effect. Consider the following snippet:
begin
require 'bundler/inline'
rescue LoadError => e
$stderr.puts 'Bundler version 1.10 or later is required.'
raise e
end
gemfile(true) do
source 'https://rubygems.org'
gem 'activerecord', '5.1.0'
gem 'mobility', '0.2.2'
gem 'pg', '0.18.4'
gem 'rspec', '3.6.0', require: 'rspec/autorun'
end
DB = ENV.fetch('MOBILITY_TEST_DB', 'mobility-test').freeze
ActiveRecord::Base.establish_connection(adapter: 'postgresql', database: DB)
ActiveRecord::Base.logger = Logger.new($stdout)
I18n.default_locale = :en
I18n.available_locales = %i[en ru]
Mobility.configure do |config|
config.default_backend = :jsonb
config.accessor_method = :translates
config.default_fallbacks = { ru: :en, en: :ru }
end
ActiveRecord::Schema.define do
create_table :articles, force: true do |t|
t.jsonb :title, null: false, default: {}
end
end
class Article < ActiveRecord::Base
extend Mobility
translates :title
end
RSpec.describe 'Fallbacks' do
around do |example|
I18n.with_locale(:en) { Article.create!(title: 'Title') }
I18n.with_locale(:ru) { example.run }
end
subject(:article) { Article.last }
it { expect(article.title).to eq('Title') }
end
I expected that setting default_fallbacks
will make all translated attributes to fallback to existing locales by default. Moreover, when I try to use both default_fallbacks
and the fallbacks
option to translates
, I get "default_fallbacks
: undefined method call
for {:ru=>:en, :en=>:ru}:Hash (NoMethodError)":
begin
require 'bundler/inline'
rescue LoadError => e
$stderr.puts 'Bundler version 1.10 or later is required.'
raise e
end
gemfile(true) do
source 'https://rubygems.org'
gem 'activerecord', '5.1.0'
gem 'mobility', '0.2.2'
gem 'pg', '0.18.4'
end
DB = ENV.fetch('MOBILITY_TEST_DB', 'mobility-test').freeze
ActiveRecord::Base.establish_connection(adapter: 'postgresql', database: DB)
ActiveRecord::Base.logger = Logger.new($stdout)
I18n.default_locale = :en
I18n.available_locales = %i[en ru]
Mobility.configure do |config|
config.default_backend = :jsonb
config.accessor_method = :translates
config.default_fallbacks = { ru: :en, en: :ru }
end
ActiveRecord::Schema.define do
create_table :articles, force: true do |t|
t.jsonb :title, null: false, default: {}
end
end
class Article < ActiveRecord::Base
extend Mobility
translates :title, fallbacks: { ru: :en, en: :ru }
end
So I assume that either default_fallbacks
is broken or the documentation is unclear on how to properly use it.
Hi, thank you for this great gem.
When I use uniqueness: true
in validations, I get the following message. I have tried using it with an existing database and a new database.
Running via Spring preloader in process 16712
Loading development environment (Rails 5.1.1)
[1] pry(main)> Category.create(name: 'CSR')
(0.2ms) BEGIN
Mobility::ActiveRecord::StringTranslation Exists (1.3ms) SELECT 1 AS one FROM "mobility_string_translations" WHERE "mobility_string_translations"."key" = $1 AND "mobility_string_translations"."translatable_id" IS NULL AND "mobility_string_translations"."translatable_type" = $2 AND "mobility_string_translations"."locale" = $3 LIMIT $4 [["key", "name"], ["translatable_type", "Category"], ["locale", "en"], ["LIMIT", 1]]
(0.2ms) ROLLBACK
ActiveRecord::ImmutableRelation: ActiveRecord::ImmutableRelation
from /home/jonian/.gem/ruby/2.4.0/gems/activerecord-5.1.1/lib/active_record/relation/query_methods.rb:936:in `assert_mutability!'
I am using the Key-Store
backend, PostgreSQL 9.6.3
, Rails 5.1.1
and Ruby 2.4.1
.
The model configuration is below:
class Category < ApplicationRecord
# Include modules
include Mobility
# Translate attributes
translates :name, type: :string, locale_accessors: true, fallbacks: true
# Attribute validations
validates :name, presence: true, uniqueness: true
end
class ApplicationRecord < ActiveRecord::Base
# Set class as abstract
self.abstract_class = true
end
Iโm using the jsonb
backend to translate an array. This is how it looks natively.
translators: {"fr"=>["Foo Bar"]}
I want to be able to treat translators
always as an array (calling .to_sentence
), but I canโt do that if it returns nil
when the locale is en
. Is there any way to set a default like []
?
It would be nice if the jsonb
and hstore
backends had a different default column name such as title_translations
for a title
attribute. This way we are not overriding methods generated by ActiveRecord and we can access it directly without using a work around like read_attribute
.
Also read_attribute
might be used by other gems as an alternative way to access the value and expect it to be the same as calling the accessor method.
The real motivation for this change is that we can define type_for_attribute
or column_for_attribute
methods so that other gems can infer the type (such as simple_form and formtastic). Overriding this on a column which already exists might have some bad side effects.
This change would probably require a major version bump so I can understand if it gets rejected.
RE: #75
There are two different errors popping up as you try to add a new item in a model that has mobility loaded and pointed at the jsonb columns.
Gems:
hanami 1.0.0
sequel (4.49.0)
sequel-annotate (1.0.0)
sequel-rake (0.1.0)
sequel_pg (1.7.0)
sequel_polymorphic (0.3.1)
sequel_sluggable (0.0.6)
Columns are created with:
jsonb :synopsis
Except for title who has null: false
Model:
translates :title, type: :string
translates :synopsis, type: :text
Well, it should create the item.
When creating an item with a null column which is jsonb and mobility pointed at:
Sequel::Error: The AND operator requires at least 1 argument
When specifying data for both jsonb columns (title and synopsis):
2.3.3 :004 >
m.synopsis = "test"
=> "test"
2.3.3 :005 > m.save
Sequel::DatabaseError: PG::DatatypeMismatch: ERROR: column "synopsis" is of type jsonb but expression is of type boolean
LINE 1: ...gas" ("synopsis", "title", "created_at") VALUES (('en' = 'te...
^
HINT: You will need to rewrite or cast the expression.
This hits both translated columns it just seems to put the error out on the last one though.
If I comment out the translates one on synopsis it hits the title one.
Might be Sequel's fault.
Accessing a specific language works already neatly with
post.title(locale: :en)
It would be great if one could simply pass a fallback locale in the getter directly
post.title(locale: :en, fallback: :de)
#
Hello. Thanks for this gem.
I have one problem, I can't use single-table inheritance with mobility gem.
Isolated reproduction here: https://gist.github.com/ArturLyapin/62669b79a0ab3a57ad2f563644f8e9e4
This seems to happen with Spring in a Rails project. When spring reloads, the defaults in the Mobility configuration are reset and not reloaded from the initializer, so you see the exception Backend option required if Mobility.config.default_backend is not set."
.
Not yet quite sure why this happens, just posting this for now to remember to fix it.
I can't figure out how I correctly setup a form when using fallbacks. Now all the empty fields are filled in by the fallback language.
Since 0.2.0, it has become possible to set Mobility.default_options
which are merged into the options set whenever translates
is called on a Mobility model. However, the problem with this is that the "default" default_options is overridden if the user sets these options with Mobility.default_options =
, which will typically disable "low-profile" plugins like presence and cache.
Setting Mobility.default_options =
raises an error and instructs the user to set options by their key, e.g.:
Mobility.configure do |config|
# ...
config.default_options[:fallbacks] = { ... }
config.default_options[:dirty] = true
# ...
end
Mobility allows default options to be overridden entirely, implicitly disabling any options which were previously true
but now are nil
.
Add a deprecation warning on the setter in 0.3.0, and remove thereafter.
I've been trying to use Mobility in my Rails plugin.
To reproduce the error create a new plugin:
rails plugin new my_plugin --mountable
If I run this from my_plugin
root
rails generate mobility:install
it returns this error
Could not find generator 'mobility:install'. Maybe you meant 'mobility:install', 'mobility:translations' or 'test_unit:system'
I also opened a stackoverflow question
When using locale_accessors, the locale specific accessors also use the fallbacks. e.g.,
Event.new(title_en: "foo").title_ja
#=> "foo" instead of nil
I'm using the locale accessors to generate a form that provides the ability to edit an events title in Japanese and English (in two different form fields). With globalize, the locale specific accessors would return nil. This made it obvious which fields have not been translated. It also meant translations only got written when the user entered them. With mobility's current behaviour, if a user edits an event with an untranslated title and then saves it, the untranslated title will become the original title.
I have a Page
model that translates :title, :navigation_title, :lead, :content
.
class Page < ApplicationRecord
extend Mobility
translates :title, :navigation_title, :lead, :content
end
I'm using the following config:
Mobility.configure do |config|
config.default_backend = :column
config.accessor_method = :translates
config.query_method = :i18n
config.default_options = {
fallbacks: { de: :en }
}
end
I noticed a strange behaviour: whenever I submit a value using the form with a trailing whitespace (e.g. _x
, _x_
, or x_
, where whitespace is substituted with an underscore) to a translated field (e.g. lead
) I get the following error:
ActiveModel::MissingAttributeError at /de/pages/1
can't write unknown attribute `lead`
If I submit any other value (e.g.
x`), it works perfectly.
This only happens to translated fields, not to other fields.
Any idea what's going on? I'm happy to provide more details if needed.
Here's the app I'm currently working on: jmuheim/base#86
I created the basic migrations with rails generate mobility:install
with backend: key_value
using mobility version 0.2.3 (Rails 5.1.4).
It should run the initial migration.
When I do rails db:migrate
it fails for
add_index(:mobility_string_translations, [:translatable_type, :key, :value, :locale], {:name=>:index_mobility_string_translations_on_query_keys})
with the following message:
Mysql2::Error: Specified key was too long; max key length is 3072 bytes: CREATE INDEX
index_mobility_string_translations_on_query_keys
ONmobility_string_translations
(translatable_type
,key
,value
,locale
)
This maybe happens because we use utf8mb4-encoding in database.yml
development:
adapter: mysql2
encoding: utf8mb4
UTF8mb4 is a 4-Byte character encoding. We prefer it because it is a real UTF-8-encoding (the origininal is missing lots of symbols, see this explanation).
We can now calculate the length of the index:
translatable_type
: 255*4key
: 255*4value
: 255*4locale
: 255*4We can for example make locale varchar(15)
(the longest locales I found were 12 characters long).
We then have to save another 12 chars. That can be for example by changing key from varchar(255)
to varchar(243)
.
So the Migration would look like this:
class CreateStringTranslations < ActiveRecord::Migration[5.1]
def change
create_table :mobility_string_translations do |t|
t.string :locale, limit: 15 # NEW limit option
t.string :key, limit: 243 # NEW limit option
t.string :value
t.integer :translatable_id
t.string :translatable_type
t.timestamps
end
add_index :mobility_string_translations, [:translatable_id, :translatable_type, :locale, :key], unique: true, name: :index_mobility_string_translations_on_keys
add_index :mobility_string_translations, [:translatable_id, :translatable_type, :key], name: :index_mobility_string_translations_on_translatable_attribute
add_index :mobility_string_translations, [:translatable_type, :key, :value, :locale], name: :index_mobility_string_translations_on_query_keys
end
end
This proposed fix worked for me, but is maybe harmful so I do not make a PR because I don't know what you think about this.
I am having troubles deciding whether to use :jsonb or :table as backend.
I use Postgres, and I am having the need of keeping 5+ locales.
let's say I want to use :jsonb and the table looks like this (btw. a wiki on the right creation of the migrations would be very useful):
create_table :posts do |t|
t.jsonb :title, null: false, default: '{}'
t.jsonb :content, null: false, default: '{}'
end
add_index :posts, :title, using: :gin
add_index :posts, :content, using: :gin
In this case there are two fields that will get two different data.
The first is a title (max 200 chars), and the second is the content of the post (3000+ chars).
The doubt arise from the fact that rails gets the whole columns (3000+ chars) with all the translations at once from the db ({it: ..., es: ..., en: ..., ...}).
Even if, in the end, only one of the locale will be used.
In the case of the title looks ok, while for the content it could be an useless amount of data retrived (imagine 10+ locales).
I would have preferred to get only the required locale from the db (through something like: SELECT content->':locale' as content) but it's not currently possible with rails.
Is this a good practice? Is this a case where using :table (or also :key_value) as backend is preferred, even with postgres? Am I missing something or overthinking over a non-problem?
Ps. I am sorry in advance if this is not the appropriate place where discuss about this.
When I'm doing the following:
Mobility.with_locale(:de) do
visit page_path(@page)
end
...then the default locale (en) is still active. It only works like so:
visit page_path(@page, locale: :de)
Rails 5 introduced the saved_changes
method to the Dirty module, but it seems like Mobility
still doesn't support that:
class Post
include Mobility
translates :translated_attribute, dirty: true
end
post = Post.first
post.translated_attribute = "test"
post.not_a_translated_attribute = "test"
post.changes
# => { "translated_attribute" => ["", "test"], "not_a_translated_attribute" => ["", test] }
post.save!
post.saved_changed
# => { "not_a_translated_attribute" => ["", "test"] }
It would be great if saved_changes
also returned the translated attributes.
Thank you.
There appears to be a bug on line 257.
Lines 248 to 257 in 07a6863
Rails was crashing when autoloading a model on @mobility.dup
, as @mobility
was nil. Because this is a loading issue, I don't think it is easy to reproduce. I'm guessing you mean mobility.dup
, which appears to resolve the issue for me.
Mobility's :table
backend is very similar to globalize
. globalize
also has rails_admin_globalize_field gem which allows easy integration with rails_admin. The only thing that currently doesn't work is setting accepts_nested_attributes_for :translations, allow_destroy: true
on the model that accepts translation (as instructed in usage). When set up, ActiveRecord complains about no association:
/home/quintasan/.rvm/gems/ruby-2.4.1@domore/gems/activerecord-5.1.3/lib/active_record/nested_attributes.rb:337:in `block in accepts_nested_attributes_for': No association found for name `translations'. Has it been defined yet? (ArgumentError)
Is there any way mobility can set this association when using :table
backend or is there any way I can fool ActiveRecord into accepting that?
The required column of ancestry that records the hierarchy will be null after loading all of categories. I can not figure out the reason.
any idea? Thanks.
i.e.
Mobility.locale = :en
Post.i18n.where(title: "foo")
...should return the post with title "foo" in English if a post with title in English exists, if not it should find a post with title "foo" in one of the fallback locales defined for the title
attributes.
validates :name, presence: true, uniqueness: true
translates :name
xxxx.name does not exist
There are some specs failing when running against Sequel 5.0 that need to be fixed. We can release Sequel 5.0 compatibility in 0.3.0.
One problem I have with the Moblity Gem is that when I wanted to display the translated attributes of all records, a separate call is made to the database for each record
(OR maybe I don't know how to do it correctly; if this is the case, please let me know)
I tried writing a helper method myself, which can be found here:
http://railsmisadventures.blogspot.hk/2017/10/eliminate-n1-calls-using-mobility-gem.html
But I would certainly like this incorporated into the gem itself in someway to make things simpler
ActiveRecord 5.2.beta.2 has just been released, and we are now testing against it (see #112). There are about twenty specs failing currently, mostly around the Dirty plugin.
The main issue seems to be with rails/rails#28661 (fixing rails/rails#28660), which replaced a __send__(attr)
to _attributes(attr)
, and ultimately to _read_attribute(attr)
. This is problematic since while in ActiveModel _read_attribute
simply maps to __send__
, which for a localized accessor like title_en
will correctly return the current value of the title, in AR it maps to this:
def _read_attribute(attr_name, &block) # :nodoc
@attributes.fetch_value(attr_name.to_s, &block)
end
Here, @attributes
will not contain the localized translated attributes, so title_en
here will return nil
. So most of the AR::Dirty specs are failing with changes all coming out as [nil, nil]
instead of e.g. [nil, "foo"]
.
I'm currently investigating if we could override _read_attribute
to do something special for translated attributes, but it's tricky because in the dirty plugin we're using localized attributes to track changes in multiple locales. This is tricky since without a complex regex, we can't really tell if a given attribute passed to _read_attribute
is actually a translated attribute or not.
I don't want to be matching regexes every time _read_attribute
is called since this is a very frequently-used method and regexes are slow, so this could significantly impact performance. For now just tracking the problem here, I'll see if there are other ways to fix the problem.
I noticed when I access a given locale with the table backend, a Translation object is also getting initialized. This means I have two translation records for every object I create.
More specifically, I have an Event
class like the following.
class Event < ApplicationRecord
translates :title, backend: :table
validates :title_en, length: { maximum: 255 }
validates :title_ja, length: { maximum: 255 }
end
Because I have two validations on the length (maybe there's a better way to do this with mobility?), whenever I create an event, I get two translation records. This behavior doesn't appear to be specific to accessing it within the validation context. If I remove those validations, the following spec still fails.
it do
event = Event.new(title: "MyTitle")
expect(event.translations.pluck(:title)).to eq([event.title])
event.title(locale: :ja)
event.save!
# will fail with ["MyTitle", nil]
expect(event.translations.pluck(:title)).to eq([event.title])
end
The plugins don't seem to effect this. Even with the default options, it still happens. Is this behavior intentional?
Returns an error if plugin: :dirty is loaded in Sequel even though its: dirty: true
Using Sequel with jsonb and , dirty: true
on the translates columns.
Well, it should work.
In sequel.rb:
Sequel::Model.plugin :dirty
In model:
translates :title, dirty: true
translates :synopsis, dirty: true
Returns (on Model.new):
2.3.3 :001 > a = Model.new
NoMethodError: undefined method `[]' for nil:NilClass
from /home/vagrant/.rvm/gems/ruby-2.3.3/gems/mobility-0.2.1/lib/mobility/backends/hash_valued.rb:14:in `read'
from /home/vagrant/.rvm/gems/ruby-2.3.3/gems/mobility-0.2.1/lib/mobility/backend/stringify_locale.rb:10:in `read'
from /home/vagrant/.rvm/gems/ruby-2.3.3/gems/mobility-0.2.1/lib/mobility/plugins/cache/translation_cacher.rb:23:in `block in initialize'
from /home/vagrant/.rvm/gems/ruby-2.3.3/gems/mobility-0.2.1/lib/mobility/plugins/fallbacks.rb:99:in `block in define_read'
from /home/vagrant/.rvm/gems/ruby-2.3.3/gems/mobility-0.2.1/lib/mobility/plugins/presence.rb:25:in `read'
from /home/vagrant/.rvm/gems/ruby-2.3.3/gems/mobility-0.2.1/lib/mobility/plugins/default.rb:64:in `block in initialize'
from /home/vagrant/.rvm/gems/ruby-2.3.3/gems/mobility-0.2.1/lib/mobility/attributes.rb:185:in `block in define_reader'
from /home/vagrant/.rvm/gems/ruby-2.3.3/gems/sequel-4.49.0/lib/sequel/plugins/dirty.rb:194:in `change_column_value'
from /home/vagrant/.rvm/gems/ruby-2.3.3/gems/sequel-4.49.0/lib/sequel/model/base.rb:1468:in `[]='
from /home/vagrant/.rvm/gems/ruby-2.3.3/gems/mobility-0.2.1/lib/mobility/backends/sequel/pg_hash.rb:32:in `block (4 levels) in <class:PgHash>'
from /home/vagrant/.rvm/gems/ruby-2.3.3/gems/mobility-0.2.1/lib/mobility/backends/sequel/pg_hash.rb:32:in `each'
from /home/vagrant/.rvm/gems/ruby-2.3.3/gems/mobility-0.2.1/lib/mobility/backends/sequel/pg_hash.rb:32:in `block (3 levels) in <class:PgHash>'
from /home/vagrant/.rvm/gems/ruby-2.3.3/gems/sequel-4.49.0/lib/sequel/model/base.rb:1440:in `initialize'
from /home/vagrant/.rvm/gems/ruby-2.3.3/gems/enumerize-2.1.2/lib/enumerize/base.rb:51:in `initialize'
from (irb):1:in `new'
from (irb):1
from /home/vagrant/.rvm/gems/ruby-2.3.3/gems/hanami-1.0.0/lib/hanami/commands/console.rb:61:in `start'
from /home/vagrant/.rvm/gems/ruby-2.3.3/gems/hanami-1.0.0/lib/hanami/cli.rb:89:in `console'
from /home/vagrant/.rvm/gems/ruby-2.3.3/gems/thor-0.20.0/lib/thor/command.rb:27:in `run'
from /home/vagrant/.rvm/gems/ruby-2.3.3/gems/thor-0.20.0/lib/thor/invocation.rb:126:in `invoke_command'
from /home/vagrant/.rvm/gems/ruby-2.3.3/gems/thor-0.20.0/lib/thor.rb:387:in `dispatch'
from /home/vagrant/.rvm/gems/ruby-2.3.3/gems/thor-0.20.0/lib/thor/base.rb:466:in `start'
from /home/vagrant/.rvm/gems/ruby-2.3.3/gems/hanami-1.0.0/bin/hanami:5:in `<top (required)>'
from /home/vagrant/.rvm/gems/ruby-2.3.3/bin/hanami:22:in `load'
from /home/vagrant/.rvm/gems/ruby-2.3.3/bin/hanami:22:in `<main>'
from /home/vagrant/.rvm/gems/ruby-2.3.3/bin/ruby_executable_hooks:15:in `eval'
from /home/vagrant/.rvm/gems/ruby-2.3.3/bin/ruby_executable_hooks:15:in `<main>'
Enumerize isn't the culprit, Mobility is. If I disable enumerize but keep Mobility it still fails, if I disable Dirty plugin it works, if I disable mobility but keep enumerize it works.
None
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.