armot 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 ADDED
@@ -0,0 +1,11 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
4
+
5
+ Gemfile.lock
6
+ .DS_Store
7
+ test_app/.bundle
8
+ test_app/db/*.sqlite3
9
+ test_app/log/*.log
10
+ test_app/tmp/**/*
11
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in armot.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,87 @@
1
+ Armot
2
+ =====
3
+
4
+ Armot is a minimal rails 3 library for handle your model translations. It's
5
+ heavily based on [puret](https://github.com/jo/puret), by Johannes Jörg
6
+ Schmidt, since it does basically the same but relying on i18n ActiveRecord
7
+ backend to store and fetch translations instead of custom tables.
8
+
9
+ Choosing between puret or armot is as always a decision based on your custom
10
+ requirements:
11
+
12
+ 1. If your application is multilingual, and it's translated with default yaml
13
+ files with i18n Simple backend, you should definitely go with Puret. In this
14
+ scenario your application contents are multilingual but doesn't dynamically
15
+ change, they're always the same.
16
+
17
+ 2. If your application is multilingual, and also you want to give access to
18
+ change it's contents, you might have chose to use another i18n backend like
19
+ activerecord to be able to edit the translations in live. If this is your
20
+ scenario, armot may give you a few advantages:
21
+
22
+ - Your translations are centralized. If you're giving your users (maybe the
23
+ admins of the site) the ability to change it's multilingual contents, it
24
+ means that you already have an interface to edit I18n translations. Use it
25
+ to edit your model translations too.
26
+ - Use of all i18n benefits for free, like fallbacks or speed up model
27
+ translations with Flatten and Memoize.
28
+ - No worry for eager loading translations every time you load translated
29
+ models.
30
+ - Easy setup, no external tables.
31
+
32
+
33
+ Installing armot
34
+ ----------------
35
+
36
+ First add the following line to your Gemfile:
37
+
38
+ gem 'armot'
39
+
40
+ Using armot is pretty straightforward. Just add the following line to any
41
+ model with attributes you want to translate:
42
+
43
+ class Product < ActiveRecord::Base
44
+ armotize :name, :description
45
+ end
46
+
47
+ This will make attributes 'name' and 'description' multilingual for the
48
+ product model.
49
+
50
+ If your application is already in production and with real contents, making an
51
+ attribute armotized won't do any difference. You can expect your models to
52
+ return their old values until you make some translations.
53
+
54
+
55
+ Usage
56
+ -----
57
+
58
+ Your translated model will have different contents for each locale transparently.
59
+
60
+ I18n.locale = :en
61
+
62
+ car = Product.create :name => "A car"
63
+ car.name #=> A car
64
+
65
+ I18n.locale = :es
66
+ car.name = "Un coche"
67
+ car.name #=> Un coche
68
+
69
+ I18n.locale = :en
70
+ car.name #=> A car
71
+
72
+
73
+ Be aware that armot doesn't take care of any cache expiration. If you're using
74
+ Memoize with I18n ActiveRecord backend you must take care yourself to reload
75
+ the backend with an observer, for example.
76
+
77
+
78
+
79
+ Migrating from puret
80
+ --------------------
81
+
82
+ If you want to migrate your current model translations from puret to armot,
83
+ simply run this rake task:
84
+
85
+ rake armot:migrate_puret
86
+
87
+
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rake'
5
+ require 'rake/testtask'
6
+
7
+
8
+ desc 'Default: run unit tests.'
9
+ task :default => :test
10
+
11
+ desc 'Test the puret plugin.'
12
+ Rake::TestTask.new(:test) do |t|
13
+ t.libs << 'lib'
14
+ t.libs << 'test'
15
+ t.pattern = 'test/**/*_test.rb'
16
+ t.verbose = true
17
+ end
data/armot.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "armot/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "armot"
7
+ s.version = Armot::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Roger Campos"]
10
+ s.email = ["roger@itnig.net"]
11
+ s.homepage = "https://github.com/rogercampos/armot"
12
+ s.summary = %q{translation support for your models with an I18n active-record backend}
13
+ s.description = %q{translation support for your models with an I18n active-record backend}
14
+
15
+ s.rubyforge_project = "armot"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_dependency "i18n-active_record", ">= 0.0.2"
23
+ s.add_development_dependency "activerecord", ">= 3.0.0"
24
+ s.add_development_dependency "activesupport", ">= 3.0.0"
25
+ s.add_development_dependency "rake"
26
+ s.add_development_dependency "sqlite3"
27
+
28
+ end
data/lib/armot.rb ADDED
@@ -0,0 +1,9 @@
1
+ if defined?(I18n::Backend::ActiveRecord)
2
+ require 'armot/active_record_extensions'
3
+ end
4
+
5
+ require 'armot/railtie' if defined?(Rails)
6
+
7
+ module Armot
8
+ end
9
+
@@ -0,0 +1,64 @@
1
+ module Armot
2
+ module ActiveRecordExtensions
3
+ module ClassMethods
4
+ def armotize(*attributes)
5
+ make_it_armot! unless included_modules.include?(InstanceMethods)
6
+
7
+ attributes.each do |attribute|
8
+ # attribute setter
9
+ define_method "#{attribute}=" do |value|
10
+ armot_attributes[I18n.locale][attribute] = value
11
+ end
12
+
13
+ # attribute getter
14
+ define_method attribute do
15
+ return armot_attributes[I18n.locale][attribute] if armot_attributes[I18n.locale][attribute]
16
+ return if new_record?
17
+
18
+ begin
19
+ I18n.t "#{attribute}_#{id}", :scope => "armot.#{self.class.to_s.underscore.pluralize}.#{attribute}", :raise => true
20
+ rescue I18n::MissingTranslationData
21
+ self[attribute] || "translation missing: #{I18n.locale}.armot.#{self.class.to_s.underscore.pluralize}.#{attribute}.#{attribute}_#{id}"
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ # configure model
30
+ def make_it_armot!
31
+ include InstanceMethods
32
+
33
+ after_save :update_translations!
34
+ after_destroy :remove_i18n_entries
35
+ end
36
+ end
37
+
38
+ module InstanceMethods
39
+
40
+ private
41
+ def armot_attributes
42
+ @armot_attributes ||= Hash.new { |hash, key| hash[key] = {} }
43
+ end
44
+
45
+ # called after save
46
+ def update_translations!
47
+ return if armot_attributes.blank?
48
+ armot_attributes.each do |locale, attributes|
49
+ attributes.each do |k, v|
50
+ translation = I18n::Backend::ActiveRecord::Translation.find_or_initialize_by_locale_and_key(locale.to_s, "armot.#{self.class.to_s.underscore.pluralize}.#{k}.#{k}_#{id}")
51
+ translation.update_attribute(:value, v)
52
+ end
53
+ end
54
+ end
55
+
56
+ def remove_i18n_entries
57
+ t = I18n::Backend::ActiveRecord::Translation.arel_table
58
+ I18n::Backend::ActiveRecord::Translation.delete_all(t[:key].matches("armot.#{self.class.to_s.underscore.pluralize}%_#{id}"))
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ ActiveRecord::Base.extend Armot::ActiveRecordExtensions::ClassMethods
@@ -0,0 +1,35 @@
1
+ module Armot
2
+ module PuretIntegration
3
+
4
+ # It assumes all of your tables ending with _translations are puret
5
+ # translation tables.
6
+ def self.migrate
7
+
8
+ black_list = ["created_at", "updated_at", "locale", "id"]
9
+
10
+ new_i18n_translations = []
11
+ ActiveRecord::Base.connection.tables.each do |t|
12
+ if t =~ /(\w+)_translations$/
13
+ model = $1
14
+ attributes = ActiveRecord::Base.connection.columns(t).select{|x| !(black_list + ["#{model}_id"]).include?(x.name)}.map{|x| x.name}
15
+
16
+ t.classify.constantize.all.each do |instance|
17
+
18
+ attributes.each do |attr|
19
+ if value = instance.send(attr)
20
+ new_i18n_translations << I18n::Backend::ActiveRecord::Translation.new(
21
+ :locale => instance.locale,
22
+ :value => value,
23
+ :key => "armot.#{model.pluralize}.#{attr}.#{attr}_#{ instance.send("#{model}_id") }")
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ I18n::Backend::ActiveRecord::Translation.transaction do
31
+ new_i18n_translations.each {|x| x.save}
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,7 @@
1
+ module Armot
2
+ class Railtie < Rails::Railtie
3
+ rake_tasks do
4
+ load "tasks/puret_migration.rake"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ module Armot
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,8 @@
1
+ require 'armot/puret_integration'
2
+
3
+ namespace :armot do
4
+ desc 'dump all the existing translations in puret to i18n'
5
+ task :migrate_puret => :environment do
6
+ Armot::PuretIntegration.migrate
7
+ end
8
+ end
@@ -0,0 +1,102 @@
1
+ require 'test_helper'
2
+
3
+ class ArmotTest < ActiveSupport::TestCase
4
+ def setup
5
+ setup_db
6
+ I18n.locale = I18n.default_locale = :en
7
+ Post.create(:title => 'English title')
8
+ end
9
+
10
+ def teardown
11
+ teardown_db
12
+ end
13
+
14
+ test "database setup" do
15
+ assert_equal 1, Post.count
16
+ end
17
+
18
+ test "allow translation" do
19
+ I18n.locale = :de
20
+ Post.first.update_attribute :title, 'Deutscher Titel'
21
+ assert_equal 'Deutscher Titel', Post.first.title
22
+ I18n.locale = :en
23
+ assert_equal 'English title', Post.first.title
24
+ end
25
+
26
+ test "assert fallback to default locale" do
27
+ post = Post.first
28
+ I18n.locale = :sv
29
+ post.title = 'Svensk titel'
30
+ I18n.locale = :en
31
+ assert_equal 'English title', post.title
32
+ I18n.locale = :de
33
+ assert_equal 'English title', post.title
34
+ end
35
+
36
+ test 'validates_presence_of should work' do
37
+ post = Post.new
38
+ assert_equal false, post.valid?
39
+
40
+ post.title = 'English title'
41
+ assert_equal true, post.valid?
42
+ end
43
+
44
+ test 'temporary locale switch should not clear changes' do
45
+ I18n.locale = :de
46
+ post = Post.first
47
+ post.text = 'Deutscher Text'
48
+ assert !post.title.blank?
49
+ assert_equal 'Deutscher Text', post.text
50
+ end
51
+
52
+ test 'temporary locale switch should work like expected' do
53
+ post = Post.new
54
+ post.title = 'English title'
55
+ I18n.locale = :de
56
+ post.title = 'Deutscher Titel'
57
+ post.save
58
+ assert_equal 'Deutscher Titel', post.title
59
+ I18n.locale = :en
60
+ assert_equal 'English title', post.title
61
+ end
62
+
63
+ test 'should remove unused I18n translation entries when instance is removed' do
64
+ post = Post.first
65
+ post.title = "English title"
66
+ post.text = "Some text"
67
+ post.save!
68
+ assert_equal 2, I18n::Backend::ActiveRecord::Translation.count
69
+ post.destroy
70
+ assert_equal 0, I18n::Backend::ActiveRecord::Translation.count
71
+ end
72
+
73
+ test "should remove entries for all languages" do
74
+ post = Post.first
75
+ post.title = "English title"
76
+ I18n.locale = :ca
77
+ post.title = "Catalan title"
78
+ post.save!
79
+ assert_equal 2, I18n::Backend::ActiveRecord::Translation.count
80
+ post.destroy
81
+ assert_equal 0, I18n::Backend::ActiveRecord::Translation.count
82
+ end
83
+
84
+ test "should not remove other instance translations" do
85
+ post = Post.first
86
+ post.title = "English title"
87
+ post.save!
88
+ post2 = Post.create! :title => "Other english title"
89
+ assert_equal 2, I18n::Backend::ActiveRecord::Translation.count
90
+ post.destroy
91
+ assert_equal 1, I18n::Backend::ActiveRecord::Translation.count
92
+ end
93
+
94
+ test "should return original attribute when there are no translations" do
95
+ post = Post.first
96
+ post[:title] = "original title"
97
+ post.save!
98
+ I18n::Backend::ActiveRecord::Translation.delete_all
99
+
100
+ assert_equal "original title", Post.first.title
101
+ end
102
+ end
@@ -0,0 +1,49 @@
1
+ require 'test_helper'
2
+ require 'armot/puret_integration'
3
+
4
+ class PuretMigrationTest < ActiveSupport::TestCase
5
+ def setup
6
+ setup_db
7
+ I18n.locale = I18n.default_locale = :en
8
+ Post.create :title => "English title"
9
+ Post.create :title => "Second english title"
10
+ I18n::Backend::ActiveRecord::Translation.delete_all
11
+
12
+ PostTranslation.create(:post_id => Post.first.id, :locale => "en", :title => 'English title', :text => "Some text")
13
+ PostTranslation.create(:post_id => Post.last.id, :locale => "en", :title => 'Second english title')
14
+ end
15
+
16
+ def teardown
17
+ teardown_db
18
+ end
19
+
20
+ test "db setup" do
21
+ assert_equal 0, I18n::Backend::ActiveRecord::Translation.count
22
+ assert_equal 2, Post.count
23
+ assert_equal 2, PostTranslation.count
24
+ end
25
+
26
+ test "armot should not work" do
27
+ assert_equal "translation missing: en.armot.posts.title.title_1", Post.first.title
28
+ end
29
+
30
+ test "should create i18n records for exiting puret translations" do
31
+ Armot::PuretIntegration.migrate
32
+
33
+ assert_equal 3, I18n::Backend::ActiveRecord::Translation.count
34
+ end
35
+
36
+ test "translations with armot should work after migrate" do
37
+ Armot::PuretIntegration.migrate
38
+
39
+ assert_equal "English title", Post.first.title
40
+ assert_equal "Second english title", Post.last.title
41
+ end
42
+
43
+ test "non existing translations reamain the same" do
44
+ Armot::PuretIntegration.migrate
45
+
46
+ assert_equal "translation missing: en.armot.posts.text.text_2", Post.last.text
47
+ end
48
+
49
+ end
data/test/schema.rb ADDED
@@ -0,0 +1,27 @@
1
+ ActiveRecord::Schema.define(:version => 1) do
2
+ create_table :posts do |t|
3
+ t.string :title
4
+ t.text :text
5
+
6
+ t.timestamps
7
+ end
8
+
9
+ # I18n ar translations table
10
+ create_table :translations do |t|
11
+ t.string :locale
12
+ t.string :key
13
+ t.text :value
14
+ t.text :interpolations
15
+ t.boolean :is_proc, :default => false
16
+ end
17
+
18
+ # Puret post translations to test the migration process
19
+ create_table :post_translations do |t|
20
+ t.references :post
21
+ t.string :locale
22
+ t.string :title
23
+ t.text :text
24
+ t.timestamps
25
+ end
26
+ end
27
+
@@ -0,0 +1,33 @@
1
+ require 'test/unit'
2
+ require 'i18n'
3
+ require 'i18n/backend/active_record'
4
+
5
+ require 'armot'
6
+ require 'logger'
7
+
8
+ ActiveRecord::Base.logger = Logger.new(nil)
9
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
10
+
11
+ # Allow fallbacks to test production-like behaviour
12
+ I18n::Backend::ActiveRecord.send(:include, I18n::Backend::Fallbacks)
13
+ I18n.backend = I18n::Backend::ActiveRecord.new
14
+
15
+ def setup_db
16
+ ActiveRecord::Migration.verbose = false
17
+ load "schema.rb"
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
+ armotize :title, :text
28
+ validates_presence_of :title
29
+ end
30
+
31
+ # Puret translation model to test migration process
32
+ class PostTranslation < ActiveRecord::Base
33
+ end
metadata ADDED
@@ -0,0 +1,157 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: armot
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Roger Campos
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-03-07 00:00:00 +01:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: i18n-active_record
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 27
30
+ segments:
31
+ - 0
32
+ - 0
33
+ - 2
34
+ version: 0.0.2
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: activerecord
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 7
46
+ segments:
47
+ - 3
48
+ - 0
49
+ - 0
50
+ version: 3.0.0
51
+ type: :development
52
+ version_requirements: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ name: activesupport
55
+ prerelease: false
56
+ requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ hash: 7
62
+ segments:
63
+ - 3
64
+ - 0
65
+ - 0
66
+ version: 3.0.0
67
+ type: :development
68
+ version_requirements: *id003
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ prerelease: false
72
+ requirement: &id004 !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ hash: 3
78
+ segments:
79
+ - 0
80
+ version: "0"
81
+ type: :development
82
+ version_requirements: *id004
83
+ - !ruby/object:Gem::Dependency
84
+ name: sqlite3
85
+ prerelease: false
86
+ requirement: &id005 !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ hash: 3
92
+ segments:
93
+ - 0
94
+ version: "0"
95
+ type: :development
96
+ version_requirements: *id005
97
+ description: translation support for your models with an I18n active-record backend
98
+ email:
99
+ - roger@itnig.net
100
+ executables: []
101
+
102
+ extensions: []
103
+
104
+ extra_rdoc_files: []
105
+
106
+ files:
107
+ - .gitignore
108
+ - Gemfile
109
+ - README.md
110
+ - Rakefile
111
+ - armot.gemspec
112
+ - lib/armot.rb
113
+ - lib/armot/active_record_extensions.rb
114
+ - lib/armot/puret_integration.rb
115
+ - lib/armot/railtie.rb
116
+ - lib/armot/version.rb
117
+ - lib/tasks/puret_migration.rake
118
+ - test/armot_test.rb
119
+ - test/puret_migration_test.rb
120
+ - test/schema.rb
121
+ - test/test_helper.rb
122
+ has_rdoc: true
123
+ homepage: https://github.com/rogercampos/armot
124
+ licenses: []
125
+
126
+ post_install_message:
127
+ rdoc_options: []
128
+
129
+ require_paths:
130
+ - lib
131
+ required_ruby_version: !ruby/object:Gem::Requirement
132
+ none: false
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ hash: 3
137
+ segments:
138
+ - 0
139
+ version: "0"
140
+ required_rubygems_version: !ruby/object:Gem::Requirement
141
+ none: false
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ hash: 3
146
+ segments:
147
+ - 0
148
+ version: "0"
149
+ requirements: []
150
+
151
+ rubyforge_project: armot
152
+ rubygems_version: 1.3.7
153
+ signing_key:
154
+ specification_version: 3
155
+ summary: translation support for your models with an I18n active-record backend
156
+ test_files: []
157
+