globalize2 0.1.0 → 0.2.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.
Files changed (52) hide show
  1. data/.gitignore +2 -1
  2. data/README.textile +27 -143
  3. data/VERSION +1 -1
  4. data/generators/templates/db_backend_migration.rb +3 -3
  5. data/globalize2.gemspec +30 -49
  6. data/init.rb +1 -8
  7. data/lib/globalize.rb +15 -0
  8. data/lib/globalize/active_record.rb +195 -0
  9. data/lib/globalize/active_record/adapter.rb +80 -0
  10. data/lib/globalize/active_record/attributes.rb +25 -0
  11. data/lib/globalize/active_record/migration.rb +40 -0
  12. data/lib/{globalize/i18n → i18n}/missing_translations_log_handler.rb +8 -8
  13. data/lib/{globalize/i18n → i18n}/missing_translations_raise_handler.rb +3 -5
  14. data/test/active_record/fallbacks_test.rb +102 -0
  15. data/test/{model/active_record → active_record}/migration_test.rb +21 -26
  16. data/test/{model/active_record → active_record}/sti_translated_test.rb +4 -30
  17. data/test/active_record/translates_test.rb +87 -0
  18. data/test/active_record/translation_class_test.rb +30 -0
  19. data/test/active_record/validation_tests.rb +75 -0
  20. data/test/active_record_test.rb +451 -0
  21. data/test/data/models.rb +16 -0
  22. data/test/data/schema.rb +23 -7
  23. data/test/i18n/missing_translations_test.rb +6 -6
  24. data/test/test_helper.rb +55 -15
  25. metadata +33 -46
  26. data/lib/globalize/backend/chain.rb +0 -102
  27. data/lib/globalize/backend/pluralizing.rb +0 -37
  28. data/lib/globalize/backend/static.rb +0 -61
  29. data/lib/globalize/load_path.rb +0 -63
  30. data/lib/globalize/locale/fallbacks.rb +0 -63
  31. data/lib/globalize/locale/language_tag.rb +0 -81
  32. data/lib/globalize/model/active_record.rb +0 -56
  33. data/lib/globalize/model/active_record/adapter.rb +0 -100
  34. data/lib/globalize/model/active_record/translated.rb +0 -174
  35. data/lib/globalize/translation.rb +0 -32
  36. data/lib/locale/root.yml +0 -3
  37. data/lib/rails_edge_load_path_patch.rb +0 -40
  38. data/notes.textile +0 -51
  39. data/test/backends/chained_test.rb +0 -175
  40. data/test/backends/pluralizing_test.rb +0 -63
  41. data/test/backends/static_test.rb +0 -147
  42. data/test/data/locale/all.yml +0 -2
  43. data/test/data/locale/de-DE.yml +0 -2
  44. data/test/data/locale/en-US.yml +0 -2
  45. data/test/data/locale/en-US/module.yml +0 -2
  46. data/test/data/locale/fi-FI/module.yml +0 -2
  47. data/test/data/locale/root.yml +0 -0
  48. data/test/load_path_test.rb +0 -49
  49. data/test/locale/fallbacks_test.rb +0 -154
  50. data/test/locale/language_tag_test.rb +0 -130
  51. data/test/model/active_record/translated_test.rb +0 -487
  52. data/test/translation_test.rb +0 -54
