├── .gitignore ├── lib ├── hstore_translate │ ├── version.rb │ ├── translates │ │ ├── active_record_with_hstore_translates.rb │ │ └── instance_methods.rb │ └── translates.rb └── hstore_translate.rb ├── .travis.yml ├── Gemfile ├── Rakefile ├── test ├── database.yml ├── gemfiles │ ├── Gemfile.rails-4.2.x │ └── Gemfile.rails-5.0.x ├── test_helper.rb └── translates_test.rb ├── MIT-LICENSE ├── hstore_translate.gemspec └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .bundle 3 | Gemfile.lock 4 | pkg/* 5 | test/coverage 6 | -------------------------------------------------------------------------------- /lib/hstore_translate/version.rb: -------------------------------------------------------------------------------- 1 | module HstoreTranslate 2 | VERSION = "2.0.0" 3 | end 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | rvm: 2 | - 2.4.0 3 | gemfile: 4 | - test/gemfiles/Gemfile.rails-4.2.x 5 | - test/gemfiles/Gemfile.rails-5.0.x 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | gem 'pg', :platform => :ruby 6 | gem 'activerecord-jdbcpostgresql-adapter', :platform => :jruby 7 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake/testtask' 2 | 3 | Rake::TestTask.new do |t| 4 | t.libs << 'test' 5 | t.pattern = 'test/*_test.rb' 6 | end 7 | 8 | task :default => :test 9 | -------------------------------------------------------------------------------- /test/database.yml: -------------------------------------------------------------------------------- 1 | test: 2 | adapter: postgresql 3 | host: localhost 4 | database: hstore_translate_test 5 | username: postgres 6 | min_messages: warning 7 | -------------------------------------------------------------------------------- /test/gemfiles/Gemfile.rails-4.2.x: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gemspec :path => './../..' 3 | 4 | gem 'activerecord', '~> 4.2.0' 5 | gem 'pg', :platform => :ruby 6 | -------------------------------------------------------------------------------- /test/gemfiles/Gemfile.rails-5.0.x: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gemspec :path => './../..' 3 | 4 | gem 'activerecord', '~> 5.0.0' 5 | gem 'pg', :platform => :ruby 6 | -------------------------------------------------------------------------------- /lib/hstore_translate.rb: -------------------------------------------------------------------------------- 1 | require "active_record" 2 | require "active_record/connection_adapters/postgresql_adapter" 3 | require "hstore_translate/translates" 4 | require "hstore_translate/translates/instance_methods" 5 | require "hstore_translate/translates/active_record_with_hstore_translates" 6 | 7 | module HstoreTranslate 8 | def self.native_hstore? 9 | @native_hstore ||= ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES.key?(:hstore) 10 | end 11 | end 12 | 13 | require 'activerecord-postgres-hstore' unless HstoreTranslate::native_hstore? 14 | 15 | ActiveRecord::Base.extend(HstoreTranslate::Translates) 16 | -------------------------------------------------------------------------------- /lib/hstore_translate/translates/active_record_with_hstore_translates.rb: -------------------------------------------------------------------------------- 1 | module HstoreTranslate 2 | module Translates 3 | module ActiveRecordWithHstoreTranslate 4 | def respond_to?(symbol, include_all = false) 5 | return true if parse_translated_attribute_accessor(symbol) 6 | super(symbol, include_all) 7 | end 8 | 9 | def method_missing(method_name, *args) 10 | translated_attr_name, locale, assigning = parse_translated_attribute_accessor(method_name) 11 | 12 | return super(method_name, *args) unless translated_attr_name 13 | 14 | if assigning 15 | write_hstore_translation(translated_attr_name, args.first, locale) 16 | else 17 | read_hstore_translation(translated_attr_name, locale) 18 | end 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Rob Worley 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /hstore_translate.gemspec: -------------------------------------------------------------------------------- 1 | $:.push File.expand_path('../lib', __FILE__) 2 | require 'hstore_translate/version' 3 | 4 | Gem::Specification.new do |s| 5 | s.name = 'hstore_translate' 6 | s.version = HstoreTranslate::VERSION 7 | s.summary = "Rails I18n library for ActiveRecord model/data translation using PostgreSQL's hstore datatype." 8 | s.description = "#{s.summary} Translations are stored directly in the model table rather than shadow tables." 9 | s.authors = ['Rob Worley', 'Cédric Fabianski'] 10 | s.email = 'cfabianski@me.com' 11 | s.homepage = 'https://github.com/cfabianski/hstore_translate' 12 | s.platform = Gem::Platform::RUBY 13 | s.license = 'MIT' 14 | 15 | s.files = Dir['lib/**/*.rb', 'README.md', 'MIT-LICENSE'] 16 | s.test_files = Dir['test/**/*'] 17 | s.require_paths = ['lib'] 18 | 19 | s.add_dependency 'activerecord', '>= 4.2.0' 20 | 21 | s.add_development_dependency 'rake' 22 | s.add_development_dependency 'minitest', '>= 4.0' 23 | s.add_development_dependency 'database_cleaner' 24 | end 25 | -------------------------------------------------------------------------------- /lib/hstore_translate/translates.rb: -------------------------------------------------------------------------------- 1 | module HstoreTranslate 2 | module Translates 3 | SUFFIX = "_translations".freeze 4 | 5 | def translates(*attrs) 6 | include InstanceMethods 7 | 8 | class_attribute :translated_attribute_names, :permitted_translated_attributes 9 | 10 | self.translated_attribute_names = attrs 11 | self.permitted_translated_attributes = [ 12 | *self.ancestors 13 | .select {|klass| klass.respond_to?(:permitted_translated_attributes) } 14 | .map(&:permitted_translated_attributes), 15 | *attrs.product(I18n.available_locales) 16 | .map { |attribute, locale| :"#{attribute}_#{locale}" } 17 | ].flatten.compact 18 | 19 | attrs.each do |attr_name| 20 | serialize "#{attr_name}#{SUFFIX}", ActiveRecord::Coders::Hstore unless HstoreTranslate::native_hstore? 21 | 22 | define_method attr_name do 23 | read_hstore_translation(attr_name) 24 | end 25 | 26 | define_method "#{attr_name}=" do |value| 27 | write_hstore_translation(attr_name, value) 28 | end 29 | 30 | define_singleton_method "with_#{attr_name}_translation" do |value, locale = I18n.locale| 31 | quoted_translation_store = connection.quote_column_name("#{attr_name}#{SUFFIX}") 32 | where("#{quoted_translation_store} @> hstore(:locale, :value)", locale: locale, value: value) 33 | end 34 | end 35 | 36 | send(:prepend, ActiveRecordWithHstoreTranslate) 37 | end 38 | 39 | def translates? 40 | included_modules.include?(InstanceMethods) 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require 'hstore_translate' 3 | 4 | require 'database_cleaner' 5 | DatabaseCleaner.strategy = :transaction 6 | 7 | I18n.available_locales = [:en, :fr] 8 | 9 | class Post < ActiveRecord::Base 10 | translates :title 11 | end 12 | 13 | class PostDetailed < Post 14 | translates :comment 15 | end 16 | 17 | class HstoreTranslate::Test < Minitest::Test 18 | class << self 19 | def prepare_database 20 | create_database 21 | create_table 22 | end 23 | 24 | private 25 | 26 | def db_config 27 | @db_config ||= begin 28 | filepath = File.join('test', 'database.yml') 29 | YAML.load_file(filepath)['test'] 30 | end 31 | end 32 | 33 | def establish_connection(config) 34 | ActiveRecord::Base.establish_connection(config) 35 | ActiveRecord::Base.connection 36 | end 37 | 38 | def create_database 39 | system_config = db_config.merge('database' => 'postgres', 'schema_search_path' => 'public') 40 | connection = establish_connection(system_config) 41 | connection.create_database(db_config['database']) rescue nil 42 | enable_extension 43 | end 44 | 45 | def enable_extension 46 | connection = establish_connection(db_config) 47 | unless connection.select_value("SELECT proname FROM pg_proc WHERE proname = 'akeys'") 48 | if connection.send(:postgresql_version) < 90100 49 | pg_sharedir = `pg_config --sharedir`.strip 50 | hstore_script_path = File.join(pg_sharedir, 'contrib', 'hstore.sql') 51 | connection.execute(File.read(hstore_script_path)) 52 | else 53 | connection.execute('CREATE EXTENSION IF NOT EXISTS hstore') 54 | end 55 | end 56 | end 57 | 58 | def create_table 59 | connection = establish_connection(db_config) 60 | connection.create_table(:posts, :force => true) do |t| 61 | t.column :title_translations, 'hstore' 62 | t.column :comment_translations, 'hstore' 63 | end 64 | end 65 | end 66 | 67 | prepare_database 68 | 69 | def setup 70 | I18n.available_locales = ['en', 'en-US', 'fr'] 71 | I18n.config.enforce_available_locales = true 72 | DatabaseCleaner.start 73 | end 74 | 75 | def teardown 76 | DatabaseCleaner.clean 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /lib/hstore_translate/translates/instance_methods.rb: -------------------------------------------------------------------------------- 1 | module HstoreTranslate 2 | module Translates 3 | module InstanceMethods 4 | def disable_fallback 5 | toggle_fallback(false) 6 | end 7 | 8 | def enable_fallback 9 | toggle_fallback(true) 10 | end 11 | 12 | protected 13 | 14 | attr_reader :enabled_fallback 15 | 16 | def hstore_translate_fallback_locales(locale) 17 | return locale if enabled_fallback == false || !I18n.respond_to?(:fallbacks) 18 | I18n.fallbacks[locale] 19 | end 20 | 21 | def read_hstore_translation(attr_name, locale = I18n.locale) 22 | translations = public_send("#{attr_name}#{SUFFIX}") || {} 23 | available = Array(hstore_translate_fallback_locales(locale)).detect do |available_locale| 24 | translations[available_locale.to_s].present? 25 | end 26 | 27 | translations[available.to_s] 28 | end 29 | 30 | def write_hstore_translation(attr_name, value, locale = I18n.locale) 31 | translation_store = "#{attr_name}#{SUFFIX}" 32 | translations = public_send(translation_store) || {} 33 | public_send("#{translation_store}_will_change!") unless translations[locale.to_s] == value 34 | translations[locale.to_s] = value 35 | public_send("#{translation_store}=", translations) 36 | value 37 | end 38 | 39 | def respond_to_with_translates?(symbol, include_all = false) 40 | return true if parse_translated_attribute_accessor(symbol) 41 | respond_to_without_translates?(symbol, include_all) 42 | end 43 | 44 | def method_missing_with_translates(method_name, *args) 45 | translated_attr_name, locale, assigning = parse_translated_attribute_accessor(method_name) 46 | 47 | return method_missing_without_translates(method_name, *args) unless translated_attr_name 48 | 49 | if assigning 50 | write_hstore_translation(translated_attr_name, args.first, locale) 51 | else 52 | read_hstore_translation(translated_attr_name, locale) 53 | end 54 | end 55 | 56 | # Internal: Parse a translated convenience accessor name. 57 | # 58 | # method_name - The accessor name. 59 | # 60 | # Examples 61 | # 62 | # parse_translated_attribute_accessor("title_en=") 63 | # # => [:title, :en, true] 64 | # 65 | # parse_translated_attribute_accessor("title_fr") 66 | # # => [:title, :fr, false] 67 | # 68 | # Returns the attribute name Symbol, locale Symbol, and a Boolean 69 | # indicating whether or not the caller is attempting to assign a value. 70 | def parse_translated_attribute_accessor(method_name) 71 | return unless /(?[a-z_]+)_(?[a-z]{2})(?=?)\z/ =~ method_name 72 | 73 | translated_attr_name = attribute.to_sym 74 | return unless translated_attribute_names.include?(translated_attr_name) 75 | 76 | locale = locale.to_sym 77 | assigning = assignment.present? 78 | 79 | [translated_attr_name, locale, assigning] 80 | end 81 | 82 | def toggle_fallback(enabled) 83 | if block_given? 84 | old_value = @enabled_fallback 85 | begin 86 | @enabled_fallback = enabled 87 | yield 88 | ensure 89 | @enabled_fallback = old_value 90 | end 91 | else 92 | @enabled_fallback = enabled 93 | end 94 | end 95 | end 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Gem Version](https://badge.fury.io/rb/hstore_translate.svg)](https://badge.fury.io/rb/hstore_translate) 2 | [![Build Status](https://api.travis-ci.org/cfabianski/hstore_translate.png)](https://travis-ci.org/cfabianski/hstore_translate) 3 | [![License](http://img.shields.io/badge/license-mit-brightgreen.svg)](COPYRIGHT) 4 | [![Code Climate](https://codeclimate.com/github/robworley/hstore_translate.png)](https://codeclimate.com/github/cfabianski/hstore_translate) 5 | 6 | # Hstore Translate 7 | 8 | Rails I18n library for ActiveRecord model/data translation using PostgreSQL's 9 | hstore datatype. It provides an interface inspired by 10 | [Globalize3](https://github.com/svenfuchs/globalize3) but removes the need to 11 | maintain separate translation tables. 12 | 13 | ## Requirements 14 | 15 | * ActiveRecord > 4.2.0 16 | * I18n 17 | 18 | ## Installation 19 | 20 | gem install hstore_translate 21 | 22 | When using bundler, put it in your Gemfile: 23 | 24 | ```ruby 25 | source 'https://rubygems.org' 26 | 27 | gem 'activerecord' 28 | gem 'pg', :platform => :ruby 29 | gem 'hstore_translate' 30 | ``` 31 | 32 | ## Model translations 33 | 34 | Model translations allow you to translate your models' attribute values. E.g. 35 | 36 | ```ruby 37 | class Post < ActiveRecord::Base 38 | translates :title, :body 39 | end 40 | ``` 41 | 42 | Allows you to translate the attributes :title and :body per locale: 43 | 44 | ```ruby 45 | I18n.locale = :en 46 | post.title # => This database rocks! 47 | 48 | I18n.locale = :he 49 | post.title # => אתר זה טוב 50 | ``` 51 | 52 | You also have locale-specific convenience methods from [easy_globalize3_accessors](https://github.com/paneq/easy_globalize3_accessors): 53 | 54 | ```ruby 55 | I18n.locale = :en 56 | post.title # => This database rocks! 57 | post.title_he # => אתר זה טוב 58 | ``` 59 | 60 | To find records using translations without constructing hstore queries by hand: 61 | 62 | ```ruby 63 | Post.with_title_translation("This database rocks!") # => # 64 | Post.with_title_translation("אתר זה טוב", :he) # => # 65 | ``` 66 | 67 | In order to make this work, you'll need to define an hstore column for each of 68 | your translated attributes, using the suffix "_translations": 69 | 70 | ```ruby 71 | class CreatePosts < ActiveRecord::Migration 72 | def up 73 | create_table :posts do |t| 74 | t.column :title_translations, 'hstore' 75 | t.column :body_translations, 'hstore' 76 | t.timestamps 77 | end 78 | end 79 | def down 80 | drop_table :posts 81 | end 82 | end 83 | ``` 84 | 85 | ## I18n fallbacks for missing translations 86 | 87 | It is possible to enable fallbacks for missing translations. It will depend 88 | on the configuration setting you have set for I18n translations in your Rails 89 | config. 90 | 91 | You can enable them by adding the next line to `config/application.rb` (or 92 | only `config/environments/production.rb` if you only want them in production) 93 | 94 | ```ruby 95 | config.i18n.fallbacks = true 96 | ``` 97 | 98 | Sven Fuchs wrote a [detailed explanation of the fallback 99 | mechanism](https://github.com/svenfuchs/i18n/wiki/Fallbacks). 100 | 101 | ## Temporarily disable fallbacks 102 | 103 | If you've enabled fallbacks for missing translations, you probably want to disable 104 | them in the admin interface to display which translations the user still has to 105 | fill in. 106 | 107 | From: 108 | 109 | ```ruby 110 | I18n.locale = :en 111 | post.title # => This database rocks! 112 | post.title_nl # => This database rocks! 113 | ``` 114 | 115 | To: 116 | 117 | ```ruby 118 | I18n.locale = :en 119 | post.title # => This database rocks! 120 | post.disable_fallback 121 | post.title_nl # => nil 122 | ``` 123 | 124 | You can also call your code into a block that temporarily disable or enable fallbacks. 125 | 126 | ```ruby 127 | I18n.locale = :en 128 | post.title_nl # => This database rocks! 129 | 130 | post.disable_fallback do 131 | post.title_nl # => nil 132 | end 133 | 134 | post.disable_fallback 135 | post.enable_fallback do 136 | post.title_nl # => This database rocks! 137 | end 138 | ``` 139 | -------------------------------------------------------------------------------- /test/translates_test.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | require 'test_helper' 3 | 4 | class TranslatesTest < HstoreTranslate::Test 5 | def test_assigns_in_current_locale 6 | I18n.with_locale(:en) do 7 | p = Post.new(:title => "English Title") 8 | assert_equal("English Title", p.title_translations['en']) 9 | end 10 | end 11 | 12 | def test_retrieves_in_current_locale 13 | p = Post.new(:title_translations => { "en" => "English Title", "fr" => "Titre français" }) 14 | I18n.with_locale(:fr) do 15 | assert_equal("Titre français", p.title) 16 | end 17 | end 18 | 19 | def test_retrieves_in_current_locale_with_fallbacks 20 | I18n::Backend::Simple.include(I18n::Backend::Fallbacks) 21 | I18n.default_locale = :"en-US" 22 | 23 | p = Post.new(:title_translations => {"en" => "English Title"}) 24 | I18n.with_locale(:fr) do 25 | assert_equal("English Title", p.title) 26 | end 27 | end 28 | 29 | def test_assigns_in_specified_locale 30 | I18n.with_locale(:en) do 31 | p = Post.new(:title_translations => { "en" => "English Title" }) 32 | p.title_fr = "Titre français" 33 | assert_equal("Titre français", p.title_translations["fr"]) 34 | end 35 | end 36 | 37 | def test_persists_changes_in_specified_locale 38 | I18n.with_locale(:en) do 39 | p = Post.create!(:title_translations => { "en" => "Original Text" }) 40 | p.title_en = "Updated Text" 41 | p.save! 42 | assert_equal("Updated Text", Post.last.title_en) 43 | end 44 | end 45 | 46 | def test_retrieves_in_specified_locale 47 | I18n.with_locale(:en) do 48 | p = Post.new(:title_translations => { "en" => "English Title", "fr" => "Titre français" }) 49 | assert_equal("Titre français", p.title_fr) 50 | end 51 | end 52 | 53 | def test_retrieves_in_specified_locale_with_fallbacks 54 | I18n::Backend::Simple.include(I18n::Backend::Fallbacks) 55 | I18n.default_locale = :"en-US" 56 | 57 | p = Post.new(:title_translations => { "en" => "English Title" }) 58 | I18n.with_locale(:fr) do 59 | assert_equal("English Title", p.title_fr) 60 | end 61 | end 62 | 63 | def test_fallback_from_empty_string 64 | I18n::Backend::Simple.include(I18n::Backend::Fallbacks) 65 | I18n.default_locale = :"en-US" 66 | 67 | p = Post.new(:title_translations => { "en" => "English Title", "fr" => "" }) 68 | I18n.with_locale(:fr) do 69 | assert_equal("English Title", p.title_fr) 70 | end 71 | end 72 | 73 | def test_retrieves_in_specified_locale_with_fallback_disabled 74 | I18n::Backend::Simple.include(I18n::Backend::Fallbacks) 75 | I18n.default_locale = :"en-US" 76 | 77 | p = Post.new(:title_translations => { "en" => "English Title" }) 78 | p.disable_fallback 79 | I18n.with_locale(:fr) do 80 | assert_nil p.title_fr 81 | end 82 | end 83 | 84 | def test_retrieves_in_specified_locale_with_fallback_disabled_using_a_block 85 | I18n::Backend::Simple.include(I18n::Backend::Fallbacks) 86 | I18n.default_locale = :"en-US" 87 | 88 | p = Post.new(:title_translations => { "en" => "English Title" }) 89 | p.enable_fallback 90 | 91 | assert_equal("English Title", p.title_fr) 92 | p.disable_fallback { assert_nil p.title_fr } 93 | end 94 | 95 | def test_retrieves_in_specified_locale_with_fallback_reenabled 96 | I18n::Backend::Simple.include(I18n::Backend::Fallbacks) 97 | I18n.default_locale = :"en-US" 98 | 99 | p = Post.new(:title_translations => { "en" => "English Title" }) 100 | p.disable_fallback 101 | p.enable_fallback 102 | I18n.with_locale(:fr) do 103 | assert_equal("English Title", p.title_fr) 104 | end 105 | end 106 | 107 | def test_retrieves_in_specified_locale_with_fallback_reenabled_using_a_block 108 | I18n::Backend::Simple.include(I18n::Backend::Fallbacks) 109 | I18n.default_locale = :"en-US" 110 | 111 | p = Post.new(:title_translations => { "en" => "English Title" }) 112 | p.disable_fallback 113 | 114 | assert_nil(p.title_fr) 115 | p.enable_fallback { assert_equal("English Title", p.title_fr) } 116 | end 117 | 118 | def test_method_missing_delegates 119 | assert_raises(NoMethodError) { Post.new.nonexistant_method } 120 | end 121 | 122 | def test_method_missing_delegates_non_translated_attributes 123 | assert_raises(NoMethodError) { Post.new.other_fr } 124 | end 125 | 126 | def test_persists_translations_assigned_as_hash 127 | p = Post.create!(:title_translations => { "en" => "English Title", "fr" => "Titre français" }) 128 | p.reload 129 | assert_equal({"en" => "English Title", "fr" => "Titre français"}, p.title_translations) 130 | end 131 | 132 | def test_persists_translations_assigned_to_localized_accessors 133 | p = Post.create!(:title_en => "English Title", :title_fr => "Titre français") 134 | p.reload 135 | assert_equal({"en" => "English Title", "fr" => "Titre français"}, p.title_translations) 136 | end 137 | 138 | def test_with_translation_relation 139 | p = Post.create!(:title_translations => { "en" => "Alice in Wonderland", "fr" => "Alice au pays des merveilles" }) 140 | I18n.with_locale(:en) do 141 | assert_equal p.title_en, Post.with_title_translation("Alice in Wonderland").first.try(:title) 142 | end 143 | end 144 | 145 | def test_class_method_translates? 146 | assert_equal true, Post.translates? 147 | assert_equal true, PostDetailed.translates? 148 | end 149 | 150 | def test_translate_post_detailed 151 | p = PostDetailed.create!( 152 | :title_translations => { 153 | "en" => "Alice in Wonderland", 154 | "fr" => "Alice au pays des merveilles" 155 | }, 156 | :comment_translations => { 157 | "en" => "Awesome book", 158 | "fr" => "Un livre unique" 159 | } 160 | ) 161 | 162 | I18n.with_locale(:en) { assert_equal "Awesome book", p.comment } 163 | I18n.with_locale(:en) { assert_equal "Alice in Wonderland", p.title } 164 | I18n.with_locale(:fr) { assert_equal "Un livre unique", p.comment } 165 | I18n.with_locale(:fr) { assert_equal "Alice au pays des merveilles", p.title } 166 | end 167 | 168 | def test_permitted_translated_attributes 169 | assert_equal [:title_en, :title_fr, :comment_en, :comment_fr], PostDetailed.permitted_translated_attributes 170 | end 171 | end 172 | --------------------------------------------------------------------------------