model_translations 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +22 -0
- data/MIT-LICENSE +20 -0
- data/README.markdown +98 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/init.rb +1 -0
- data/lib/model_translations.rb +70 -0
- data/model_translations.gemspec +52 -0
- data/rails/init.rb +3 -0
- data/test/model_translations_test.rb +89 -0
- data/test/test_helper.rb +16 -0
- metadata +67 -0
data/.gitignore
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 Jan Andersson
|
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.
|
data/README.markdown
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
# ModelTranslations
|
2
|
+
|
3
|
+
Minimal implementation of Globalize2 style model translations. Rails 2.2 is
|
4
|
+
required.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
### Gem:
|
9
|
+
|
10
|
+
in config/environment.rb:
|
11
|
+
|
12
|
+
config.gem 'model_translations', :source => 'http://gemcutter.org'
|
13
|
+
|
14
|
+
### Plugin:
|
15
|
+
|
16
|
+
script/plugin install git://github.com/guillaumegentil/model_translations.git
|
17
|
+
|
18
|
+
## Implementation
|
19
|
+
|
20
|
+
class Post < ActiveRecord::Base
|
21
|
+
translates :title, :text
|
22
|
+
end
|
23
|
+
|
24
|
+
Allows you to translate values for the attributes :title and :text per locale:
|
25
|
+
|
26
|
+
I18n.locale = :en
|
27
|
+
post.title # ModelTranslations rocks!
|
28
|
+
I18n.locale = :sv
|
29
|
+
post.title # Rockar fett!
|
30
|
+
|
31
|
+
In order to make this work you need to take care of creating the appropriate
|
32
|
+
database migrations manually. The migration for the above Post model could look
|
33
|
+
like this:
|
34
|
+
|
35
|
+
class CreatePosts < ActiveRecord::Migration
|
36
|
+
def self.up
|
37
|
+
create_table :posts do |t|
|
38
|
+
t.timestamps
|
39
|
+
end
|
40
|
+
create_table :post_translations do |t|
|
41
|
+
t.string :locale
|
42
|
+
t.references :post
|
43
|
+
t.string :title
|
44
|
+
t.text :text
|
45
|
+
t.timestamps
|
46
|
+
end
|
47
|
+
end
|
48
|
+
def self.down
|
49
|
+
drop_table :posts
|
50
|
+
drop_table :post_translations
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
To migrate from a model with existing attributes to one with translated
|
55
|
+
attributes the migration could look like this.
|
56
|
+
|
57
|
+
class RemoveTitleTextFromPosts < ActiveRecord::Migration
|
58
|
+
def self.up
|
59
|
+
[:title, :text].each do |attribute|
|
60
|
+
Post.all.each{|post| post.update_attribute(attribute, post.read_attribute(attribute)) }
|
61
|
+
remove_column :post, attribute
|
62
|
+
end
|
63
|
+
end
|
64
|
+
def self.down
|
65
|
+
add_column :post, :title, :string
|
66
|
+
add_column :post, :text, :text
|
67
|
+
[:title, :text].each do |attribute|
|
68
|
+
Post.all.each{|post| post.write_attribute(attribute, post.send(attribute)); post.save}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
## Advanced Querying
|
74
|
+
|
75
|
+
All models that have translations are hooked up with a has_many :translations
|
76
|
+
association for their corresponding translation table. Use this to your advantage.
|
77
|
+
|
78
|
+
Note that the following example requires Rails 2.3 since default_scope is used.
|
79
|
+
|
80
|
+
class Post < ActiveRecord::Base
|
81
|
+
translates :title, :text
|
82
|
+
|
83
|
+
default_scope :include => :translations
|
84
|
+
|
85
|
+
named_scope :translated, lambda { { :conditions => { 'post_translations.locale' => I18n.locale.to_s } } }
|
86
|
+
named_scope :ordered_by_title, :order => 'post_translations.title'
|
87
|
+
named_scope :with_title, lambda { |title| { :conditions => { 'post_translations.title' => title } } }
|
88
|
+
end
|
89
|
+
|
90
|
+
Post.translated.ordered_by_title # All posts with the current locale sorted on title
|
91
|
+
Post.with_title('My translated title') # Equivalent to Post.find_all_by_title
|
92
|
+
|
93
|
+
As you can see including the model_translations on all querys by default gives
|
94
|
+
us (apart from reducing the number of querys to the database) the possibility
|
95
|
+
of using the post_translations table for further query customization.
|
96
|
+
|
97
|
+
|
98
|
+
Copyright (c) 2008 Jan Andersson, released under the MIT license
|
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "model_translations"
|
8
|
+
gem.summary = "ActiveRecord model translations"
|
9
|
+
gem.description = "Minimal implementation of Globalize2 style model translations. Rails 2.2 is required."
|
10
|
+
gem.email = "thibaud@thibaud.me"
|
11
|
+
gem.homepage = "http://github.com/guillaumegentil/model_translations"
|
12
|
+
gem.authors = ["Jan Andersson", "Thibaud Guillaume-Gentil"]
|
13
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
14
|
+
gem.files.include %w(rails/init.rb)
|
15
|
+
end
|
16
|
+
Jeweler::GemcutterTasks.new
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'rake/testtask'
|
22
|
+
Rake::TestTask.new(:test) do |test|
|
23
|
+
test.libs << 'lib' << 'test'
|
24
|
+
test.pattern = 'test/**/*_test.rb'
|
25
|
+
test.verbose = true
|
26
|
+
end
|
27
|
+
|
28
|
+
begin
|
29
|
+
require 'rcov/rcovtask'
|
30
|
+
Rcov::RcovTask.new do |test|
|
31
|
+
test.libs << 'test'
|
32
|
+
test.pattern = 'test/**/*_test.rb'
|
33
|
+
test.verbose = true
|
34
|
+
end
|
35
|
+
rescue LoadError
|
36
|
+
task :rcov do
|
37
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
task :test => :check_dependencies
|
42
|
+
|
43
|
+
task :default => :test
|
44
|
+
|
45
|
+
require 'rake/rdoctask'
|
46
|
+
Rake::RDocTask.new do |rdoc|
|
47
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
48
|
+
|
49
|
+
rdoc.rdoc_dir = 'rdoc'
|
50
|
+
rdoc.title = "model_translations #{version}"
|
51
|
+
rdoc.rdoc_files.include('README*')
|
52
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
53
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/rails/init"
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module ModelTranslations
|
2
|
+
def translates(*attributes)
|
3
|
+
attributes = attributes.collect{ |attribute| attribute.to_sym }
|
4
|
+
|
5
|
+
add_translation_model_and_logic(attributes) unless included_modules.include?(InstanceMethods)
|
6
|
+
add_translatable_attributes(attributes)
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
def add_translation_model_and_logic(attributes)
|
11
|
+
type = self.to_s.underscore
|
12
|
+
translation_class_name = "#{self.to_s}Translation"
|
13
|
+
translation_class = Class.new(ActiveRecord::Base) { belongs_to type.to_sym }
|
14
|
+
Object.const_set(translation_class_name, translation_class)
|
15
|
+
|
16
|
+
include InstanceMethods
|
17
|
+
|
18
|
+
has_many :translations, :class_name => translation_class_name, :dependent => :delete_all , :order => 'created_at desc'
|
19
|
+
|
20
|
+
before_validation :clear_cached_translations
|
21
|
+
after_save :update_translations!
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_translatable_attributes(attributes)
|
25
|
+
attributes.each do |attribute|
|
26
|
+
define_method "#{attribute}=" do |value|
|
27
|
+
translated_attributes[attribute] = value
|
28
|
+
end
|
29
|
+
|
30
|
+
define_method attribute do
|
31
|
+
Rails.cache.fetch "#{self.class.to_s.downcase}_translations.#{id}.#{attribute}.#{I18n.locale}" do
|
32
|
+
if translated_attributes[attribute]
|
33
|
+
translated_attributes[attribute]
|
34
|
+
elsif !new_record?
|
35
|
+
translation = translations.detect { |t| t.locale == I18n.locale.to_s } ||
|
36
|
+
translations.detect { |t| t.locale == I18n.default_locale.to_s } ||
|
37
|
+
translations.first
|
38
|
+
translation ? translation[attribute] : nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
module InstanceMethods
|
46
|
+
def translated_attributes
|
47
|
+
@translated_attributes ||= {}
|
48
|
+
end
|
49
|
+
|
50
|
+
# before_validation
|
51
|
+
def clear_cached_translations
|
52
|
+
# clear related cached
|
53
|
+
translated_attributes.each do |attribute|
|
54
|
+
Rails.cache.delete "#{self.class.to_s.downcase}_translations.#{id}.#{attribute.first}.#{I18n.locale}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# after_save
|
59
|
+
def update_translations!
|
60
|
+
unless translated_attributes.empty?
|
61
|
+
# update or create translation
|
62
|
+
translation = translations.find_or_initialize_by_locale(I18n.locale.to_s)
|
63
|
+
translated_attributes.each do |attribute, translation_string|
|
64
|
+
translation.send("#{attribute}=", translation_string)
|
65
|
+
end
|
66
|
+
translation.save!
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{model_translations}
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Jan Andersson", "Thibaud Guillaume-Gentil"]
|
12
|
+
s.date = %q{2009-11-16}
|
13
|
+
s.description = %q{Minimal implementation of Globalize2 style model translations. Rails 2.2 is required.}
|
14
|
+
s.email = %q{thibaud@thibaud.me}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README.markdown"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
".gitignore",
|
20
|
+
"MIT-LICENSE",
|
21
|
+
"README.markdown",
|
22
|
+
"Rakefile",
|
23
|
+
"VERSION",
|
24
|
+
"init.rb",
|
25
|
+
"lib/model_translations.rb",
|
26
|
+
"model_translations.gemspec",
|
27
|
+
"rails/init.rb",
|
28
|
+
"rails/init.rb",
|
29
|
+
"test/model_translations_test.rb",
|
30
|
+
"test/test_helper.rb"
|
31
|
+
]
|
32
|
+
s.homepage = %q{http://github.com/guillaumegentil/model_translations}
|
33
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
34
|
+
s.require_paths = ["lib"]
|
35
|
+
s.rubygems_version = %q{1.3.5}
|
36
|
+
s.summary = %q{ActiveRecord model translations}
|
37
|
+
s.test_files = [
|
38
|
+
"test/model_translations_test.rb",
|
39
|
+
"test/test_helper.rb"
|
40
|
+
]
|
41
|
+
|
42
|
+
if s.respond_to? :specification_version then
|
43
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
44
|
+
s.specification_version = 3
|
45
|
+
|
46
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
47
|
+
else
|
48
|
+
end
|
49
|
+
else
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
data/rails/init.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper.rb'
|
2
|
+
|
3
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => ":memory:")
|
4
|
+
|
5
|
+
def setup_db
|
6
|
+
ActiveRecord::Schema.define(:version => 1) do
|
7
|
+
create_table :posts do |t|
|
8
|
+
t.timestamps
|
9
|
+
end
|
10
|
+
create_table :post_translations do |t|
|
11
|
+
t.string :locale
|
12
|
+
t.references :post
|
13
|
+
t.string :title
|
14
|
+
t.text :text
|
15
|
+
t.timestamps
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def teardown_db
|
21
|
+
ActiveRecord::Base.connection.tables.each do |table|
|
22
|
+
ActiveRecord::Base.connection.drop_table(table)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Post < ActiveRecord::Base
|
27
|
+
translates :title, :text
|
28
|
+
validates_presence_of :title
|
29
|
+
end
|
30
|
+
|
31
|
+
class ModelTranslationsTest < ActiveSupport::TestCase
|
32
|
+
|
33
|
+
def setup
|
34
|
+
setup_db
|
35
|
+
I18n.locale = I18n.default_locale = :en
|
36
|
+
Post.create(:title => 'English title', :text => 'Text')
|
37
|
+
end
|
38
|
+
|
39
|
+
def teardown
|
40
|
+
teardown_db
|
41
|
+
end
|
42
|
+
|
43
|
+
test "database setup" do
|
44
|
+
assert Post.count == 1
|
45
|
+
end
|
46
|
+
|
47
|
+
test "allow translation" do
|
48
|
+
I18n.locale = :sv
|
49
|
+
Post.first.update_attribute :title, 'Svensk titel'
|
50
|
+
assert_equal 'Svensk titel', Post.first.title
|
51
|
+
I18n.locale = :en
|
52
|
+
assert_equal 'English title', Post.first.title
|
53
|
+
end
|
54
|
+
|
55
|
+
test "assert fallback to default" do
|
56
|
+
assert Post.first.title == 'English title'
|
57
|
+
I18n.locale = :sv
|
58
|
+
assert Post.first.title == 'English title'
|
59
|
+
end
|
60
|
+
|
61
|
+
test "parent has_many translations" do
|
62
|
+
assert_equal PostTranslation, Post.first.translations.first.class
|
63
|
+
end
|
64
|
+
|
65
|
+
test "translations are deleted when parent is destroyed" do
|
66
|
+
I18n.locale = :sv
|
67
|
+
Post.first.update_attribute :title, 'Svensk titel'
|
68
|
+
assert_equal 2, PostTranslation.count
|
69
|
+
|
70
|
+
Post.destroy_all
|
71
|
+
assert_equal 0, PostTranslation.count
|
72
|
+
end
|
73
|
+
|
74
|
+
test 'validates_presence_of should work' do
|
75
|
+
post = Post.new
|
76
|
+
assert_equal false, post.valid?
|
77
|
+
|
78
|
+
post.title = 'English title'
|
79
|
+
assert_equal true, post.valid?
|
80
|
+
end
|
81
|
+
|
82
|
+
test 'temporary locale switch should not clear changes' do
|
83
|
+
I18n.locale = :sv
|
84
|
+
post = Post.first
|
85
|
+
post.text = 'Svensk text'
|
86
|
+
post.title.blank?
|
87
|
+
assert_equal 'Svensk text', post.text
|
88
|
+
end
|
89
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/test_case'
|
5
|
+
require 'active_record'
|
6
|
+
|
7
|
+
require File.dirname(__FILE__) + '/../lib/model_translations'
|
8
|
+
# Explicitly include the module
|
9
|
+
ActiveRecord::Base.send :extend, ModelTranslations
|
10
|
+
|
11
|
+
# mimic Rails
|
12
|
+
module Rails
|
13
|
+
def self.cache
|
14
|
+
ActiveSupport::Cache.lookup_store(:memory_store)
|
15
|
+
end
|
16
|
+
end
|
metadata
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: model_translations
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jan Andersson
|
8
|
+
- Thibaud Guillaume-Gentil
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2009-11-16 00:00:00 +01:00
|
14
|
+
default_executable:
|
15
|
+
dependencies: []
|
16
|
+
|
17
|
+
description: Minimal implementation of Globalize2 style model translations. Rails 2.2 is required.
|
18
|
+
email: thibaud@thibaud.me
|
19
|
+
executables: []
|
20
|
+
|
21
|
+
extensions: []
|
22
|
+
|
23
|
+
extra_rdoc_files:
|
24
|
+
- README.markdown
|
25
|
+
files:
|
26
|
+
- .gitignore
|
27
|
+
- MIT-LICENSE
|
28
|
+
- README.markdown
|
29
|
+
- Rakefile
|
30
|
+
- VERSION
|
31
|
+
- init.rb
|
32
|
+
- lib/model_translations.rb
|
33
|
+
- model_translations.gemspec
|
34
|
+
- rails/init.rb
|
35
|
+
- test/model_translations_test.rb
|
36
|
+
- test/test_helper.rb
|
37
|
+
has_rdoc: true
|
38
|
+
homepage: http://github.com/guillaumegentil/model_translations
|
39
|
+
licenses: []
|
40
|
+
|
41
|
+
post_install_message:
|
42
|
+
rdoc_options:
|
43
|
+
- --charset=UTF-8
|
44
|
+
require_paths:
|
45
|
+
- lib
|
46
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: "0"
|
51
|
+
version:
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "0"
|
57
|
+
version:
|
58
|
+
requirements: []
|
59
|
+
|
60
|
+
rubyforge_project:
|
61
|
+
rubygems_version: 1.3.5
|
62
|
+
signing_key:
|
63
|
+
specification_version: 3
|
64
|
+
summary: ActiveRecord model translations
|
65
|
+
test_files:
|
66
|
+
- test/model_translations_test.rb
|
67
|
+
- test/test_helper.rb
|