@@ -0,0 +1,80 @@
1
+ module Globalize
2
+ module ActiveRecord
3
+ class Adapter
4
+ # The cache caches attributes that already were looked up for read access.
5
+ # The stash keeps track of new or changed values that need to be saved.
6
+ attr_reader :record, :cache, :stash
7
+
8
+ def initialize(record)
9
+ @record = record
10
+ @cache = Attributes.new
11
+ @stash = Attributes.new
12
+ end
13
+
14
+ def fetch(locale, attr_name)
15
+ cache.contains?(locale, attr_name) ?
16
+ cache.read(locale, attr_name) :
17
+ cache.write(locale, attr_name, fetch_attribute(locale, attr_name))
18
+ end
19
+
20
+ def write(locale, attr_name, value)
21
+ stash.write(locale, attr_name, value)
22
+ cache.write(locale, attr_name, value)
23
+ end
24
+
25
+ def save_translations!
26
+ stash.each do |locale, attrs|
27
+ translation = record.translations.find_or_initialize_by_locale(locale.to_s)
28
+ attrs.each { |attr_name, value| translation[attr_name] = value }
29
+ translation.save!
30
+ end
31
+ stash.clear
32
+ end
33
+
34
+ def reset
35
+ cache.clear
36
+ # stash.clear
37
+ end
38
+
39
+ protected
40
+
41
+ def fetch_translation(locale)
42
+ locale = locale.to_sym
43
+ record.translations.loaded? ? record.translations.detect { |t| t.locale == locale } :
44
+ record.translations.by_locale(locale)
45
+ end
46
+
47
+ def fetch_translations(locale)
48
+ # only query if not already included with :include => translations
49
+ record.translations.loaded? ? record.translations :
50
+ record.translations.by_locales(Globalize.fallbacks(locale))
51
+ end
52
+
53
+ def fetch_attribute(locale, attr_name)
54
+ translations = fetch_translations(locale)
55
+ value, requested_locale = nil, locale
56
+
57
+ Globalize.fallbacks(locale).each do |fallback|
58
+ translation = translations.detect { |t| t.locale == fallback }
59
+ value = translation && translation.send(attr_name)
60
+ locale = fallback && break if value
61
+ end
62
+
63
+ set_metadata(value, :locale => locale, :requested_locale => requested_locale)
64
+ value
65
+ end
66
+
67
+ def set_metadata(object, metadata)
68
+ if object.respond_to?(:translation_metadata)
69
+ object.translation_metadata.merge!(meta_data)
70
+ end
71
+ end
72
+
73
+ def translation_metadata_accessor(object)
74
+ return if obj.respond_to?(:translation_metadata)
75
+ class << object; attr_accessor :translation_metadata end
76
+ object.translation_metadata ||= {}
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,25 @@
1
+ # Helper class for storing values per locale. Used by Globalize::Adapter
2
+ # to stash and cache attribute values.
3
+ module Globalize
4
+ module ActiveRecord
5
+ class Attributes < Hash
6
+ def [](locale)
7
+ locale = locale.to_sym
8
+ self[locale] = {} unless has_key?(locale)
9
+ self.fetch(locale)
10
+ end
11
+
12
+ def contains?(locale, attr_name)
13
+ self[locale].has_key?(attr_name)
14
+ end
15
+
16
+ def read(locale, attr_name)
17
+ self[locale][attr_name]
18
+ end
19
+
20
+ def write(locale, attr_name, value)
21
+ self[locale][attr_name] = value
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,40 @@
1
+ module Globalize
2
+ module ActiveRecord
3
+ module Migration
4
+ def create_translation_table!(fields)
5
+ translated_attribute_names.each do |f|
6
+ raise MigrationMissingTranslatedField, "Missing translated field #{f}" unless fields[f]
7
+ end
8
+
9
+ fields.each do |name, type|
10
+ if translated_attribute_names.include?(name) && ![:string, :text].include?(type)
11
+ raise BadMigrationFieldType, "Bad field type for #{name}, should be :string or :text"
12
+ end
13
+ end
14
+
15
+ self.connection.create_table(translation_table_name) do |t|
16
+ t.references self.table_name.singularize
17
+ t.string :locale
18
+ fields.each do |name, type|
19
+ t.column name, type
20
+ end
21
+ t.timestamps
22
+ end
23
+
24
+ self.connection.add_index(translation_table_name, "#{self.table_name.singularize}_id", :name => translation_index_name)
25
+ end
26
+
27
+ def translation_index_name
28
+ require 'digest/sha1'
29
+ # FIXME what's the max size of an index name?
30
+ index_name = "index_#{translation_table_name}_on_#{self.table_name.singularize}_id"
31
+ index_name.size < 50 ? index_name : "index_#{Digest::SHA1.hexdigest(index_name)}"
32
+ end
33
+
34
+ def drop_translation_table!
35
+ self.connection.remove_index(translation_table_name, :name => translation_index_name) rescue nil
36
+ self.connection.drop_table(translation_table_name)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -2,10 +2,10 @@
2
2
  # but additionally logs missing translations to a given log.
3
3
  #
