galetahub-globalize3 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/README.textile +206 -0
  2. data/Rakefile +22 -0
  3. data/lib/globalize.rb +59 -0
  4. data/lib/globalize/active_record.rb +13 -0
  5. data/lib/globalize/active_record/accessors.rb +22 -0
  6. data/lib/globalize/active_record/act_macro.rb +67 -0
  7. data/lib/globalize/active_record/adapter.rb +101 -0
  8. data/lib/globalize/active_record/attributes.rb +27 -0
  9. data/lib/globalize/active_record/class_methods.rb +125 -0
  10. data/lib/globalize/active_record/exceptions.rb +19 -0
  11. data/lib/globalize/active_record/instance_methods.rb +166 -0
  12. data/lib/globalize/active_record/migration.rb +125 -0
  13. data/lib/globalize/active_record/translation.rb +37 -0
  14. data/lib/globalize/engine.rb +17 -0
  15. data/lib/globalize/utils.rb +142 -0
  16. data/lib/globalize/versioning.rb +5 -0
  17. data/lib/globalize/versioning/paper_trail.rb +41 -0
  18. data/lib/globalize3.rb +1 -0
  19. data/lib/globalize3/version.rb +3 -0
  20. data/lib/i18n/missing_translations_log_handler.rb +41 -0
  21. data/lib/i18n/missing_translations_raise_handler.rb +25 -0
  22. data/lib/patches/active_record/query_method.rb +35 -0
  23. data/lib/patches/active_record/xml_attribute_serializer.rb +13 -0
  24. data/lib/tasks/globalize.rake +13 -0
  25. data/test/all.rb +1 -0
  26. data/test/data/models.rb +68 -0
  27. data/test/data/schema.rb +108 -0
  28. data/test/globalize3/attributes_test.rb +133 -0
  29. data/test/globalize3/clone_test.rb +58 -0
  30. data/test/globalize3/dirty_tracking_test.rb +61 -0
  31. data/test/globalize3/dynamic_finders_test.rb +171 -0
  32. data/test/globalize3/fallbacks_test.rb +146 -0
  33. data/test/globalize3/locale_test.rb +81 -0
  34. data/test/globalize3/migration_test.rb +156 -0
  35. data/test/globalize3/set_translations_test.rb +54 -0
  36. data/test/globalize3/translation_class_test.rb +59 -0
  37. data/test/globalize3/validations_test.rb +92 -0
  38. data/test/globalize3/versioning_test.rb +87 -0
  39. data/test/globalize3_test.rb +159 -0
  40. data/test/i18n/missing_translations_test.rb +35 -0
  41. data/test/test_helper.rb +105 -0
  42. 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,5 @@
1
+ module Globalize
2
+ module Versioning
3
+ autoload :PaperTrail, 'globalize/versioning/paper_trail'
4
+ end
5
+ 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
@@ -0,0 +1 @@
1
+ require 'globalize'
@@ -0,0 +1,3 @@
1
+ module Globalize3
2
+ VERSION = '0.2.1'
3
+ end
@@ -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
@@ -0,0 +1 @@
1
+ Dir[File.expand_path('../**/*_test.rb', __FILE__)].each { |file| require file }
@@ -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