galetahub-globalize3 0.2.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/README.textile +206 -0
- data/Rakefile +22 -0
- data/lib/globalize.rb +59 -0
- data/lib/globalize/active_record.rb +13 -0
- data/lib/globalize/active_record/accessors.rb +22 -0
- data/lib/globalize/active_record/act_macro.rb +67 -0
- data/lib/globalize/active_record/adapter.rb +101 -0
- data/lib/globalize/active_record/attributes.rb +27 -0
- data/lib/globalize/active_record/class_methods.rb +125 -0
- data/lib/globalize/active_record/exceptions.rb +19 -0
- data/lib/globalize/active_record/instance_methods.rb +166 -0
- data/lib/globalize/active_record/migration.rb +125 -0
- data/lib/globalize/active_record/translation.rb +37 -0
- data/lib/globalize/engine.rb +17 -0
- data/lib/globalize/utils.rb +142 -0
- data/lib/globalize/versioning.rb +5 -0
- data/lib/globalize/versioning/paper_trail.rb +41 -0
- data/lib/globalize3.rb +1 -0
- data/lib/globalize3/version.rb +3 -0
- data/lib/i18n/missing_translations_log_handler.rb +41 -0
- data/lib/i18n/missing_translations_raise_handler.rb +25 -0
- data/lib/patches/active_record/query_method.rb +35 -0
- data/lib/patches/active_record/xml_attribute_serializer.rb +13 -0
- data/lib/tasks/globalize.rake +13 -0
- data/test/all.rb +1 -0
- data/test/data/models.rb +68 -0
- data/test/data/schema.rb +108 -0
- data/test/globalize3/attributes_test.rb +133 -0
- data/test/globalize3/clone_test.rb +58 -0
- data/test/globalize3/dirty_tracking_test.rb +61 -0
- data/test/globalize3/dynamic_finders_test.rb +171 -0
- data/test/globalize3/fallbacks_test.rb +146 -0
- data/test/globalize3/locale_test.rb +81 -0
- data/test/globalize3/migration_test.rb +156 -0
- data/test/globalize3/set_translations_test.rb +54 -0
- data/test/globalize3/translation_class_test.rb +59 -0
- data/test/globalize3/validations_test.rb +92 -0
- data/test/globalize3/versioning_test.rb +87 -0
- data/test/globalize3_test.rb +159 -0
- data/test/i18n/missing_translations_test.rb +35 -0
- data/test/test_helper.rb +105 -0
- metadata +243 -0
@@ -0,0 +1,37 @@
|
|
1
|
+
module Globalize
|
2
|
+
module ActiveRecord
|
3
|
+
class Translation < ::ActiveRecord::Base
|
4
|
+
attr_accessible :locale
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def with_locales(*locales)
|
8
|
+
# Avoid using "IN" with SQL queries when only using one locale.
|
9
|
+
locales = locales.flatten.map(&:to_s)
|
10
|
+
locales = locales.first if locales.one?
|
11
|
+
where(:locale => locales)
|
12
|
+
end
|
13
|
+
alias with_locale with_locales
|
14
|
+
|
15
|
+
def translated_locales
|
16
|
+
select('DISTINCT locale').map(&:locale)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def locale
|
21
|
+
read_attribute(:locale).to_sym
|
22
|
+
end
|
23
|
+
|
24
|
+
def locale=(locale)
|
25
|
+
write_attribute(:locale, locale.to_s)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Setting this will force polymorphic associations to subclassed objects
|
32
|
+
# to use their table_name rather than the parent object's table name,
|
33
|
+
# which will allow you to get their models back in a more appropriate
|
34
|
+
# format.
|
35
|
+
#
|
36
|
+
# See http://www.ruby-forum.com/topic/159894 for details.
|
37
|
+
Globalize::ActiveRecord::Translation.abstract_class = true
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'rails'
|
3
|
+
require 'globalize'
|
4
|
+
|
5
|
+
module Globalize
|
6
|
+
class Engine < ::Rails::Engine
|
7
|
+
config.before_initialize do
|
8
|
+
ActiveSupport.on_load :active_record do
|
9
|
+
::ActiveRecord::Base.extend(Globalize::ActiveRecord::ActMacro)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
rake_tasks do
|
14
|
+
load "tasks/globalize.rake"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Globalize
|
3
|
+
class Utils
|
4
|
+
class << self
|
5
|
+
def model_dir
|
6
|
+
@model_dir || "app/models"
|
7
|
+
end
|
8
|
+
|
9
|
+
def model_dir=(dir)
|
10
|
+
@model_dir = dir
|
11
|
+
end
|
12
|
+
|
13
|
+
def show_log(action, table_name, name=false, type=false)
|
14
|
+
log = []
|
15
|
+
log << action
|
16
|
+
log << ' ' + table_name.to_s
|
17
|
+
log << '.' + name.to_s if name
|
18
|
+
log << ' as ' + type.to_s if type
|
19
|
+
|
20
|
+
puts log.join
|
21
|
+
end
|
22
|
+
|
23
|
+
# Return a list of the model files to translate. If we have
|
24
|
+
# command line arguments, they're assumed to be either
|
25
|
+
# the underscore or CamelCase versions of model names.
|
26
|
+
# Otherwise we take all the model files in the
|
27
|
+
# model_dir directory.
|
28
|
+
def get_model_files
|
29
|
+
models = ARGV.dup
|
30
|
+
models.shift
|
31
|
+
models.reject!{|m| m.match(/^(.*)=/)}
|
32
|
+
if models.empty?
|
33
|
+
begin
|
34
|
+
Dir.chdir(model_dir) do
|
35
|
+
models = Dir["**/*.rb"]
|
36
|
+
end
|
37
|
+
rescue SystemCallError
|
38
|
+
puts "No models found in directory '#{model_dir}'."
|
39
|
+
exit 1;
|
40
|
+
end
|
41
|
+
end
|
42
|
+
models
|
43
|
+
end
|
44
|
+
|
45
|
+
# Retrieve the classes belonging to the model names we're asked to process
|
46
|
+
# Check for namespaced models in subdirectories as well as models
|
47
|
+
# in subdirectories without namespacing.
|
48
|
+
def get_model_class(file)
|
49
|
+
require File.expand_path("#{model_dir}/#{file}") # this is for non-rails projects, which don't get Rails auto-require magic
|
50
|
+
model = file.gsub(/\.rb$/, '').camelize
|
51
|
+
parts = model.split('::')
|
52
|
+
begin
|
53
|
+
parts.inject(Object) {|klass, part| klass.const_get(part) }
|
54
|
+
rescue LoadError, NameError
|
55
|
+
Object.const_get(parts.last)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# create or delete parent model fields, such as is_locale_{locale}
|
60
|
+
def make_parent_checkers(klass, connect)
|
61
|
+
columns = connect.columns(klass.table_name)
|
62
|
+
|
63
|
+
Globalize.available_locales.each do |locale|
|
64
|
+
name = "is_locale_#{locale}"
|
65
|
+
|
66
|
+
unless columns.map(&:name).include?(name)
|
67
|
+
connect.add_column klass.table_name, name, :boolean, :default => false
|
68
|
+
show_log("add column", klass.table_name, name, :boolean)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def make_up(klass)
|
74
|
+
conn = klass.connection
|
75
|
+
table_name = klass.translations_table_name
|
76
|
+
|
77
|
+
make_parent_checkers(klass, conn)
|
78
|
+
|
79
|
+
if conn.table_exists?(table_name) # translated table exits
|
80
|
+
columns = conn.columns(table_name)
|
81
|
+
|
82
|
+
klass.translated_columns_hash.each do |key, value|
|
83
|
+
columns.each do |column|
|
84
|
+
if column.name.to_sym == key && column.type != value
|
85
|
+
conn.change_column table_name, key, value
|
86
|
+
show_log("change column", table_name, key, value)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
unless columns.map(&:name).include?(key.to_s)
|
91
|
+
conn.add_column table_name, key, value
|
92
|
+
show_log("add column", table_name, key, value)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
columns.each do |column|
|
97
|
+
if !klass.translated_attribute_names.include?(column.name.to_sym) && [:string, :text].include?(column.type) && column.name != "locale"
|
98
|
+
conn.remove_column table_name, column.name
|
99
|
+
show_log("remove column", table_name, column.name)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
else
|
103
|
+
klass.create_translation_table!(klass.translated_columns_hash)
|
104
|
+
show_log("create table", table_name)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def make_down(klass)
|
109
|
+
if klass.connection.table_exists?(klass.translations_table_name)
|
110
|
+
klass.drop_translation_table!
|
111
|
+
show_log("drop table", klass.translations_table_name)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def init(kind)
|
116
|
+
get_model_files.each do |file|
|
117
|
+
begin
|
118
|
+
klass = get_model_class(file)
|
119
|
+
if klass < ::ActiveRecord::Base && !klass.abstract_class? && klass.respond_to?(:translated_attribute_names)
|
120
|
+
case kind
|
121
|
+
when :up then make_up(klass)
|
122
|
+
when :down then make_down(klass)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
rescue Exception => e
|
126
|
+
puts "Unable to #{kind} #{file}: #{e.inspect}"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Convert string "title:string" to hash { :title => :string }
|
132
|
+
# Convert array ["title:string", "content:text"] to hash { :title => :string, :content => :text }
|
133
|
+
def convert_columns(value)
|
134
|
+
[value].flatten.inject({}) do |hash, schema|
|
135
|
+
arr = schema.to_s.split(':')
|
136
|
+
hash[arr.first.to_sym] = (arr.size == 1 ? :text : arr.last.to_sym)
|
137
|
+
hash
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'paper_trail'
|
2
|
+
|
3
|
+
module Globalize
|
4
|
+
module Versioning
|
5
|
+
module PaperTrail
|
6
|
+
# At present this isn't used but we may use something similar in paper trail
|
7
|
+
# shortly, so leaving it around to reference easily.
|
8
|
+
#def versioned_columns
|
9
|
+
#super + self.class.translated_attribute_names
|
10
|
+
#end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
ActiveRecord::Base.class_eval do
|
16
|
+
class << self
|
17
|
+
def has_paper_trail_with_globalize(*args)
|
18
|
+
has_paper_trail_without_globalize(*args)
|
19
|
+
include Globalize::Versioning::PaperTrail
|
20
|
+
end
|
21
|
+
alias_method_chain :has_paper_trail, :globalize
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
Version.class_eval do
|
26
|
+
|
27
|
+
before_save do |version|
|
28
|
+
version.locale = Globalize.locale.to_s
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.locale_conditions_to_sql
|
32
|
+
"locale = '#{Globalize.locale.to_s}'"
|
33
|
+
end
|
34
|
+
|
35
|
+
scope :for_this_locale, lambda{ { :conditions => locale_conditions_to_sql } }
|
36
|
+
|
37
|
+
def sibling_versions_with_locales
|
38
|
+
sibling_versions_without_locales.for_this_locale
|
39
|
+
end
|
40
|
+
alias_method_chain :sibling_versions, :locales
|
41
|
+
end
|
data/lib/globalize3.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'globalize'
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# A simple exception handler that behaves like the default exception handler
|
2
|
+
# but additionally logs missing translations to a given log.
|
3
|
+
#
|
4
|
+
# Useful for identifying missing translations during testing.
|
5
|
+
#
|
6
|
+
# E.g.
|
7
|
+
#
|
8
|
+
# require 'globalize/i18n/missing_translations_log_handler'
|
9
|
+
# I18n.missing_translations_logger = RAILS_DEFAULT_LOGGER
|
10
|
+
# I18n.exception_handler = :missing_translations_log_handler
|
11
|
+
#
|
12
|
+
# To set up a different log file:
|
13
|
+
#
|
14
|
+
# logger = Logger.new("#{RAILS_ROOT}/log/missing_translations.log")
|
15
|
+
# I18n.missing_translations_logger = logger
|
16
|
+
|
17
|
+
module I18n
|
18
|
+
@@missing_translations_logger = nil
|
19
|
+
|
20
|
+
class << self
|
21
|
+
def missing_translations_logger
|
22
|
+
@@missing_translations_logger ||= begin
|
23
|
+
require 'logger' unless defined?(Logger)
|
24
|
+
Logger.new(STDOUT)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def missing_translations_logger=(logger)
|
29
|
+
@@missing_translations_logger = logger
|
30
|
+
end
|
31
|
+
|
32
|
+
def missing_translations_log_handler(exception, locale, key, options)
|
33
|
+
if MissingTranslationData === exception
|
34
|
+
missing_translations_logger.warn(exception.message)
|
35
|
+
return exception.message
|
36
|
+
else
|
37
|
+
raise exception
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# A simple exception handler that behaves like the default exception handler
|
2
|
+
# but also raises on missing translations.
|
3
|
+
#
|
4
|
+
# Useful for identifying missing translations during testing.
|
5
|
+
#
|
6
|
+
# E.g.
|
7
|
+
#
|
8
|
+
# require 'globalize/i18n/missing_translations_raise_handler'
|
9
|
+
# I18n.exception_handler = :missing_translations_raise_handler
|
10
|
+
module I18n
|
11
|
+
class << self
|
12
|
+
def missing_translations_raise_handler(exception, locale, key, options)
|
13
|
+
raise exception
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
I18n.exception_handler = :missing_translations_raise_handler
|
19
|
+
|
20
|
+
ActionView::Helpers::TranslationHelper.module_eval do
|
21
|
+
def translate(key, options = {})
|
22
|
+
I18n.translate(key, options)
|
23
|
+
end
|
24
|
+
alias :t :translate
|
25
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'active_record/attribute_methods/query'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module AttributeMethods
|
5
|
+
module Query
|
6
|
+
def query_attribute(attr_name)
|
7
|
+
unless value = read_attribute(attr_name)
|
8
|
+
false
|
9
|
+
else
|
10
|
+
column = self.class.columns_hash[attr_name]
|
11
|
+
if column.nil?
|
12
|
+
|
13
|
+
# TODO submit a rails patch
|
14
|
+
|
15
|
+
# not sure what active_record tests say but i guess this should mean:
|
16
|
+
# call to_i and check zero? if the value is a Numeric or starts with
|
17
|
+
# a digit, so it can meaningfully be typecasted by to_i
|
18
|
+
|
19
|
+
# if Numeric === value || value !~ /[^0-9]/
|
20
|
+
if Numeric === value || value.to_s =~ /^[0-9]/
|
21
|
+
!value.to_i.zero?
|
22
|
+
else
|
23
|
+
return false if ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value)
|
24
|
+
!value.blank?
|
25
|
+
end
|
26
|
+
elsif column.number?
|
27
|
+
!value.zero?
|
28
|
+
else
|
29
|
+
!value.blank?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'active_record/serializers/xml_serializer'
|
2
|
+
|
3
|
+
ActiveRecord::XmlSerializer::Attribute.class_eval do
|
4
|
+
def compute_type_with_translations
|
5
|
+
klass = @serializable.class
|
6
|
+
if klass.translates? && klass.translated_attribute_names.include?(name.to_sym)
|
7
|
+
:string
|
8
|
+
else
|
9
|
+
compute_type_without_translations
|
10
|
+
end
|
11
|
+
end
|
12
|
+
alias_method_chain :compute_type, :translations
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
namespace :db do
|
2
|
+
namespace :globalize do
|
3
|
+
desc "Synchronize translated columns with database (create/drop table or add/change/remove column)"
|
4
|
+
task :up => :environment do
|
5
|
+
Globalize::Utils.init(:up)
|
6
|
+
end
|
7
|
+
|
8
|
+
desc "Drop all globalize translations tables (but be careful: non-refundable)"
|
9
|
+
task :down => :environment do
|
10
|
+
Globalize::Utils.init(:down)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/test/all.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Dir[File.expand_path('../**/*_test.rb', __FILE__)].each { |file| require file }
|
data/test/data/models.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
# this little innocent class here makes 0.0.9 test fail
|
2
|
+
class Translation < ActiveRecord::Base
|
3
|
+
end
|
4
|
+
|
5
|
+
class Post < ActiveRecord::Base
|
6
|
+
translates :title, :content, :published, :published_at, :versioning => true
|
7
|
+
validates_presence_of :title
|
8
|
+
scope :with_some_title, :conditions => { :title => 'some_title' }
|
9
|
+
end
|
10
|
+
|
11
|
+
class PostTranslation < ActiveRecord::Base
|
12
|
+
def existing_method ; end
|
13
|
+
end
|
14
|
+
|
15
|
+
class ReloadingPost < Post
|
16
|
+
after_create { reload }
|
17
|
+
end
|
18
|
+
|
19
|
+
class Blog < ActiveRecord::Base
|
20
|
+
has_many :posts, :order => 'id ASC'
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
class Validatee < ActiveRecord::Base
|
25
|
+
translates :string
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
class Parent < ActiveRecord::Base
|
30
|
+
translates :content
|
31
|
+
end
|
32
|
+
|
33
|
+
class Child < Parent
|
34
|
+
end
|
35
|
+
|
36
|
+
class Comment < ActiveRecord::Base
|
37
|
+
validates_presence_of :content
|
38
|
+
belongs_to :post
|
39
|
+
end
|
40
|
+
|
41
|
+
class TranslatedComment < Comment
|
42
|
+
translates :content
|
43
|
+
end
|
44
|
+
|
45
|
+
class User < ActiveRecord::Base
|
46
|
+
translates :name
|
47
|
+
validates_presence_of :name, :email
|
48
|
+
end
|
49
|
+
|
50
|
+
class Migrated < ActiveRecord::Base
|
51
|
+
translates :name
|
52
|
+
end
|
53
|
+
|
54
|
+
class MigratedWithUltraLongModelName < ActiveRecord::Base
|
55
|
+
translates :name
|
56
|
+
end
|
57
|
+
|
58
|
+
class UppercaseTableName < ActiveRecord::Base
|
59
|
+
set_table_name "UPPERCASE_TABLE_NAME"
|
60
|
+
translates :name
|
61
|
+
end
|
62
|
+
|
63
|
+
class Untranslated < ActiveRecord::Base
|
64
|
+
end
|
65
|
+
|
66
|
+
class Task < ActiveRecord::Base
|
67
|
+
translates :name, :fallbacks_for_empty_translations => true
|
68
|
+
end
|