4
4
  # Useful for identifying missing translations during testing.
5
- #
6
- # E.g.
7
5
  #
8
- # require 'globalize/i18n/missing_translations_log_handler
6
+ # E.g.
7
+ #
8
+ # require 'globalize/i18n/missing_translations_log_handler'
9
9
  # I18n.missing_translations_logger = RAILS_DEFAULT_LOGGER
10
10
  # I18n.exception_handler = :missing_translations_log_handler
11
11
  #
@@ -16,7 +16,7 @@
16
16
 
17
17
  module I18n
18
18
  @@missing_translations_logger = nil
19
-
19
+
20
20
  class << self
21
21
  def missing_translations_logger
22
22
  @@missing_translations_logger ||= begin
@@ -24,18 +24,18 @@ module I18n
24
24
  Logger.new(STDOUT)
25
25
  end
26
26
  end
27
-
27
+
28
28
  def missing_translations_logger=(logger)
29
29
  @@missing_translations_logger = logger
30
30
  end
31
-
31
+
32
32
  def missing_translations_log_handler(exception, locale, key, options)
33
33
  if MissingTranslationData === exception
34
34
  missing_translations_logger.warn(exception.message)
35
- return exception.message
35
+ return exception.message
36
36
  else
37
37
  raise exception
38
38
  end
39
39
  end
40
40
  end
41
- end
41
+ end
@@ -2,10 +2,10 @@
2
2
  # but also raises on missing translations.
3
3
  #
4
4
  # Useful for identifying missing translations during testing.
5
- #
6
- # E.g.
7
5
  #
8
- # require 'globalize/i18n/missing_translations_raise_handler
6
+ # E.g.
7
+ #
8
+ # require 'globalize/i18n/missing_translations_raise_handler'
9
9
  # I18n.exception_handler = :missing_translations_raise_handler
10
10
  module I18n
11
11
  class << self
@@ -13,8 +13,6 @@ module I18n
13
13
  raise exception
14
14
  end
15
15
  end
16
-
17
- # self.exception_handler = :missing_translations_raise_handler
18
16
  end
19
17
 
20
18
  I18n.exception_handler = :missing_translations_raise_handler
