globalize3-jovoto 0.1.0.beta

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2008, 2009, 2010 Sven Fuchs, Joshua Harvey, Clemens Kofler, John-Paul Bader
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,122 @@
1
+ h1. Globalize3
2
+
3
+ Globalize3 is the successor of Globalize for Rails. Globalize is targeted at ActiveRecord 3. It is compatible with and builds on the new "I18n API in Ruby on Rails":http://guides.rubyonrails.org/i18n.html and adds model translations to ActiveRecord.
4
+
5
+ Globalize3 is much more lightweight and compatible than its predecessor Globalize for Rails was. Model translations in Globalize3 use default ActiveRecord features and do not limit any ActiveRecord functionality any more.
6
+
7
+ h2. Requirements
8
+
9
+ ActiveRecord > 3.0.0.rc
10
+ I18n
11
+
12
+ h2. Installation
13
+
14
+ To install Globalize3 with its default setup just use:
15
+
16
+ <pre><code>
17
+ $ gem install globalize3
18
+ </code></pre>
19
+
20
+ h2. Model translations
21
+
22
+ Model translations allow you to translate your models' attribute values. E.g.
23
+
24
+ <pre><code>
25
+ class Post < ActiveRecord::Base
26
+ translates :title, :text
27
+ end
28
+ </code></pre>
29
+
30
+ Allows you to translate the attributes :title and :text per locale:
31
+
32
+ <pre><code>
33
+ I18n.locale = :en
34
+ post.title # => Globalize3 rocks!
35
+
36
+ I18n.locale = :he
37
+ post.title # => גלובאלייז2 שולט!
38
+ </code></pre>
39
+
40
+ In order to make this work, you'll need to add the appropriate translation tables. Globalize3 comes with a handy helper method to help you do this. It's called @create_translation_table!@. Here's an example:
41
+
42
+ <pre><code>
43
+ class CreatePosts < ActiveRecord::Migration
44
+ def self.up
45
+ create_table :posts do |t|
46
+ t.timestamps
47
+ end
48
+ Post.create_translation_table! :title => :string, :text => :text
49
+ end
50
+ def self.down
51
+ drop_table :posts
52
+ Post.drop_translation_table!
53
+ end
54
+ end
55
+ </code></pre>
56
+
57
+ Note that the ActiveRecord model @Post@ must already exist and have a @translates@ directive listing the translated fields.
58
+
59
+ h2. Migrating existing data to and from the translated version
60
+
61
+ As well as creating a translation table, you can also use @create_translation_table!@ to migrate across any
62
+ existing data to the default locale. This can also operate in reverse to restore any translations from the default locale
63
+ back to the model when you don't want to use a translation table anymore using @drop_translation_table!@
64
+
65
+ This feature makes use of @untranslated_attributes@ which allows access to the model's attributes as they were before
66
+ the translation was applied. Here's an example (which assumes you already have a model called @Post@ and its table exists):
67
+
68
+ <pre><code>
69
+ class TranslatePosts < ActiveRecord::Migration
70
+ def self.up
71
+ Post.create_translation_table!({
72
+ :title => :string,
73
+ :text => :text
74
+ }, {
75
+ :migrate_data => true
76
+ })
77
+ end
78
+ def self.down
79
+ Post.drop_translation_table! :migrate_data => true
80
+ end
81
+ end
82
+ </code></pre>
83
+
84
+ h2. Versioning with Globalize3
85
+
86
+ Globalize3 nicely integrates with "vestal_versions":http://github.com/laserlemon/vestal_versions:
87
+
88
+ <pre><code>
89
+ require 'globalize/versioning/vestal_versions'
90
+ </code></pre>
91
+
92
+ As of writing (2010-08-05) the original vestal_versions respository has not been updated to be compatible with Rails 3 though. You can use "this fork":http://github.com/svenfuchs/vestal_versions though. "Globalize3's Gemfile":http://github.com/svenfuchs/globalize3/blob/master/Gemfile#L10 is currently set up accordingly.
93
+
94
+ Please also note that @update_attribute@ currently hides itself from dirty tracking in ActiveRecord >= 3.0.0.beta (which is considered a "regression":http://github.com/rails/rails/commit/01629d180468049d17a8be6900e27a4f0d2b18c4#commitcomment-123199). That means that you currently need to use attribute writers or @update_attributes@ in order to track changes/versions for your models.
95
+
96
+ Also, please see the tests in test/globalize3/versioning_test.rb for some current gotchas.
97
+
98
+ h2. Changes since Globalize2
99
+
100
+ * `translation_table_name` was renamed to `translations_table_name`
101
+ * `available_locales` has been removed. please use `translated_locales`
102
+
103
+ h2. Migration from Globalize for Rails (version 1)
104
+
105
+ See this script by Tomasz Stachewicz: http://gist.github.com/120867
106
+
107
+ h2. Alternative Solutions
108
+
109
+ * "Veger's fork":http://github.com/veger/globalize2 - uses default AR schema for the default locale, delegates to the translations table for other locales only
110
+ * "TranslatableColumns":http://github.com/iain/translatable_columns - have multiple languages of the same attribute in a model (Iain Hecker)
111
+ * "localized_record":http://github.com/glennpow/localized_record - allows records to have localized attributes without any modifications to the database (Glenn Powell)
112
+ * "model_translations":http://github.com/janne/model_translations - Minimal implementation of Globalize2 style model translations (Jan Andersson)
113
+
114
+ h2. Related solutions
115
+
116
+ * "globalize2_versioning":http://github.com/joshmh/globalize2_versioning - acts_as_versioned style versioning for globalize2 (Joshua Harvey)
117
+ * "i18n_multi_locales_validations":http://github.com/ZenCocoon/i18n_multi_locales_validations - multi-locales attributes validations to validates attributes from globalize2 translations models (Sébastien Grosjean)
118
+ * "globalize2 Demo App":http://github.com/svenfuchs/globalize2-demo - demo application for globalize2 (Sven Fuchs)</li>
119
+ * "migrate_from_globalize1":http://gist.github.com/120867 - migrate model translations from Globalize1 to globalize2 (Tomasz Stachewicz)</li>
120
+ * "easy_globalize2_accessors":http://github.com/astropanic/easy_globalize2_accessors - easily access (read and write) globalize2-translated fields (astropanic, Tomasz Stachewicz)</li>
121
+ * "globalize2-easy-translate":http://github.com/bsamman/globalize2-easy-translate - adds methods to easily access or set translated attributes to your model (bsamman)</li>
122
+ * "batch_translations":http://github.com/alvarezrilla/batch_translations - allow saving multiple globalize2 translations in the same request (Jose Alvarez Rilla)</li>
@@ -0,0 +1,22 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Run all tests.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.pattern = 'test/**/*_test.rb'
12
+ t.verbose = true
13
+ end
14
+
15
+ desc 'Generate documentation.'
16
+ Rake::RDocTask.new(:rdoc) do |rdoc|
17
+ rdoc.rdoc_dir = 'rdoc'
18
+ rdoc.title = 'Globalize3'
19
+ rdoc.options << '--line-numbers' << '--inline-source'
20
+ rdoc.rdoc_files.include('README')
21
+ rdoc.rdoc_files.include('lib/**/*.rb')
22
+ end
@@ -0,0 +1,52 @@
1
+ require 'active_record'
2
+ require 'patches/active_record/xml_attribute_serializer'
3
+ require 'patches/active_record/query_method'
4
+
5
+ module Globalize
6
+ autoload :ActiveRecord, 'globalize/active_record'
7
+ autoload :Versioning, 'globalize/versioning'
8
+
9
+ class << self
10
+ def locale
11
+ read_locale || I18n.locale
12
+ end
13
+
14
+ def locale=(locale)
15
+ set_locale(locale)
16
+ end
17
+
18
+ def with_locale(locale, &block)
19
+ previous_locale = read_locale
20
+ set_locale(locale)
21
+ result = yield(locale)
22
+ set_locale(previous_locale)
23
+ result
24
+ end
25
+
26
+ def with_locales(*locales, &block)
27
+ locales.flatten.map do |locale|
28
+ with_locale(locale, &block)
29
+ end
30
+ end
31
+
32
+ def fallbacks?
33
+ I18n.respond_to?(:fallbacks)
34
+ end
35
+
36
+ def fallbacks(locale = self.locale)
37
+ fallbacks? ? I18n.fallbacks[locale] : [locale.to_sym]
38
+ end
39
+
40
+ protected
41
+
42
+ def read_locale
43
+ Thread.current[:globalize_locale]
44
+ end
45
+
46
+ def set_locale(locale)
47
+ Thread.current[:globalize_locale] = locale
48
+ end
49
+ end
50
+ end
51
+
52
+ ActiveRecord::Base.extend(Globalize::ActiveRecord::ActMacro)
@@ -0,0 +1,12 @@
1
+ module Globalize
2
+ module ActiveRecord
3
+ autoload :ActMacro, 'globalize/active_record/act_macro'
4
+ autoload :Adapter, 'globalize/active_record/adapter'
5
+ autoload :Attributes, 'globalize/active_record/attributes'
6
+ autoload :ClassMethods, 'globalize/active_record/class_methods'
7
+ autoload :Exceptions, 'globalize/active_record/exceptions'
8
+ autoload :InstanceMethods, 'globalize/active_record/instance_methods'
9
+ autoload :Migration, 'globalize/active_record/migration'
10
+ autoload :Translation, 'globalize/active_record/translation'
11
+ end
12
+ end
@@ -0,0 +1,45 @@
1
+ module Globalize
2
+ module ActiveRecord
3
+ module ActMacro
4
+ def translates(*attr_names)
5
+ return if translates?
6
+
7
+ options = attr_names.extract_options!
8
+ options[:table_name] ||= "#{table_name.singularize}_translations"
9
+
10
+ class_attribute :translated_attribute_names, :translation_options
11
+ self.translated_attribute_names = attr_names.map(&:to_sym)
12
+ self.translation_options = options
13
+
14
+ include InstanceMethods
15
+ extend ClassMethods, Migration
16
+
17
+ has_many :translations, :class_name => translation_class.name,
18
+ :foreign_key => class_name.foreign_key,
19
+ :dependent => :delete_all,
20
+ :extend => HasManyExtensions
21
+
22
+ after_save :save_translations!
23
+
24
+ attr_names.each { |attr_name| translated_attr_accessor(attr_name) }
25
+ end
26
+
27
+ def class_name
28
+ @class_name ||= begin
29
+ class_name = table_name[table_name_prefix.length..-(table_name_suffix.length + 1)].downcase.camelize
30
+ pluralize_table_names ? class_name.singularize : class_name
31
+ end
32
+ end
33
+
34
+ def translates?
35
+ included_modules.include?(InstanceMethods)
36
+ end
37
+ end
38
+
39
+ module HasManyExtensions
40
+ def find_or_initialize_by_locale(locale)
41
+ with_locale(locale.to_s).first || build(:locale => locale.to_s)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,100 @@
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
+ delegate :translation_class, :to => :'record.class'
9
+
10
+ def initialize(record)
11
+ @record = record
12
+ @cache = Attributes.new
13
+ @stash = Attributes.new
14
+ end
15
+
16
+ def fetch(locale, name)
17
+ cache.contains?(locale, name) ?
18
+ type_cast(name, cache.read(locale, name)) :
19
+ cache.write(locale, name, fetch_attribute(locale, name))
20
+ end
21
+
22
+ def write(locale, name, value)
23
+ stash.write(locale, name, value)
24
+ cache.write(locale, name, value)
25
+ end
26
+
27
+ def save_translations!
28
+ stash.each do |locale, attrs|
29
+ translation = record.translations.find_or_initialize_by_locale(locale.to_s)
30
+ attrs.each { |name, value| translation[name] = value }
31
+ translation.save!
32
+ end
33
+ stash.clear
34
+ end
35
+
36
+ def reset
37
+ cache.clear
38
+ end
39
+
40
+ protected
41
+
42
+ def type_cast(name, value)
43
+ if value.nil?
44
+ nil
45
+ elsif column = column_for_attribute(name)
46
+ column.type_cast(value)
47
+ else
48
+ value
49
+ end
50
+ end
51
+
52
+ def column_for_attribute(name)
53
+ translation_class.columns_hash[name.to_s]
54
+ end
55
+
56
+ def unserializable_attribute?(name, column)
57
+ column.text? && translation_class.serialized_attributes[name.to_s]
58
+ end
59
+
60
+ def fetch_translation(locale)
61
+ locale = locale.to_sym
62
+ record.translations.loaded? ? record.translations.detect { |t| t.locale == locale } :
63
+ record.translations.with_locales(locale)
64
+ end
65
+
66
+ def fetch_translations(locale)
67
+ # only query if not already included with :include => translations
68
+ record.translations.loaded? ? record.translations :
69
+ record.translations.with_locales(Globalize.fallbacks(locale))
70
+ end
71
+
72
+ def fetch_attribute(locale, name)
73
+ translations = fetch_translations(locale)
74
+ value, requested_locale = nil, locale
75
+
76
+ Globalize.fallbacks(locale).each do |fallback|
77
+ translation = translations.to_a.detect { |t| t.locale == fallback }
78
+ value = translation && translation.send(name)
79
+ locale = fallback && break if value
80
+ end
81
+
82
+ set_metadata(value, :locale => locale, :requested_locale => requested_locale)
83
+ value
84
+ end
85
+
86
+ def set_metadata(object, metadata)
87
+ if object.respond_to?(:translation_metadata)
88
+ object.translation_metadata.merge!(meta_data)
89
+ end
90
+ end
91
+
92
+ def translation_metadata_accessor(object)
93
+ return if obj.respond_to?(:translation_metadata)
94
+ class << object; attr_accessor :translation_metadata end
95
+ object.translation_metadata ||= {}
96
+ end
97
+ end
98
+ end
99
+ end
100
+
@@ -0,0 +1,26 @@
1
+ # Helper class for storing values per locale. Used by Globalize::Adapter
2
+ # to stash and cache attribute values.
3
+
4
+ module Globalize
5
+ module ActiveRecord
6
+ class Attributes < Hash
7
+ def [](locale)
8
+ locale = locale.to_sym
9
+ self[locale] = {} unless has_key?(locale)
10
+ self.fetch(locale)
11
+ end
12
+
13
+ def contains?(locale, name)
14
+ self[locale].has_key?(name.to_s)
15
+ end
16
+
17
+ def read(locale, name)
18
+ self[locale][name.to_s]
19
+ end
20
+
21
+ def write(locale, name, value)
22
+ self[locale][name.to_s] = value
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,90 @@
1
+ module Globalize
2
+ module ActiveRecord
3
+ module ClassMethods
4
+ delegate :translated_locales, :set_translations_table_name, :to => :translation_class
5
+
6
+ def with_locales(*locales)
7
+ scoped & translation_class.with_locales(*locales)
8
+ end
9
+
10
+ def with_translations(*locales)
11
+ locales = translated_locales if locales.empty?
12
+ includes(:translations).with_locales(locales).with_required_attributes
13
+ end
14
+
15
+ def with_required_attributes
16
+ required_translated_attributes.inject(scoped) do |scope, name|
17
+ scope.where("#{translated_column_name(name)} IS NOT NULL")
18
+ end
19
+ end
20
+
21
+ def with_translated_attribute(name, value, locales = nil)
22
+ locales ||= Globalize.fallbacks
23
+ with_translations.where(
24
+ translated_column_name(name) => value,
25
+ translated_column_name(:locale) => Array(locales).map(&:to_s)
26
+ )
27
+ end
28
+
29
+ def translated?(name)
30
+ translated_attribute_names.include?(name.to_sym)
31
+ end
32
+
33
+ def required_attributes
34
+ validators.map { |v| v.attributes if v.is_a?(ActiveModel::Validations::PresenceValidator) }.flatten
35
+ end
36
+
37
+ def required_translated_attributes
38
+ translated_attribute_names & required_attributes
39
+ end
40
+
41
+ def translation_class
42
+ @translation_class ||= begin
43
+ klass = self.const_get(:Translation) rescue nil
44
+ if klass.nil? || klass.class_name != (self.class_name + "Translation")
45
+ klass = self.const_set(:Translation, Class.new(Globalize::ActiveRecord::Translation))
46
+ end
47
+
48
+ if klass.table_name == 'translations'
49
+ klass.set_table_name(translation_options[:table_name])
50
+ klass.belongs_to name.underscore.gsub('/', '_')
51
+ end
52
+ klass
53
+ end
54
+ end
55
+
56
+ def translations_table_name
57
+ translation_class.table_name
58
+ end
59
+
60
+ def translated_column_name(name)
61
+ "#{translation_class.table_name}.#{name}"
62
+ end
63
+
64
+ def respond_to?(method, *args, &block)
65
+ method.to_s =~ /^find_(all_|)by_(\w+)$/ && translated?($2.to_sym) || super
66
+ end
67
+
68
+ def method_missing(method, *args)
69
+ if method.to_s =~ /^find_(all_|)by_(\w+)$/ && translated?($2.to_sym)
70
+ result = with_translated_attribute($2, args.first)
71
+ $1 == 'all_' ? result.all : result.first
72
+ else
73
+ super
74
+ end
75
+ end
76
+
77
+ protected
78
+
79
+ def translated_attr_accessor(name)
80
+ define_method(:"#{name}=") do |value|
81
+ write_attribute(name, value)
82
+ end
83
+ define_method(name) do |*args|
84
+ read_attribute(name, {:locale => args.first})
85
+ end
86
+ alias_method :"#{name}_before_type_cast", name
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,19 @@
1
+ module Globalize
2
+ module ActiveRecord
3
+ module Exceptions
4
+ class MigrationError < StandardError; end
5
+
6
+ class BadFieldName < MigrationError
7
+ def initialize(field)
8
+ super("Missing translated field #{field.inspect}")
9
+ end
10
+ end
11
+
12
+ class BadFieldType < MigrationError
13
+ def initialize(name, type)
14
+ super("Bad field type for field #{name.inspect} (#{type.inspect}), should be :string or :text")
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,112 @@
1
+ module Globalize
2
+ module ActiveRecord
3
+ module InstanceMethods
4
+ delegate :translated_locales, :to => :translations
5
+
6
+ def globalize
7
+ @globalize ||= Adapter.new(self)
8
+ end
9
+
10
+ def attributes
11
+ super.merge(translated_attributes)
12
+ end
13
+
14
+ def attributes=(attributes, *args)
15
+ with_given_locale(attributes) { super }
16
+ end
17
+
18
+ def update_attributes!(attributes, *args)
19
+ with_given_locale(attributes) { super }
20
+ end
21
+
22
+ def update_attributes(attributes, *args)
23
+ with_given_locale(attributes) { super }
24
+ end
25
+
26
+ def write_attribute(name, value, options = {})
27
+ # Make sure that we return some value as some methods might
28
+ # rely on the data
29
+ return_value = super(name, value)
30
+
31
+ if translated?(name)
32
+ # Deprecate old use of locale
33
+ unless options.is_a?(Hash)
34
+ warn "[DEPRECATION] passing 'locale' as #{options.inspect} is deprecated. Please use {:locale => #{options.inspect}} instead."
35
+ options = {:locale => options}
36
+ end
37
+ options = {:locale => nil}.merge(options)
38
+ return_value = globalize.write(options[:locale] || Globalize.locale, name, value)
39
+ end
40
+ return_value
41
+ end
42
+
43
+ def read_attribute(name, options = {})
44
+ # Deprecate old use of locale
45
+ unless options.is_a?(Hash)
46
+ warn "[DEPRECATION] passing 'locale' as #{options.inspect} is deprecated. Please use {:locale => #{options.inspect}} instead."
47
+ options = {:locale => options}
48
+ end
49
+
50
+ options = {:translated => true, :locale => nil}.merge(options)
51
+ if self.class.translated?(name) and options[:translated]
52
+ globalize.fetch(options[:locale] || Globalize.locale, name)
53
+ else
54
+ super(name)
55
+ end
56
+ end
57
+
58
+ def attribute_names
59
+ translated_attribute_names.map(&:to_s) + super
60
+ end
61
+
62
+ def translated?(name)
63
+ self.class.translated?(name)
64
+ end
65
+
66
+ def translated_attributes
67
+ translated_attribute_names.inject({}) do |attributes, name|
68
+ attributes.merge(name.to_s => send(name))
69
+ end
70
+ end
71
+
72
+ # This method is basically the method built into Rails
73
+ # but we have to pass {:translated => false}
74
+ def untranslated_attributes
75
+ attrs = {}
76
+ attribute_names.each do |name|
77
+ attrs[name] = read_attribute(name, {:translated => false})
78
+ end
79
+ attrs
80
+ end
81
+
82
+ def set_translations(options)
83
+ options.keys.each do |locale|
84
+ translation = translations.find_by_locale(locale.to_s) ||
85
+ translations.build(:locale => locale.to_s)
86
+ translation.update_attributes!(options[locale])
87
+ end
88
+ end
89
+
90
+ def reload(options = nil)
91
+ translated_attribute_names.each { |name| @attributes.delete(name.to_s) }
92
+ globalize.reset
93
+ super(options)
94
+ end
95
+
96
+ protected
97
+
98
+ def save_translations!
99
+ globalize.save_translations!
100
+ end
101
+
102
+ def with_given_locale(attributes, &block)
103
+ attributes.symbolize_keys! if attributes.respond_to?(:symbolize_keys!)
104
+ if locale = attributes.try(:delete, :locale)
105
+ Globalize.with_locale(locale, &block)
106
+ else
107
+ yield
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,125 @@
1
+ require 'digest/sha1'
2
+
3
+ module Globalize
4
+ module ActiveRecord
5
+ module Migration
6
+ attr_reader :globalize_migrator
7
+
8
+ def globalize_migrator
9
+ @globalize_migrator ||= Migrator.new(self)
10
+ end
11
+
12
+ delegate :create_translation_table!, :drop_translation_table!,
13
+ :translation_index_name, :to => :globalize_migrator
14
+
15
+ class Migrator
16
+ include Globalize::ActiveRecord::Exceptions
17
+
18
+ attr_reader :model, :fields
19
+ delegate :translated_attribute_names, :connection, :table_name,
20
+ :table_name_prefix, :translations_table_name, :columns, :to => :model
21
+
22
+ def initialize(model)
23
+ @model = model
24
+ end
25
+
26
+ def create_translation_table!(fields = {}, options = {})
27
+ @fields = fields
28
+ complete_translated_fields
29
+ validate_translated_fields
30
+
31
+ create_translation_table
32
+ move_data_to_translation_table if options[:migrate_data]
33
+ create_translations_index
34
+ end
35
+
36
+ def drop_translation_table!(options = {})
37
+ move_data_to_model_table if options[:migrate_data]
38
+ drop_translations_index
39
+ drop_translation_table
40
+ end
41
+
42
+ def complete_translated_fields
43
+ translated_attribute_names.each do |name|
44
+ fields[name] = column_type(name) unless fields[name]
45
+ end
46
+ end
47
+
48
+ def create_translation_table
49
+ connection.create_table(translations_table_name) do |t|
50
+ t.references table_name.sub(/^#{table_name_prefix}/, '').singularize
51
+ t.string :locale
52
+ fields.each { |name, type| t.column name, type }
53
+ t.timestamps
54
+ end
55
+ end
56
+
57
+ def create_translations_index
58
+ connection.add_index(
59
+ translations_table_name,
60
+ "#{table_name.sub(/^#{table_name_prefix}/, "").singularize}_id",
61
+ :name => translation_index_name
62
+ )
63
+ end
64
+
65
+ def drop_translation_table
66
+ connection.drop_table(translations_table_name)
67
+ end
68
+
69
+ def drop_translations_index
70
+ connection.remove_index(translations_table_name, :name => translation_index_name) rescue nil
71
+ end
72
+
73
+ def move_data_to_translation_table
74
+ # Find all of the existing untranslated attributes for this model.
75
+ all_model_fields = @model.all
76
+ model_attributes = all_model_fields.collect {|m| m.untranslated_attributes}
77
+ all_model_fields.each do |model_record|
78
+ # Assign the attributes back to the model which will enable globalize3 to translate them.
79
+ model_record.attributes = model_attributes.detect{|a| a['id'] == model_record.id}
80
+ model_record.save!
81
+ end
82
+ end
83
+
84
+ def move_data_to_model_table
85
+ # Find all of the translated attributes for all records in the model.
86
+ all_translated_attributes = @model.all.collect{|m| m.attributes}
87
+ all_translated_attributes.each do |translated_record|
88
+ # Create a hash containing the translated column names and their values.
89
+ translated_attribute_names.inject(fields_to_update={}) do |f, name|
90
+ f.update({name.to_sym => translated_record[name.to_s]})
91
+ end
92
+
93
+ # Now, update the actual model's record with the hash.
94
+ @model.update_all(fields_to_update, {:id => translated_record['id']})
95
+ end
96
+ end
97
+
98
+ def validate_translated_fields
99
+ fields.each do |name, type|
100
+ raise BadFieldName.new(name) unless valid_field_name?(name)
101
+ raise BadFieldType.new(name, type) unless valid_field_type?(name, type)
102
+ end
103
+ end
104
+
105
+ def column_type(name)
106
+ columns.detect { |c| c.name == name.to_s }.try(:type)
107
+ end
108
+
109
+ def valid_field_name?(name)
110
+ translated_attribute_names.include?(name)
111
+ end
112
+
113
+ def valid_field_type?(name, type)
114
+ !translated_attribute_names.include?(name) || [:string, :text].include?(type)
115
+ end
116
+
117
+ def translation_index_name
118
+ # FIXME what's the max size of an index name?
119
+ index_name = "index_#{translations_table_name}_on_#{table_name.singularize}_id"
120
+ index_name.size < 50 ? index_name : "index_#{Digest::SHA1.hexdigest(index_name)}"
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,24 @@
1
+ module Globalize
2
+ module ActiveRecord
3
+ class Translation < ::ActiveRecord::Base
4
+ class << self
5
+ def with_locales(*locales)
6
+ where(:locale => locales.flatten.map(&:to_s))
7
+ end
8
+ alias with_locale with_locales
9
+
10
+ def translated_locales
11
+ select('DISTINCT locale').map(&:locale)
12
+ end
13
+ end
14
+
15
+ def locale
16
+ read_attribute(:locale).to_sym
17
+ end
18
+
19
+ def locale=(locale)
20
+ write_attribute(:locale, locale.to_s)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,5 @@
1
+ module Globalize
2
+ module Versioning
3
+ autoload :VestalVersions, 'globalize/versioning/vestal_versions'
4
+ end
5
+ end
@@ -0,0 +1,35 @@
1
+ require 'vestal_versions'
2
+
3
+ module Globalize
4
+ module Versioning
5
+ module VestalVersions
6
+ def versioned_columns
7
+ super + self.class.translated_attribute_names
8
+ end
9
+ end
10
+ end
11
+ end
12
+
13
+ ActiveRecord::Base.class_eval do
14
+ class << self
15
+ def versioned_with_globalize(*args)
16
+ versioned_without_globalize(*args)
17
+ include Globalize::Versioning::VestalVersions
18
+ end
19
+ alias_method_chain :versioned, :globalize
20
+ end
21
+ end
22
+
23
+ VestalVersions::Version.class_eval do
24
+ before_save do |version|
25
+ version.locale = Globalize.locale.to_s
26
+ end
27
+
28
+ class Condition
29
+ def to_sql
30
+ "locale = '#{Globalize.locale.to_s}'"
31
+ end
32
+ end
33
+
34
+ default_scope(:conditions => Condition.new)
35
+ end
@@ -0,0 +1 @@
1
+ require 'globalize'
@@ -0,0 +1,3 @@
1
+ module Globalize3
2
+ VERSION = '0.1.0.beta'
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
metadata ADDED
@@ -0,0 +1,211 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: globalize3-jovoto
3
+ version: !ruby/object:Gem::Version
4
+ hash: 31098185
5
+ prerelease: true
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ - beta
11
+ version: 0.1.0.beta
12
+ platform: ruby
13
+ authors:
14
+ - Sven Fuchs
15
+ - Joshua Harvey
16
+ - Clemens Kofler
17
+ - John-Paul Bader
18
+ autorequire:
19
+ bindir: bin
20
+ cert_chain: []
21
+
22
+ date: 2011-04-18 00:00:00 +02:00
23
+ default_executable:
24
+ dependencies:
25
+ - !ruby/object:Gem::Dependency
26
+ name: activerecord
27
+ prerelease: false
28
+ requirement: &id001 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ hash: 7
34
+ segments:
35
+ - 3
36
+ - 0
37
+ - 0
38
+ version: 3.0.0
39
+ type: :runtime
40
+ version_requirements: *id001
41
+ - !ruby/object:Gem::Dependency
42
+ name: activemodel
43
+ prerelease: false
44
+ requirement: &id002 !ruby/object:Gem::Requirement
45
+ none: false
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ hash: 7
50
+ segments:
51
+ - 3
52
+ - 0
53
+ - 0
54
+ version: 3.0.0
55
+ type: :runtime
56
+ version_requirements: *id002
57
+ - !ruby/object:Gem::Dependency
58
+ name: database_cleaner
59
+ prerelease: false
60
+ requirement: &id003 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - "="
64
+ - !ruby/object:Gem::Version
65
+ hash: 15
66
+ segments:
67
+ - 0
68
+ - 5
69
+ - 2
70
+ version: 0.5.2
71
+ type: :development
72
+ version_requirements: *id003
73
+ - !ruby/object:Gem::Dependency
74
+ name: mocha
75
+ prerelease: false
76
+ requirement: &id004 !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ hash: 3
82
+ segments:
83
+ - 0
84
+ version: "0"
85
+ type: :development
86
+ version_requirements: *id004
87
+ - !ruby/object:Gem::Dependency
88
+ name: pathname_local
89
+ prerelease: false
90
+ requirement: &id005 !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ hash: 3
96
+ segments:
97
+ - 0
98
+ version: "0"
99
+ type: :development
100
+ version_requirements: *id005
101
+ - !ruby/object:Gem::Dependency
102
+ name: test_declarative
103
+ prerelease: false
104
+ requirement: &id006 !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ hash: 3
110
+ segments:
111
+ - 0
112
+ version: "0"
113
+ type: :development
114
+ version_requirements: *id006
115
+ - !ruby/object:Gem::Dependency
116
+ name: ruby-debug
117
+ prerelease: false
118
+ requirement: &id007 !ruby/object:Gem::Requirement
119
+ none: false
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ hash: 3
124
+ segments:
125
+ - 0
126
+ version: "0"
127
+ type: :development
128
+ version_requirements: *id007
129
+ - !ruby/object:Gem::Dependency
130
+ name: sqlite3-ruby
131
+ prerelease: false
132
+ requirement: &id008 !ruby/object:Gem::Requirement
133
+ none: false
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ hash: 3
138
+ segments:
139
+ - 0
140
+ version: "0"
141
+ type: :development
142
+ version_requirements: *id008
143
+ description: "Rails I18n: de-facto standard library for ActiveRecord 3 model/data translation."
144
+ email: nobody@globalize-rails.org
145
+ executables: []
146
+
147
+ extensions: []
148
+
149
+ extra_rdoc_files: []
150
+
151
+ files:
152
+ - lib/globalize/active_record/act_macro.rb
153
+ - lib/globalize/active_record/adapter.rb
154
+ - lib/globalize/active_record/attributes.rb
155
+ - lib/globalize/active_record/class_methods.rb
156
+ - lib/globalize/active_record/exceptions.rb
157
+ - lib/globalize/active_record/instance_methods.rb
158
+ - lib/globalize/active_record/migration.rb
159
+ - lib/globalize/active_record/translation.rb
160
+ - lib/globalize/active_record.rb
161
+ - lib/globalize/versioning/vestal_versions.rb
162
+ - lib/globalize/versioning.rb
163
+ - lib/globalize.rb
164
+ - lib/globalize3/version.rb
165
+ - lib/globalize3.rb
166
+ - lib/i18n/missing_translations_log_handler.rb
167
+ - lib/i18n/missing_translations_raise_handler.rb
168
+ - lib/patches/active_record/query_method.rb
169
+ - lib/patches/active_record/xml_attribute_serializer.rb
170
+ - Gemfile
171
+ - LICENSE
172
+ - Rakefile
173
+ - README.textile
174
+ has_rdoc: true
175
+ homepage: http://github.com/jovoto-team/globalize3
176
+ licenses: []
177
+
178
+ post_install_message:
179
+ rdoc_options: []
180
+
181
+ require_paths:
182
+ - lib
183
+ required_ruby_version: !ruby/object:Gem::Requirement
184
+ none: false
185
+ requirements:
186
+ - - ">="
187
+ - !ruby/object:Gem::Version
188
+ hash: 3
189
+ segments:
190
+ - 0
191
+ version: "0"
192
+ required_rubygems_version: !ruby/object:Gem::Requirement
193
+ none: false
194
+ requirements:
195
+ - - ">"
196
+ - !ruby/object:Gem::Version
197
+ hash: 25
198
+ segments:
199
+ - 1
200
+ - 3
201
+ - 1
202
+ version: 1.3.1
203
+ requirements: []
204
+
205
+ rubyforge_project: "[none]"
206
+ rubygems_version: 1.3.7
207
+ signing_key:
208
+ specification_version: 3
209
+ summary: "Rails I18n: de-facto standard library for ActiveRecord 3 model/data translation"
210
+ test_files: []
211
+