globalize2 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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