@@ -0,0 +1,102 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+ require File.expand_path(File.dirname(__FILE__) + '/../data/models')
3
+
4
+ if I18n.respond_to?(:fallbacks)
5
+ class TranslatedTest < ActiveSupport::TestCase
6
+ def setup
7
+ I18n.locale = :'en-US'
8
+ I18n.fallbacks.clear
9
+ reset_db!
10
+ ActiveRecord::Base.locale = nil
11
+ end
12
+
13
+ def teardown
14
+ I18n.fallbacks.clear
15
+ end
16
+
17
+ test "keeping one field in new locale when other field is changed" do
18
+ I18n.fallbacks.map 'de-DE' => [ 'en-US' ]
19
+ post = Post.create :subject => 'foo'
20
+ I18n.locale = 'de-DE'
21
+ post.content = 'bar'
22
+ assert_equal 'foo', post.subject
23
+ end
24
+
25
+ test "modifying non-required field in a new locale" do
26
+ I18n.fallbacks.map 'de-DE' => [ 'en-US' ]
27
+ post = Post.create :subject => 'foo'
28
+ I18n.locale = 'de-DE'
29
+ post.content = 'bar'
30
+ assert post.save
31
+ end
32
+
33
+ test "resolves a simple fallback" do
34
+ I18n.locale = 'de-DE'
35
+ post = Post.create :subject => 'foo'
36
+ I18n.locale = 'de'
37
+ post.subject = 'baz'
38
+ post.content = 'bar'
39
+ post.save
40
+ I18n.locale = 'de-DE'
41
+ assert_equal 'foo', post.subject
42
+ assert_equal 'bar', post.content
43
+ end
44
+
45
+ test "resolves a simple fallback without reloading" do
46
+ I18n.locale = 'de-DE'
47
+ post = Post.new :subject => 'foo'
48
+ I18n.locale = 'de'
49
+ post.subject = 'baz'
50
+ post.content = 'bar'
51
+ I18n.locale = 'de-DE'
52
+ assert_equal 'foo', post.subject
53
+ assert_equal 'bar', post.content
54
+ end
55
+
56
+ test "resolves a complex fallback without reloading" do
57
+ I18n.fallbacks.map 'de' => %w(en he)
58
+ I18n.locale = 'de'
59
+ post = Post.new
60
+ I18n.locale = 'en'
61
+ post.subject = 'foo'
62
+ I18n.locale = 'he'
63
+ post.subject = 'baz'
64
+ post.content = 'bar'
65
+ I18n.locale = 'de'
66
+ assert_equal 'foo', post.subject
67
+ assert_equal 'bar', post.content
68
+ end
69
+
70
+ test 'fallbacks with lots of locale switching' do
71
+ I18n.fallbacks.map :'de-DE' => [ :'en-US' ]
72
+ post = Post.create :subject => 'foo'
73
+
74
+ I18n.locale = :'de-DE'
75
+ assert_equal 'foo', post.subject
76
+
77
+ I18n.locale = :'en-US'
78
+ post.update_attribute :subject, 'bar'
79
+
80
+ I18n.locale = :'de-DE'
81
+ assert_equal 'bar', post.subject
82
+ end
83
+
84
+ test 'fallbacks with lots of locale switching' do
85
+ I18n.fallbacks.map :'de-DE' => [ :'en-US' ]
86
+ child = Child.create :content => 'foo'
87
+
88
+ I18n.locale = :'de-DE'
89
+ assert_equal 'foo', child.content
90
+
91
+ I18n.locale = :'en-US'
92
+ child.update_attribute :content, 'bar'
93
+
94
+ I18n.locale = :'de-DE'
95
+ assert_equal 'bar', child.content
96
+ end
97
+ end
98
+ end
99
+
100
+ # TODO should validate_presence_of take fallbacks into account? maybe we need
101
+ # an extra validation call, or more options for validate_presence_of.
102
+
@@ -1,36 +1,32 @@
1
- require File.join( File.dirname(__FILE__), '..', '..', 'test_helper' )
2
- require 'active_record'
3
- require 'globalize/model/active_record'
4
-
5
- # Hook up model translation
6
- ActiveRecord::Base.send(:include, Globalize::Model::ActiveRecord::Translated)
7
-
8
- # Load Post model
9
- require File.join( File.dirname(__FILE__), '..', '..', 'data', 'models' )
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+ require File.expand_path(File.dirname(__FILE__) + '/../data/models')
10
3
 
11
4
  class MigrationTest < ActiveSupport::TestCase
12
5
  def setup
13
- reset_db! File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'data', 'no_globalize_schema.rb'))
6
+ reset_db!
7
+ Post.drop_translation_table!
14
8
  end
15
9
 
16
10
  test 'globalize table added' do
17
- assert !Post.connection.table_exists?( :post_translations )
18
- assert !Post.connection.index_exists?( :post_translations, :post_id )
19
- Post.create_translation_table! :subject => :string, :content => :text
20
- assert Post.connection.table_exists?( :post_translations )
21
- assert Post.connection.index_exists?( :post_translations, :post_id )
22
- columns = Post.connection.columns( :post_translations )
23
- assert locale = columns.detect {|c| c.name == 'locale' }
11
+ assert !Post.connection.table_exists?(:post_translations)
12
+ assert !Post.connection.index_exists?(:post_translations, :post_id)
13
+
14
+ Post.create_translation_table!(:subject => :string, :content => :text)
15
+ assert Post.connection.table_exists?(:post_translations)
16
+ assert Post.connection.index_exists?(:post_translations, :post_id)
17
+
18
+ columns = Post.connection.columns(:post_translations)
19
+ assert locale = columns.detect { |c| c.name == 'locale' }
24
20
  assert_equal :string, locale.type
25
- assert subject = columns.detect {|c| c.name == 'subject' }
21
+ assert subject = columns.detect { |c| c.name == 'subject' }
26
22
  assert_equal :string, subject.type
27
- assert content = columns.detect {|c| c.name == 'content' }
23
+ assert content = columns.detect { |c| c.name == 'content' }
28
24
  assert_equal :text, content.type
29
- assert post_id = columns.detect {|c| c.name == 'post_id' }
25
+ assert post_id = columns.detect { |c| c.name == 'post_id' }
30
26
  assert_equal :integer, post_id.type
