efficient_translations 0.0.1
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.
- data/.gitignore +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +33 -0
- data/README.md +157 -0
- data/Rakefile +12 -0
- data/efficient_translations.gemspec +29 -0
- data/lib/efficient_translations.rb +11 -0
- data/lib/efficient_translations/schema.rb +23 -0
- data/lib/efficient_translations/translates_method.rb +122 -0
- data/lib/efficient_translations/translation_factory.rb +31 -0
- data/lib/efficient_translations/version.rb +3 -0
- data/spec/fixtures/schema.rb +20 -0
- data/spec/lib/efficient_translations/schema_spec.rb +22 -0
- data/spec/lib/efficient_translations/translates_method_spec.rb +141 -0
- data/spec/lib/efficient_translations/translation_factory_spec.rb +59 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/support/fake_schema_adapter.rb +16 -0
- data/spec/support/working_model.rb +3 -0
- metadata +131 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
pkg
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
efficient_translations (0.0.1)
|
5
|
+
activerecord (~> 2.3)
|
6
|
+
activesupport (~> 2.3)
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
activerecord (2.3.11)
|
12
|
+
activesupport (= 2.3.11)
|
13
|
+
activesupport (2.3.11)
|
14
|
+
diff-lcs (1.1.3)
|
15
|
+
rake (0.9.2.2)
|
16
|
+
rspec (2.7.0)
|
17
|
+
rspec-core (~> 2.7.0)
|
18
|
+
rspec-expectations (~> 2.7.0)
|
19
|
+
rspec-mocks (~> 2.7.0)
|
20
|
+
rspec-core (2.7.1)
|
21
|
+
rspec-expectations (2.7.0)
|
22
|
+
diff-lcs (~> 1.1.2)
|
23
|
+
rspec-mocks (2.7.0)
|
24
|
+
sqlite3 (1.3.5)
|
25
|
+
|
26
|
+
PLATFORMS
|
27
|
+
ruby
|
28
|
+
|
29
|
+
DEPENDENCIES
|
30
|
+
efficient_translations!
|
31
|
+
rake
|
32
|
+
rspec
|
33
|
+
sqlite3
|
data/README.md
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
# EfficientTranslations
|
2
|
+
|
3
|
+
EfficientTranslations is a translation library for ActiveRecord models in Rails 2
|
4
|
+
|
5
|
+
I wrote EfficientTranslations because I'm working on several legacy rails apps with performance problems and I cannot migrate to Rails 3.
|
6
|
+
|
7
|
+
EfficientTranslations is inspired to both [Globalize2](https://github.com/joshmh/globalize2) (for models architecture: translations are stored in a separated table) and [Puret](https://github.com/jo/puret) (for cache mechanics).
|
8
|
+
|
9
|
+
|
10
|
+
## Requirements
|
11
|
+
|
12
|
+
- ActiveSupport 2.3.x
|
13
|
+
- ActiveRecord 2.3.x
|
14
|
+
|
15
|
+
|
16
|
+
## Architecture
|
17
|
+
|
18
|
+
The idea is always the same. One table for model records, another table for model translation records.
|
19
|
+
This architecture works well if app languages could change in the future and you don't want to add a column each time a new language is added, or if you have a large number of translations to manage.
|
20
|
+
|
21
|
+
EfficientTranslations is designed to reduce the number of queries done to get model+translations and the amount of data retrieved.
|
22
|
+
|
23
|
+
I don't think it's a perfect solution, indeed *I think it's far to be a perfect solution*, but it's a step forward.
|
24
|
+
|
25
|
+
|
26
|
+
## Translate a Model
|
27
|
+
|
28
|
+
To explain the gem usage we'll use the following use case:
|
29
|
+
|
30
|
+
We need a Product model with two localizable fields:
|
31
|
+
- name (string)
|
32
|
+
- another_field (integer)
|
33
|
+
|
34
|
+
### Migrations
|
35
|
+
|
36
|
+
You can create the translation table using the helper *create_translation_table*. The following:
|
37
|
+
|
38
|
+
create_table :products do |t|
|
39
|
+
t.timestamps
|
40
|
+
end
|
41
|
+
create_translation_table :products, :name => :string, :another_field => :integer
|
42
|
+
|
43
|
+
is equivalent to:
|
44
|
+
|
45
|
+
create_table :products do |t|
|
46
|
+
t.timestamps
|
47
|
+
end
|
48
|
+
create_table :product_translations do |t|
|
49
|
+
t.references :products, :null => false
|
50
|
+
t.string :locale , :null => false
|
51
|
+
t.string :name
|
52
|
+
t.integer :another_field
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
#### Models
|
57
|
+
|
58
|
+
Now we have to modify our Product model as the following:
|
59
|
+
|
60
|
+
class Prouct < ActiveRecord::Base
|
61
|
+
translates :name, :another_field
|
62
|
+
end
|
63
|
+
|
64
|
+
Done! You have the EfficientTranslations power in your hands :-)
|
65
|
+
|
66
|
+
|
67
|
+
## Usage
|
68
|
+
|
69
|
+
### Manage Translations
|
70
|
+
|
71
|
+
product = Product.new
|
72
|
+
I18n.default_locale = :en
|
73
|
+
I18n.locale = :en
|
74
|
+
|
75
|
+
product.name_translation :en # => nil
|
76
|
+
# .name is a wrapper to .name_translation I18n.locale
|
77
|
+
product.name # => nil
|
78
|
+
product.set_name_translation :en, 'Efficient Translations'
|
79
|
+
# no sql query is executed. When possible, a local collection is used
|
80
|
+
product.name # => 'Efficient Translations'
|
81
|
+
|
82
|
+
I18n.locale = :it
|
83
|
+
# when the current locale is not found, the default locale will be used
|
84
|
+
product.name # => 'Efficient Translations'
|
85
|
+
|
86
|
+
# .name= is a wrapper to #set_name_translation I18n.locale
|
87
|
+
product.name = 'Traduzioni Efficienti'
|
88
|
+
product.name # => 'Traduzioni Efficienti'
|
89
|
+
|
90
|
+
product.name_translations # => { :en => 'Efficient Translations', :it => 'Traduzioni Efficienti' }
|
91
|
+
|
92
|
+
# translations are saved in the db
|
93
|
+
product.save!
|
94
|
+
|
95
|
+
# Create a product using nested attributes
|
96
|
+
Product.create! :translations_attributes => [{:locale => I18n.locale.to_s, :name => 'Another'}]
|
97
|
+
Product.last.name # => 'Another'
|
98
|
+
|
99
|
+
### Validators
|
100
|
+
|
101
|
+
The validator *validates_presence_of_default_locale* is provided to prevent a model to be saved without a translation for the default locale. Eg:
|
102
|
+
|
103
|
+
class Product < ActiveRecord::Base
|
104
|
+
translates :name, :another_field
|
105
|
+
validates_presence_of_default_locale
|
106
|
+
end
|
107
|
+
|
108
|
+
### Named Scopes and Performances Overview
|
109
|
+
|
110
|
+
Three named scopes are defined:
|
111
|
+
|
112
|
+
#### with_translations:
|
113
|
+
|
114
|
+
# Fetch products with all translations
|
115
|
+
Product.with_translations
|
116
|
+
|
117
|
+
This will include all the translations record. So in the case you have a product with translations for :en and :it.
|
118
|
+
|
119
|
+
p = Product.with_translations.first
|
120
|
+
p.name # No sql is executed
|
121
|
+
p.name_translation :it # No sql is executed
|
122
|
+
p.name_translation :fr # No sql is executed
|
123
|
+
|
124
|
+
#### with_current_translations:
|
125
|
+
|
126
|
+
# Fetch products with translations for I18n.locale or I18n.default_locale
|
127
|
+
Product.with_current_translation
|
128
|
+
|
129
|
+
This scope will fetch only the translations you usually need when you fetch your models.
|
130
|
+
It's not perfect. Observe the following code to understand why:
|
131
|
+
|
132
|
+
Product.create! :translation_attributes => [
|
133
|
+
{ :locale => :en, :name => 'Product1' },
|
134
|
+
{ :locale => :it, :name => 'Prodotto1' }
|
135
|
+
]
|
136
|
+
Product.create! :translation_attributes => [
|
137
|
+
{ :locale => :it, :name => 'Prodotto2' }
|
138
|
+
]
|
139
|
+
I18n.locale = :en
|
140
|
+
|
141
|
+
# The second product is not included in the result because it doesn't have the I18n.locale or I18n.default_locale translation
|
142
|
+
# To prevent this you can use validates_presence_of_default_locale
|
143
|
+
Product.with_current_translation.size # => 1
|
144
|
+
|
145
|
+
p = Product.with_current_translation.first
|
146
|
+
p.name # => 'Product1'; No qury is executed because we used the named scope
|
147
|
+
# translations collection doesn't contain the 'it' value, so all calls to 'it' translations will return
|
148
|
+
# the I18n.default_locale value
|
149
|
+
p.name_translation :it # => 'Product2'
|
150
|
+
|
151
|
+
# To fetch the 'it' value you have to do the following:
|
152
|
+
p.translations true # reload all translations
|
153
|
+
p.name_translation :it # => 'Prodotto1'
|
154
|
+
|
155
|
+
#### with_translation_for
|
156
|
+
|
157
|
+
This scope behaves like *with_current_translation* but it will use, in order, a locale of your choice or I18n.default_locale to fetch the translations
|
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require 'bundler/gem_tasks'
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
|
5
|
+
desc "Run those specs"
|
6
|
+
task :spec do
|
7
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
8
|
+
t.rspec_opts = %w{--colour --format progress}
|
9
|
+
t.pattern = 'spec/**/*_spec.rb'
|
10
|
+
t.rspec_path = 'bundle exec rspec'
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
if RUBY_VERSION == '1.8.7'
|
3
|
+
$:.unshift File.expand_path("../lib", __FILE__)
|
4
|
+
require "efficient_translations/version"
|
5
|
+
else
|
6
|
+
# ruby 1.9
|
7
|
+
require File.expand_path('../lib/efficient_translations/version', __FILE__)
|
8
|
+
end
|
9
|
+
|
10
|
+
Gem::Specification.new do |gem|
|
11
|
+
gem.authors = ['Nicola Racco']
|
12
|
+
gem.email = ['nicola@nicolaracco.com']
|
13
|
+
gem.description = %q{Translation library for ActiveRecord models in Rails 2}
|
14
|
+
gem.summary = %q{Translation library for ActiveRecord models in Rails 2 with an eye on performances}
|
15
|
+
gem.homepage = ''
|
16
|
+
|
17
|
+
gem.add_development_dependency 'rake'
|
18
|
+
gem.add_development_dependency 'rspec'
|
19
|
+
gem.add_development_dependency 'sqlite3'
|
20
|
+
gem.add_dependency 'activerecord' , '~> 2.3'
|
21
|
+
gem.add_dependency 'activesupport', '~> 2.3'
|
22
|
+
|
23
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
24
|
+
gem.files = `git ls-files`.split("\n")
|
25
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
26
|
+
gem.name = "efficient_translations"
|
27
|
+
gem.require_paths = ["lib"]
|
28
|
+
gem.version = EfficientTranslations::VERSION
|
29
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'active_record'
|
3
|
+
|
4
|
+
dir = File.join File.expand_path(File.dirname __FILE__), 'efficient_translations'
|
5
|
+
require File.join dir, 'version'
|
6
|
+
require File.join dir, 'schema'
|
7
|
+
require File.join dir, 'translation_factory'
|
8
|
+
require File.join dir, 'translates_method'
|
9
|
+
|
10
|
+
::ActiveRecord::ConnectionAdapters::AbstractAdapter.send :include, EfficientTranslations::Schema
|
11
|
+
::ActiveRecord::Base.send :include, EfficientTranslations::TranslatesMethod
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module EfficientTranslations
|
2
|
+
# Holds schema information. To use it, just include its methods
|
3
|
+
# and overwrite the apply_schema method
|
4
|
+
module Schema
|
5
|
+
# Create the translation table for the given model
|
6
|
+
# It creates a table named <model_name>_translations
|
7
|
+
# translation_fields should contain an Hash that specify
|
8
|
+
# the column name to create and its type.
|
9
|
+
# eg. create_translation_table :product, :name => :string, :description => :string
|
10
|
+
def create_translation_table model_name, translation_fields
|
11
|
+
translation_table_name = "#{model_name.to_s}_translations"
|
12
|
+
create_table translation_table_name do |t|
|
13
|
+
t.references model_name, :null => false
|
14
|
+
t.string :locale , :null => false
|
15
|
+
end
|
16
|
+
translation_fields.each do |name, type|
|
17
|
+
add_column translation_table_name, name.to_s, type.to_sym
|
18
|
+
end
|
19
|
+
add_index translation_table_name, "#{model_name}_id"
|
20
|
+
add_index translation_table_name, ["#{model_name}_id", 'locale'], :unique => true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
module EfficientTranslations
|
2
|
+
module TranslatesMethod
|
3
|
+
def self.included base
|
4
|
+
base.extend ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def translates *field_names
|
9
|
+
make_efficient_translatable! unless defined?(translation_model)
|
10
|
+
field_names.each { |field| define_translation_accessors field }
|
11
|
+
end
|
12
|
+
|
13
|
+
def validates_presence_of_default_locale
|
14
|
+
validate :default_locale_required
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def make_efficient_translatable!
|
20
|
+
cattr_accessor :translation_model
|
21
|
+
self.translation_model = TranslationFactory::new_model self
|
22
|
+
|
23
|
+
has_many :translations, :class_name => translation_model.name, :dependent => :destroy
|
24
|
+
accepts_nested_attributes_for :translations
|
25
|
+
|
26
|
+
named_scope :with_translations, :include => :translations
|
27
|
+
named_scope :with_current_translation, lambda {
|
28
|
+
{
|
29
|
+
:include => :translations,
|
30
|
+
:conditions => ["#{translation_model.table_name}.locale = ? OR #{translation_model.table_name}.locale = ?", I18n.locale.to_s, I18n.default_locale.to_s]
|
31
|
+
}
|
32
|
+
}
|
33
|
+
named_scope :with_translation_for, lambda { |locale|
|
34
|
+
{
|
35
|
+
:inlude => :translations,
|
36
|
+
:conditions => ["#{translation_model.table_name}.locale = ? OR #{translation_model.table_name}.locale = ?", locale.to_s, I18n.default_locale.to_s]
|
37
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
after_save :update_translations!
|
41
|
+
|
42
|
+
self.send :include, InstanceMethods
|
43
|
+
end
|
44
|
+
|
45
|
+
def define_translation_accessors field
|
46
|
+
field = field.to_sym
|
47
|
+
class_eval do
|
48
|
+
define_method "#{field}_translation" do |locale|
|
49
|
+
locale = locale.to_sym
|
50
|
+
# search in cache
|
51
|
+
if efficient_translations_attributes[locale][field]
|
52
|
+
efficient_translations_attributes[locale][field]
|
53
|
+
else
|
54
|
+
# search in relationship
|
55
|
+
translation = translations.detect { |t| t.locale.to_sym == locale }
|
56
|
+
if translation
|
57
|
+
translation[field]
|
58
|
+
elsif locale != I18n.default_locale
|
59
|
+
# try to fetch default locale
|
60
|
+
self.send "#{field}_translation", I18n.default_locale
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
define_method "set_#{field}_translation" do |locale, value|
|
66
|
+
locale = locale.to_sym
|
67
|
+
efficient_translations_attributes[locale][field] = value
|
68
|
+
end
|
69
|
+
|
70
|
+
define_method field do
|
71
|
+
self.send "#{field}_translation", I18n.locale
|
72
|
+
end
|
73
|
+
|
74
|
+
define_method "#{field}=" do |value|
|
75
|
+
self.send "set_#{field}_translation", I18n.locale, value
|
76
|
+
end
|
77
|
+
|
78
|
+
define_method("#{field}_translations") do
|
79
|
+
found = {}
|
80
|
+
efficient_translations_attributes.each do |locale, translation|
|
81
|
+
found[locale] = translation[field]
|
82
|
+
end
|
83
|
+
translations.inject(found) do |memo, translation|
|
84
|
+
memo[translation.locale.to_sym] ||= translation[field]
|
85
|
+
memo
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
module InstanceMethods
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
# attributes are stored in @efficient_attributes instance variable via setter
|
97
|
+
def efficient_translations_attributes
|
98
|
+
@efficient_translations_attributes ||= Hash.new { |hash, key| hash[key] = {} }
|
99
|
+
end
|
100
|
+
|
101
|
+
def update_translations!
|
102
|
+
if efficient_translations_attributes.present?
|
103
|
+
translations true #force reload all translations
|
104
|
+
efficient_translations_attributes.each do |locale, attributes|
|
105
|
+
translation = translations.detect { |t| t.locale.to_sym == locale } || begin
|
106
|
+
args = { :locale => locale }
|
107
|
+
args[self.class.translation_model.translatable_relation_field] = self
|
108
|
+
self.class.translation_model.new args
|
109
|
+
end
|
110
|
+
translation.update_attributes! attributes
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def default_locale_required
|
116
|
+
unless translations.detect { |t| t.locale.to_sym == I18n.default_locale }
|
117
|
+
errors.add :translations, "for #{I18n.default_locale} is missing"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module EfficientTranslations
|
2
|
+
module TranslationFactory
|
3
|
+
def self.new_model base_model
|
4
|
+
if base_model.const_defined?(:Translation)
|
5
|
+
base_model.const_get(:Translation)
|
6
|
+
else
|
7
|
+
klass = base_model.const_set(:Translation, Class.new(::ActiveRecord::Base))
|
8
|
+
klass.instance_eval do
|
9
|
+
cattr_accessor :translatable_model, :translatable_relation_field
|
10
|
+
self.translatable_model = base_model
|
11
|
+
self.translatable_relation_field = base_model.name.underscore.gsub '/', '_'
|
12
|
+
|
13
|
+
table_name = "#{base_model.table_name.singularize}_translations"
|
14
|
+
belongs_to translatable_relation_field
|
15
|
+
|
16
|
+
named_scope :for_locale, lambda { |locale|
|
17
|
+
{ :conditions => ['locale = ? OR locale = ?', locale.to_s, I18n.locale.to_s] }
|
18
|
+
}
|
19
|
+
|
20
|
+
before_save :stringify_locale!
|
21
|
+
end
|
22
|
+
klass.class_eval do
|
23
|
+
def stringify_locale!
|
24
|
+
self.locale = locale.to_s
|
25
|
+
end
|
26
|
+
end
|
27
|
+
klass
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
ActiveRecord::Schema.define do
|
2
|
+
create_table 'my_models', :force => true do |t|
|
3
|
+
end
|
4
|
+
|
5
|
+
create_table 'my_model_translations', :force => true do |t|
|
6
|
+
t.integer :my_model_id, :null => false
|
7
|
+
t.string :locale, :null => false
|
8
|
+
t.string :name
|
9
|
+
end
|
10
|
+
|
11
|
+
create_table 'working_models', :force => true do |t|
|
12
|
+
end
|
13
|
+
|
14
|
+
create_table 'working_model_translations', :force => true do |t|
|
15
|
+
t.integer :working_model_id, :null => false
|
16
|
+
t.string :locale, :null => false
|
17
|
+
t.string :name
|
18
|
+
t.string :content
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe EfficientTranslations::Schema do
|
4
|
+
let(:schema) { EfficientTranslations::FakeSchemaAdapter.new }
|
5
|
+
|
6
|
+
describe '#create_translation_table' do
|
7
|
+
it 'should create a table named <model>_translations' do
|
8
|
+
schema.should_receive(:create_table).with('pippo_translations')
|
9
|
+
schema.create_translation_table 'pippo', :name => :string
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'should create the given translation columns' do
|
13
|
+
schema.should_receive(:add_column).with('pippo_translations', 'name', :string)
|
14
|
+
schema.create_translation_table 'pippo', :name => :string
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should create indexes' do
|
18
|
+
schema.should_receive(:add_index).twice
|
19
|
+
schema.create_translation_table 'pippo', :name => :string
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'active_record'
|
3
|
+
|
4
|
+
describe EfficientTranslations do
|
5
|
+
def my_model_class
|
6
|
+
Kernel.silence_warnings do
|
7
|
+
Kernel.const_set :MyModel, Class.new(ActiveRecord::Base)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '::translates' do
|
12
|
+
it 'should be defined in ActiveRecord::Base' do
|
13
|
+
ActiveRecord::Base.should respond_to :translates
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'could be invoked specifing multiple attributes' do
|
17
|
+
model = my_model_class
|
18
|
+
lambda { model.translates :name, :content }.should_not raise_error
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'could be invoked multiple times' do
|
22
|
+
model = my_model_class
|
23
|
+
lambda { model.translates :name }.should_not raise_error
|
24
|
+
lambda { model.translates :content }.should_not raise_error
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should generate the translation model the first time it\'s invoked' do
|
28
|
+
model = my_model_class
|
29
|
+
model.should_receive :make_efficient_translatable!
|
30
|
+
model.translates :name
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should not regenerate the translation model when called multiple times' do
|
34
|
+
model = my_model_class
|
35
|
+
model.translates :name
|
36
|
+
model.should_not_receive :make_efficient_translatable!
|
37
|
+
model.translates :content
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '::validates_presence_of_default_locale' do
|
42
|
+
it 'should be defined in ActiveRecord::Base' do
|
43
|
+
ActiveRecord::Base.should respond_to :validates_presence_of_default_locale
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should prevent saving a model without default locale' do
|
47
|
+
model = my_model_class
|
48
|
+
model.translates :name
|
49
|
+
model.validates_presence_of_default_locale
|
50
|
+
lambda { model.new.save! }.should raise_error ActiveRecord::RecordInvalid
|
51
|
+
inst = model.new :translations_attributes => [{ :locale => :en, :name => 'pippo' }]
|
52
|
+
lambda { inst.save! }.should_not raise_error
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context '::WorkingModel' do
|
57
|
+
it 'should include the translation model' do
|
58
|
+
WorkingModel.translation_model.should be_kind_of(Class)
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'should include the translations relationship' do
|
62
|
+
WorkingModel.new.should respond_to :translations
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should have some utility named scopes' do
|
66
|
+
WorkingModel.should respond_to :with_translations
|
67
|
+
WorkingModel.should respond_to :with_current_translation
|
68
|
+
WorkingModel.should respond_to :with_translation_for
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'should accept nested attributes' do
|
72
|
+
WorkingModel.delete_all
|
73
|
+
WorkingModel.create! :translations_attributes => [{ :locale => :en, :name => 'pippo' }]
|
74
|
+
WorkingModel.first.name.should == 'pippo'
|
75
|
+
end
|
76
|
+
|
77
|
+
describe 'field_translation' do
|
78
|
+
before do
|
79
|
+
@model = WorkingModel.new
|
80
|
+
@model.set_name_translation :en, 'pippo'
|
81
|
+
@model.save!
|
82
|
+
end
|
83
|
+
|
84
|
+
context 'when cache contains the translated value' do
|
85
|
+
before do
|
86
|
+
@model.set_name_translation :en, 'foo'
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'should fetch the value from cache' do
|
90
|
+
@model.name_translation(:en).should == 'foo'
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context 'when cache is empty' do
|
95
|
+
it 'should search in the relationship' do
|
96
|
+
@model = WorkingModel.find @model.id
|
97
|
+
@model.translation_model.create! :working_model_id => @model.id, :locale => 'fr', :name => 'frfr'
|
98
|
+
@model.name_translation(:fr).should == 'frfr'
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context 'when cache is empty and no value is found' do
|
103
|
+
it 'should search for I18n.default_locale if locale != I18n.default_locale' do
|
104
|
+
I18n.default_locale = :en
|
105
|
+
@model.name_translation(:de).should == @model.name_translation(:en)
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'should return nil if locale == I18n.default_locale' do
|
109
|
+
I18n.default_locale = :de
|
110
|
+
@model.name_translation(:de).should be_nil
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe 'set_field_translation' do
|
116
|
+
it 'should only set values in a cache, until save' do
|
117
|
+
model = WorkingModel.create!
|
118
|
+
model.set_name_translation :en, 'pippo'
|
119
|
+
model.translation_model.find_all_by_working_model_id(model.id).should be_empty
|
120
|
+
model.save!
|
121
|
+
model.translation_model.find_all_by_working_model_id(model.id).should_not be_empty
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
describe 'field accessor' do
|
126
|
+
it '<field> should be a wrapper for <field>_translation' do
|
127
|
+
model = WorkingModel.new
|
128
|
+
model.should_receive(:name_translation).with(:en)
|
129
|
+
I18n.locale = :en
|
130
|
+
model.name
|
131
|
+
end
|
132
|
+
|
133
|
+
it '<field>= should be a wrapper for set_<field>_translation' do
|
134
|
+
model = WorkingModel.new
|
135
|
+
model.should_receive(:set_name_translation).with(:en, 'pippo')
|
136
|
+
I18n.locale = :en
|
137
|
+
model.name = 'pippo'
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe EfficientTranslations::TranslationFactory do
|
4
|
+
before :each do
|
5
|
+
Kernel.silence_warnings do
|
6
|
+
@model_class = Kernel.const_set :MyModel, Class.new(ActiveRecord::Base)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '::new_model' do
|
11
|
+
context 'when no translation model is found' do
|
12
|
+
before do
|
13
|
+
@klass = Class.new(ActiveRecord::Base)
|
14
|
+
@model_class.stub :const_set => @klass
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should create a new ::Translation class' do
|
18
|
+
@model_class.should_receive(:const_set).with(:Translation, kind_of(Class)) { |name, klass| klass }
|
19
|
+
EfficientTranslations::TranslationFactory::new_model @model_class
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should define a belongs_to association to the main model' do
|
23
|
+
@klass.should_receive(:belongs_to)
|
24
|
+
EfficientTranslations::TranslationFactory::new_model @model_class
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should assign the translatable model in an accessor' do
|
28
|
+
translation = EfficientTranslations::TranslationFactory::new_model @model_class
|
29
|
+
translation.translatable_model.should == @model_class
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should assign the translatable model field in an accessor' do
|
33
|
+
translation = EfficientTranslations::TranslationFactory::new_model @model_class
|
34
|
+
translation.translatable_relation_field.should == @model_class.name.underscore.gsub('/','_')
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should return the created translation class' do
|
38
|
+
klass = Class.new(ActiveRecord::Base)
|
39
|
+
@model_class.stub :const_set => klass
|
40
|
+
EfficientTranslations::TranslationFactory::new_model(@model_class).should == klass
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'when a translation model is found' do
|
45
|
+
it 'should not create a ::Translation class' do
|
46
|
+
EfficientTranslations::TranslationFactory::new_model @model_class
|
47
|
+
@model_class.should_not_receive(:const_set)
|
48
|
+
EfficientTranslations::TranslationFactory::new_model @model_class
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should return the already defined translation class' do
|
52
|
+
klass = Class.new(ActiveRecord::Base)
|
53
|
+
@model_class.stub :const_set => klass
|
54
|
+
EfficientTranslations::TranslationFactory::new_model @model_class
|
55
|
+
EfficientTranslations::TranslationFactory::new_model(@model_class).should == klass
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Load the Sinatra app
|
2
|
+
require File.dirname(__FILE__) + '/../lib/efficient_translations'
|
3
|
+
|
4
|
+
require 'rspec'
|
5
|
+
|
6
|
+
Dir[File.expand_path(File.join(File.dirname(__FILE__),'support','**','*.rb'))].each {|f| require f}
|
7
|
+
|
8
|
+
RSpec.configure do |conf|
|
9
|
+
conf.before :suite do
|
10
|
+
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
|
11
|
+
require File.join(File.dirname(__FILE__), 'fixtures', 'schema.rb')
|
12
|
+
end
|
13
|
+
end
|
metadata
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: efficient_translations
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Nicola Racco
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-02-08 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
requirement: &70105172048080 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70105172048080
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
requirement: &70105172047020 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70105172047020
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: sqlite3
|
38
|
+
requirement: &70105172046400 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70105172046400
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: activerecord
|
49
|
+
requirement: &70105172045360 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.3'
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70105172045360
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: activesupport
|
60
|
+
requirement: &70105172043440 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ~>
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '2.3'
|
66
|
+
type: :runtime
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70105172043440
|
69
|
+
description: Translation library for ActiveRecord models in Rails 2
|
70
|
+
email:
|
71
|
+
- nicola@nicolaracco.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- .gitignore
|
77
|
+
- Gemfile
|
78
|
+
- Gemfile.lock
|
79
|
+
- README.md
|
80
|
+
- Rakefile
|
81
|
+
- efficient_translations.gemspec
|
82
|
+
- lib/efficient_translations.rb
|
83
|
+
- lib/efficient_translations/schema.rb
|
84
|
+
- lib/efficient_translations/translates_method.rb
|
85
|
+
- lib/efficient_translations/translation_factory.rb
|
86
|
+
- lib/efficient_translations/version.rb
|
87
|
+
- spec/fixtures/schema.rb
|
88
|
+
- spec/lib/efficient_translations/schema_spec.rb
|
89
|
+
- spec/lib/efficient_translations/translates_method_spec.rb
|
90
|
+
- spec/lib/efficient_translations/translation_factory_spec.rb
|
91
|
+
- spec/spec_helper.rb
|
92
|
+
- spec/support/fake_schema_adapter.rb
|
93
|
+
- spec/support/working_model.rb
|
94
|
+
homepage: ''
|
95
|
+
licenses: []
|
96
|
+
post_install_message:
|
97
|
+
rdoc_options: []
|
98
|
+
require_paths:
|
99
|
+
- lib
|
100
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
101
|
+
none: false
|
102
|
+
requirements:
|
103
|
+
- - ! '>='
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
segments:
|
107
|
+
- 0
|
108
|
+
hash: 4009622527868518176
|
109
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
110
|
+
none: false
|
111
|
+
requirements:
|
112
|
+
- - ! '>='
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: '0'
|
115
|
+
segments:
|
116
|
+
- 0
|
117
|
+
hash: 4009622527868518176
|
118
|
+
requirements: []
|
119
|
+
rubyforge_project:
|
120
|
+
rubygems_version: 1.8.10
|
121
|
+
signing_key:
|
122
|
+
specification_version: 3
|
123
|
+
summary: Translation library for ActiveRecord models in Rails 2 with an eye on performances
|
124
|
+
test_files:
|
125
|
+
- spec/fixtures/schema.rb
|
126
|
+
- spec/lib/efficient_translations/schema_spec.rb
|
127
|
+
- spec/lib/efficient_translations/translates_method_spec.rb
|
128
|
+
- spec/lib/efficient_translations/translation_factory_spec.rb
|
129
|
+
- spec/spec_helper.rb
|
130
|
+
- spec/support/fake_schema_adapter.rb
|
131
|
+
- spec/support/working_model.rb
|