31
- assert created_at = columns.detect {|c| c.name == 'created_at' }
27
+ assert created_at = columns.detect { |c| c.name == 'created_at' }
32
28
  assert_equal :datetime, created_at.type
33
- assert updated_at = columns.detect {|c| c.name == 'updated_at' }
29
+ assert updated_at = columns.detect { |c| c.name == 'updated_at' }
34
30
  assert_equal :datetime, updated_at.type
35
31
  end
36
32
 
@@ -46,13 +42,13 @@ class MigrationTest < ActiveSupport::TestCase
46
42
  end
47
43
 
48
44
  test 'exception on missing field inputs' do
49
- assert_raise Globalize::Model::MigrationMissingTranslatedField do
45
+ assert_raise Globalize::MigrationMissingTranslatedField do
50
46
  Post.create_translation_table! :content => :text
51
47
  end
52
48
  end
53
49
 
54
50
  test 'exception on bad input type' do
55
- assert_raise Globalize::Model::BadMigrationFieldType do
51
+ assert_raise Globalize::BadMigrationFieldType do
56
52
  Post.create_translation_table! :subject => :string, :content => :integer
57
53
  end
58
54
  end
@@ -104,16 +100,15 @@ class MigrationTest < ActiveSupport::TestCase
104
100
  end
105
101
 
106
102
  test 'globalize table dropped when table has long name' do
103
+ UltraLongModelNameWithoutProper.drop_translation_table!
107
104
  UltraLongModelNameWithoutProper.create_translation_table!(
108
105
  :subject => :string, :content => :text
109
106
  )
110
-
111
107
  UltraLongModelNameWithoutProper.drop_translation_table!
112
108
 
113
109
  assert !UltraLongModelNameWithoutProper.connection.table_exists?(
114
110
  :ultra_long_model_name_without_proper_translations
115
111
  )
116
-
117
112
  assert !UltraLongModelNameWithoutProper.connection.index_exists?(
118
113
  :ultra_long_model_name_without_proper_translations,
119
114
  :ultra_long_model_name_without_proper_id
@@ -1,22 +1,10 @@
1
- require File.join( File.dirname(__FILE__), '..', '..', 'test_helper' )
2
- require 'active_record'
3
- require 'globalize/model/active_record'
4
-
5
- # Hook up model translation
6
- ActiveRecord::Base.send(:include, Globalize::Model::ActiveRecord::Translated)
7
-
8
- # Load Post model
9
- require File.join( File.dirname(__FILE__), '..', '..', 'data', 'models' )
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+ require File.expand_path(File.dirname(__FILE__) + '/../data/models')
10
3
 
11
4
  class StiTranslatedTest < ActiveSupport::TestCase
12
5
  def setup
13
6
  I18n.locale = :'en-US'
14
- I18n.fallbacks.clear
15
- reset_db! File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'data', 'schema.rb'))
16
- end
17
-
18
- def teardown
19
- I18n.fallbacks.clear
7
+ reset_db!
20
8
  end
21
9
 
22
10
  test "works with simple dynamic finders" do
@@ -43,20 +31,6 @@ class StiTranslatedTest < ActiveSupport::TestCase
43
31
  assert_equal [ 'content' ], child.changed
44
32
  end
45
33
 
46
- test 'fallbacks with lots of locale switching' do
47
- I18n.fallbacks.map :'de-DE' => [ :'en-US' ]
48
- child = Child.create :content => 'foo'
49
-
50
- I18n.locale = :'de-DE'
51
- assert_equal 'foo', child.content
52
-
53
- I18n.locale = :'en-US'
54
- child.update_attribute :content, 'bar'
55
-
56
- I18n.locale = :'de-DE'
57
- assert_equal 'bar', child.content
58
- end
59
-
60
34
  test "saves all locales, even after locale switching" do
61
35
  child = Child.new :content => 'foo'
62
36
  I18n.locale = 'de-DE'
@@ -72,4 +46,4 @@ class StiTranslatedTest < ActiveSupport::TestCase
72
46
  I18n.locale = 'he-IL'
73
47
  assert_equal 'baz', child.content
74
48
  end
75
- end
49
+ end