single_table_globalize3 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +61 -0
- data/LICENSE +21 -0
- data/Rakefile +22 -0
- data/lib/generators/globalize3/migration_generator.rb +14 -0
- data/lib/generators/globalize3/templates/migration.rb +22 -0
- data/lib/globalize/active_record/act_macro.rb +66 -0
- data/lib/globalize/active_record/adapter.rb +112 -0
- data/lib/globalize/active_record/attributes.rb +27 -0
- data/lib/globalize/active_record/class_methods.rb +168 -0
- data/lib/globalize/active_record/exceptions.rb +19 -0
- data/lib/globalize/active_record/instance_methods.rb +210 -0
- data/lib/globalize/active_record/translation.rb +43 -0
- data/lib/globalize/active_record.rb +12 -0
- data/lib/globalize.rb +74 -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/uniqueness_validator.rb +61 -0
- data/lib/patches/active_record/xml_attribute_serializer.rb +13 -0
- data/lib/single_table_globalize3/version.rb +3 -0
- data/lib/single_table_globalize3.rb +1 -0
- metadata +198 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4d9d9b6bb1b7c9a01b80fa37c207d172b26bf321
|
4
|
+
data.tar.gz: 1f6344d88c60c9a226491d3280b2e23ae639e621
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3c82d94486a79cd745cdba46ca13463ad2cda67b321b654263c9e598a4bdb3422a9e286125c61407827c2d55068d1558b0b0c5d421a3b9302ce7ffdce0f2f45e
|
7
|
+
data.tar.gz: b22a0c72c1cad41885978ebe89b389afbd727e3bdeb230467beea39f2a3e54c694c76a6be52f30583a8cc01fe820fa077b7ab71b5456a1152a3200ff4050d74d
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
single_table_globalize3 (0.3.0)
|
5
|
+
activemodel (>= 3.0.0)
|
6
|
+
activerecord (>= 3.0.0)
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
activemodel (3.2.12)
|
12
|
+
activesupport (= 3.2.12)
|
13
|
+
builder (~> 3.0.0)
|
14
|
+
activerecord (3.2.12)
|
15
|
+
activemodel (= 3.2.12)
|
16
|
+
activesupport (= 3.2.12)
|
17
|
+
arel (~> 3.0.2)
|
18
|
+
tzinfo (~> 0.3.29)
|
19
|
+
activesupport (3.2.12)
|
20
|
+
i18n (~> 0.6)
|
21
|
+
multi_json (~> 1.0)
|
22
|
+
arel (3.0.2)
|
23
|
+
builder (3.0.4)
|
24
|
+
coderay (1.0.8)
|
25
|
+
database_cleaner (0.6.7)
|
26
|
+
i18n (0.6.1)
|
27
|
+
json (1.7.7)
|
28
|
+
metaclass (0.0.1)
|
29
|
+
method_source (0.8.1)
|
30
|
+
mocha (0.13.2)
|
31
|
+
metaclass (~> 0.0.1)
|
32
|
+
multi_json (1.6.1)
|
33
|
+
pathname_local (0.0.2)
|
34
|
+
pry (0.9.12)
|
35
|
+
coderay (~> 1.0.5)
|
36
|
+
method_source (~> 0.8)
|
37
|
+
slop (~> 3.4)
|
38
|
+
pry-nav (0.2.3)
|
39
|
+
pry (~> 0.9.10)
|
40
|
+
rake (10.0.3)
|
41
|
+
rdoc (3.12.1)
|
42
|
+
json (~> 1.4)
|
43
|
+
slop (3.4.3)
|
44
|
+
sqlite3 (1.3.7)
|
45
|
+
test_declarative (0.0.5)
|
46
|
+
tzinfo (0.3.35)
|
47
|
+
|
48
|
+
PLATFORMS
|
49
|
+
ruby
|
50
|
+
|
51
|
+
DEPENDENCIES
|
52
|
+
database_cleaner (~> 0.6.0)
|
53
|
+
mocha
|
54
|
+
pathname_local
|
55
|
+
pry
|
56
|
+
pry-nav
|
57
|
+
rake
|
58
|
+
rdoc
|
59
|
+
single_table_globalize3!
|
60
|
+
sqlite3
|
61
|
+
test_declarative
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2008, 2009, 2010, 2012 Sven Fuchs, Joshua Harvey, Clemens Kofler, John-Paul Bader, Trong Tran
|
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.
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rdoc/task'
|
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 = 'SingleTableGlobalize3'
|
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,14 @@
|
|
1
|
+
require 'rails/generators/active_record'
|
2
|
+
|
3
|
+
module SingleTableGlobalize3
|
4
|
+
module Generators
|
5
|
+
class MigrationGenerator < ActiveRecord::Generators::Base
|
6
|
+
source_root File.expand_path("../templates", __FILE__)
|
7
|
+
argument :name, :type => :string, :default => "application"
|
8
|
+
|
9
|
+
def copy_devise_migration
|
10
|
+
migration_template "migration.rb", "db/migrate/create_globalize_translations"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class CreateGlobalizeTranslations < ActiveRecord::Migration
|
2
|
+
def up
|
3
|
+
create_table :globalize_translations do |t|
|
4
|
+
t.integer :translatable_id
|
5
|
+
t.string :translatable_type
|
6
|
+
t.string :locale
|
7
|
+
t.string :attribute_name
|
8
|
+
t.text :value
|
9
|
+
t.timestamps
|
10
|
+
end
|
11
|
+
add_index :globalize_translations, [:translatable_id, :translatable_type], :name => :index_globalize_translalation_1
|
12
|
+
add_index :globalize_translations, [:translatable_id, :translatable_type, :locale], :name => :index_globalize_translalation_2
|
13
|
+
add_index :globalize_translations, [:translatable_id, :translatable_type, :locale, :attribute_name], :name => :index_globalize_translalation_3
|
14
|
+
end
|
15
|
+
|
16
|
+
def down
|
17
|
+
remove_index :globalize_translations, :name => :index_globalize_translalation_3
|
18
|
+
remove_index :globalize_translations, :name => :index_globalize_translalation_2
|
19
|
+
remove_index :globalize_translations, :name => :index_globalize_translalation_1
|
20
|
+
drop_table :globalize_translations
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Globalize
|
2
|
+
module ActiveRecord
|
3
|
+
module ActMacro
|
4
|
+
def translates(*attr_names)
|
5
|
+
|
6
|
+
options = attr_names.extract_options!
|
7
|
+
setup_translates!(options) unless translates?
|
8
|
+
|
9
|
+
attr_names = attr_names.map(&:to_sym)
|
10
|
+
attr_names -= translated_attribute_names if defined?(translated_attribute_names)
|
11
|
+
|
12
|
+
if attr_names.present?
|
13
|
+
translation_class.instance_eval %{
|
14
|
+
attr_accessible :#{attr_names.join(', :')}
|
15
|
+
}
|
16
|
+
|
17
|
+
attr_names.each do |attr_name|
|
18
|
+
# Detect and apply serialization.
|
19
|
+
serializer = self.serialized_attributes[attr_name.to_s]
|
20
|
+
if serializer.present?
|
21
|
+
if defined?(::ActiveRecord::Coders::YAMLColumn) &&
|
22
|
+
serializer.is_a?(::ActiveRecord::Coders::YAMLColumn)
|
23
|
+
|
24
|
+
serializer = serializer.object_class
|
25
|
+
end
|
26
|
+
|
27
|
+
translation_class.send :serialize, attr_name, serializer
|
28
|
+
end
|
29
|
+
|
30
|
+
# Create accessors for the attribute.
|
31
|
+
translated_attr_accessor(attr_name)
|
32
|
+
translations_accessor(attr_name)
|
33
|
+
|
34
|
+
# Add attribute to the list.
|
35
|
+
self.translated_attribute_names << attr_name
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def translates?
|
41
|
+
included_modules.include?(InstanceMethods)
|
42
|
+
end
|
43
|
+
|
44
|
+
protected
|
45
|
+
def setup_translates!(options)
|
46
|
+
class_attribute :translated_attribute_names, :translation_options, :fallbacks_for_empty_translations
|
47
|
+
self.translated_attribute_names = []
|
48
|
+
self.translation_options = options
|
49
|
+
self.fallbacks_for_empty_translations = options[:fallbacks_for_empty_translations]
|
50
|
+
|
51
|
+
include InstanceMethods
|
52
|
+
extend ClassMethods
|
53
|
+
|
54
|
+
has_many :translations, :class_name => translation_class.name,
|
55
|
+
:foreign_key => :translatable_id,
|
56
|
+
:dependent => :destroy,
|
57
|
+
:as => :translatable
|
58
|
+
|
59
|
+
after_create :save_translations!
|
60
|
+
after_update :save_translations!
|
61
|
+
|
62
|
+
translation_class.instance_eval %{ attr_accessible :locale }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,112 @@
|
|
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_accessor :record, :stash, :translations
|
7
|
+
private :record=, :stash=
|
8
|
+
|
9
|
+
delegate :translation_class, :to => :'record.class'
|
10
|
+
|
11
|
+
def initialize(record)
|
12
|
+
self.record = record
|
13
|
+
self.stash = Attributes.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def fetch_stash(locale, name)
|
17
|
+
value = stash.read(locale, name)
|
18
|
+
return value if value
|
19
|
+
return nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def stash_contains?(locale, name)
|
23
|
+
stash.contains?(locale, name)
|
24
|
+
end
|
25
|
+
|
26
|
+
def fetch(locale, name)
|
27
|
+
record.globalize_fallbacks(locale).each do |fallback|
|
28
|
+
value = stash.contains?(fallback, name) ? fetch_stash(fallback, name) : fetch_attribute(fallback, name)
|
29
|
+
|
30
|
+
unless fallbacks_for?(value)
|
31
|
+
set_metadata(value, :locale => fallback, :requested_locale => locale)
|
32
|
+
return value
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
return nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def write(locale, name, value)
|
40
|
+
stash.write(locale, name, value)
|
41
|
+
end
|
42
|
+
|
43
|
+
def save_translations!
|
44
|
+
existing_translations = {}
|
45
|
+
record.translations.each do |t|
|
46
|
+
locale_str = t.locale.to_s
|
47
|
+
existing_translations[locale_str] ||= {}
|
48
|
+
existing_translations[locale_str][t.attribute_name] = t
|
49
|
+
end
|
50
|
+
|
51
|
+
stash.each do |locale, attrs|
|
52
|
+
locale_str = locale.to_s
|
53
|
+
attrs.each do |name, value|
|
54
|
+
translation = existing_translations[locale_str].try('[]', name) ||
|
55
|
+
record.translations.build(:locale => locale_str, :attribute_name => name)
|
56
|
+
translation.update_attribute(:value, value)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
reset
|
61
|
+
end
|
62
|
+
|
63
|
+
def reset
|
64
|
+
stash.clear
|
65
|
+
end
|
66
|
+
|
67
|
+
protected
|
68
|
+
|
69
|
+
def type_cast(name, value)
|
70
|
+
if value.nil?
|
71
|
+
nil
|
72
|
+
elsif column = column_for_attribute(name)
|
73
|
+
column.type_cast(value)
|
74
|
+
else
|
75
|
+
value
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def column_for_attribute(name)
|
80
|
+
translation_class.columns_hash[name.to_s]
|
81
|
+
end
|
82
|
+
|
83
|
+
def unserializable_attribute?(name, column)
|
84
|
+
column.text? && translation_class.serialized_attributes[name.to_s]
|
85
|
+
end
|
86
|
+
|
87
|
+
def fetch_attribute(locale, name)
|
88
|
+
translation = record.translation_for(locale, name, false)
|
89
|
+
return translation && translation.value
|
90
|
+
end
|
91
|
+
|
92
|
+
def set_metadata(object, metadata)
|
93
|
+
object.translation_metadata.merge!(metadata) if object.respond_to?(:translation_metadata)
|
94
|
+
object
|
95
|
+
end
|
96
|
+
|
97
|
+
def translation_metadata_accessor(object)
|
98
|
+
return if obj.respond_to?(:translation_metadata)
|
99
|
+
class << object; attr_accessor :translation_metadata end
|
100
|
+
object.translation_metadata ||= {}
|
101
|
+
end
|
102
|
+
|
103
|
+
def fallbacks_for?(object)
|
104
|
+
object.nil? || (fallbacks_for_empty_translations? && object.blank?)
|
105
|
+
end
|
106
|
+
|
107
|
+
def fallbacks_for_empty_translations?
|
108
|
+
record.fallbacks_for_empty_translations
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,27 @@
|
|
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 # TODO: Think about using HashWithIndifferentAccess ?
|
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
|
+
#raise 'z' if value.nil? # TODO
|
23
|
+
self[locale][name.to_s] = value
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
module Globalize
|
2
|
+
module ActiveRecord
|
3
|
+
module ClassMethods
|
4
|
+
delegate :translated_locales, :to => :translation_class
|
5
|
+
|
6
|
+
def with_translations(*locales)
|
7
|
+
locales = [locales].flatten
|
8
|
+
index = locales.pop if locales.last.is_a?(Fixnum)
|
9
|
+
locales = Globalize.fallbacks if locales.empty?
|
10
|
+
joins("LEFT OUTER JOIN #{translation_class.table_name} #{translations_table_name(index)} ON #{translations_table_name(index)}.translatable_id = #{self.table_name}.id").
|
11
|
+
select("distinct #{table_name}.*").
|
12
|
+
where(translated_column_name('locale', index) => locales)
|
13
|
+
end
|
14
|
+
|
15
|
+
def with_translated_attribute(name, value, locales = nil)
|
16
|
+
locales ||= Globalize.fallbacks
|
17
|
+
self.join_index = self.join_index + 1
|
18
|
+
with_translations(locales, self.join_index).where(
|
19
|
+
translated_column_name('attribute_name', self.join_index) => name.to_s,
|
20
|
+
translated_column_name('value', self.join_index) => Array(value).map(&:to_s)
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
def translated?(name)
|
25
|
+
translated_attribute_names.include?(name.to_sym)
|
26
|
+
end
|
27
|
+
|
28
|
+
def required_attributes
|
29
|
+
validators.map { |v| v.attributes if v.is_a?(ActiveModel::Validations::PresenceValidator) }.flatten
|
30
|
+
end
|
31
|
+
|
32
|
+
def required_translated_attributes
|
33
|
+
translated_attribute_names & required_attributes
|
34
|
+
end
|
35
|
+
|
36
|
+
def translation_class
|
37
|
+
Globalize::ActiveRecord::Translation
|
38
|
+
end
|
39
|
+
|
40
|
+
def translations_table_name(index = nil)
|
41
|
+
"#{translation_class.table_name}#{index}"
|
42
|
+
end
|
43
|
+
|
44
|
+
def translated_column_name(name, index = nil)
|
45
|
+
"#{translations_table_name(index)}.#{name}"
|
46
|
+
end
|
47
|
+
|
48
|
+
if RUBY_VERSION < '1.9'
|
49
|
+
def respond_to?(method_id, *args, &block)
|
50
|
+
supported_on_missing?(method_id) || super
|
51
|
+
end
|
52
|
+
else
|
53
|
+
def respond_to_missing?(method_id, include_private = false)
|
54
|
+
supported_on_missing?(method_id) || super
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def supported_on_missing?(method_id)
|
59
|
+
return super unless RUBY_VERSION < '1.9' || respond_to?(:translated_attribute_names)
|
60
|
+
match = defined?(::ActiveRecord::DynamicFinderMatch) && (::ActiveRecord::DynamicFinderMatch.match(method_id) || ::ActiveRecord::DynamicScopeMatch.match(method_id))
|
61
|
+
return false if match.nil?
|
62
|
+
|
63
|
+
attribute_names = match.attribute_names.map(&:to_sym)
|
64
|
+
translated_attributes = attribute_names & translated_attribute_names
|
65
|
+
return false if translated_attributes.empty?
|
66
|
+
|
67
|
+
untranslated_attributes = attribute_names - translated_attributes
|
68
|
+
return false if untranslated_attributes.any?{|unt| ! respond_to?(:"scoped_by_#{unt}")}
|
69
|
+
return [match, attribute_names, translated_attributes, untranslated_attributes]
|
70
|
+
end
|
71
|
+
|
72
|
+
def method_missing(method_id, *arguments, &block)
|
73
|
+
match, attribute_names, translated_attributes, untranslated_attributes = supported_on_missing?(method_id)
|
74
|
+
return super unless match
|
75
|
+
|
76
|
+
scope = scoped
|
77
|
+
|
78
|
+
translated_attributes.each do |attr|
|
79
|
+
scope = scope.with_translated_attribute(attr, arguments[attribute_names.index(attr)])
|
80
|
+
end
|
81
|
+
|
82
|
+
untranslated_attributes.each do |unt|
|
83
|
+
index = attribute_names.index(unt)
|
84
|
+
raise StandarError unless index
|
85
|
+
scope = scope.send(:"scoped_by_#{unt}", arguments[index])
|
86
|
+
end
|
87
|
+
|
88
|
+
if defined?(::ActiveRecord::DynamicFinderMatch) && match.is_a?(::ActiveRecord::DynamicFinderMatch)
|
89
|
+
if match.instantiator? and scope.blank?
|
90
|
+
return scope.find_or_instantiator_by_attributes match, attribute_names, *arguments, &block
|
91
|
+
end
|
92
|
+
match_finder_method = match.finder.to_s
|
93
|
+
match_finder_method << "!" if match.bang? && ::ActiveRecord::VERSION::STRING >= "3.1.0"
|
94
|
+
return scope.send(match_finder_method).tap do |found|
|
95
|
+
found.is_a?(Array) ? found.map { |f| f.translations.reload } : found.translations.reload unless found.nil?
|
96
|
+
end
|
97
|
+
end
|
98
|
+
return scope
|
99
|
+
end
|
100
|
+
|
101
|
+
def find_or_instantiator_by_attributes(match, attributes, *args)
|
102
|
+
options = args.size > 1 && args.last(2).all?{ |a| a.is_a?(Hash) } ? args.extract_options! : {}
|
103
|
+
protected_attributes_for_create, unprotected_attributes_for_create = {}, {}
|
104
|
+
args.each_with_index do |arg, i|
|
105
|
+
if arg.is_a?(Hash)
|
106
|
+
protected_attributes_for_create = args[i].with_indifferent_access
|
107
|
+
else
|
108
|
+
unprotected_attributes_for_create[attributes[i]] = args[i]
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
record = if ::ActiveRecord::VERSION::STRING < "3.1.0"
|
113
|
+
new do |r|
|
114
|
+
r.send(:attributes=, protected_attributes_for_create, true) unless protected_attributes_for_create.empty?
|
115
|
+
r.send(:attributes=, unprotected_attributes_for_create, false) unless unprotected_attributes_for_create.empty?
|
116
|
+
end
|
117
|
+
else
|
118
|
+
new(protected_attributes_for_create, options) do |r|
|
119
|
+
r.assign_attributes(unprotected_attributes_for_create, :without_protection => true)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
yield(record) if block_given?
|
123
|
+
record.send(match.bang? ? :save! : :save) if match.instantiator.eql?(:create)
|
124
|
+
|
125
|
+
record
|
126
|
+
end
|
127
|
+
|
128
|
+
protected
|
129
|
+
|
130
|
+
def translated_attr_accessor(name)
|
131
|
+
define_method(:"#{name}=") do |value|
|
132
|
+
write_attribute(name, value)
|
133
|
+
end
|
134
|
+
define_method(name) do |*args|
|
135
|
+
read_attribute(name, {:locale => args.first})
|
136
|
+
end
|
137
|
+
alias_method :"#{name}_before_type_cast", name
|
138
|
+
end
|
139
|
+
|
140
|
+
def translations_accessor(name)
|
141
|
+
define_method(:"#{name}_translations") do
|
142
|
+
result = translations.each_with_object(HashWithIndifferentAccess.new) do |translation, result|
|
143
|
+
result[translation.locale] = translation.value
|
144
|
+
end
|
145
|
+
globalize.stash.keys.each_with_object(result) do |locale, result|
|
146
|
+
result[locale] = globalize.fetch_stash(locale, name) if globalize.stash_contains?(locale, name)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
define_method(:"#{name}_translations=") do |value|
|
150
|
+
value.each do |(locale, value)|
|
151
|
+
write_attribute name, value, :locale => locale
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def join_index
|
157
|
+
@join_index = 0 if @join_index.nil? || @join_index > 100
|
158
|
+
@join_index
|
159
|
+
end
|
160
|
+
|
161
|
+
def join_index=(value)
|
162
|
+
@join_index = value
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
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,210 @@
|
|
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 self.included(base)
|
15
|
+
# Maintain Rails 3.0.x compatibility while adding Rails 3.1.x compatibility
|
16
|
+
if base.method_defined?(:assign_attributes)
|
17
|
+
base.class_eval %{
|
18
|
+
def assign_attributes(attributes, options = {})
|
19
|
+
with_given_locale(attributes) { super }
|
20
|
+
end
|
21
|
+
}
|
22
|
+
else
|
23
|
+
base.class_eval %{
|
24
|
+
def attributes=(attributes, *args)
|
25
|
+
with_given_locale(attributes) { super }
|
26
|
+
end
|
27
|
+
|
28
|
+
def update_attributes!(attributes, *args)
|
29
|
+
with_given_locale(attributes) { super }
|
30
|
+
end
|
31
|
+
|
32
|
+
def update_attributes(attributes, *args)
|
33
|
+
with_given_locale(attributes) { super }
|
34
|
+
end
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Deprecate old use of locale
|
40
|
+
def deprecated_options(options)
|
41
|
+
unless options.is_a?(Hash)
|
42
|
+
warn "[DEPRECATION] passing 'locale' as #{options.inspect} is deprecated. Please use {:locale => #{options.inspect}} instead."
|
43
|
+
{:locale => options}
|
44
|
+
else
|
45
|
+
options
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def write_attribute(name, value, options = {})
|
50
|
+
if translated?(name)
|
51
|
+
options = {:locale => Globalize.locale}.merge(deprecated_options(options))
|
52
|
+
|
53
|
+
# Dirty tracking, paraphrased from
|
54
|
+
# ActiveRecord::AttributeMethods::Dirty#write_attribute.
|
55
|
+
name_str = name.to_s
|
56
|
+
# If there's already a change, delete it if this undoes the change.
|
57
|
+
if attribute_changed?(name_str) && value == changed_attributes[name_str]
|
58
|
+
changed_attributes.delete(name_str)
|
59
|
+
elsif !attribute_changed?(name_str) && value != (old = globalize.fetch(options[:locale], name))
|
60
|
+
changed_attributes[name_str] = old
|
61
|
+
end
|
62
|
+
|
63
|
+
globalize.write(options[:locale], name, value)
|
64
|
+
else
|
65
|
+
super(name, value)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def read_attribute(name, options = {})
|
70
|
+
options = {:translated => true, :locale => nil}.merge(deprecated_options(options))
|
71
|
+
if (self.class.translated?(name) && options[:translated]) && (value = globalize.fetch(options[:locale] || Globalize.locale, name))
|
72
|
+
value
|
73
|
+
else
|
74
|
+
super(name)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def attribute_names
|
79
|
+
translated_attribute_names.map(&:to_s) + super
|
80
|
+
end
|
81
|
+
|
82
|
+
def translated?(name)
|
83
|
+
self.class.translated?(name)
|
84
|
+
end
|
85
|
+
|
86
|
+
def translated_attributes
|
87
|
+
translated_attribute_names.inject({}) do |attributes, name|
|
88
|
+
attributes.merge(name.to_s => translation[name.to_s].try(:value))
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# This method is basically the method built into Rails
|
93
|
+
# but we have to pass {:translated => false}
|
94
|
+
def untranslated_attributes
|
95
|
+
attrs = {}
|
96
|
+
attribute_names.each do |name|
|
97
|
+
attrs[name] = read_attribute(name, {:translated => false})
|
98
|
+
end
|
99
|
+
attrs
|
100
|
+
end
|
101
|
+
|
102
|
+
def set_translations(options)
|
103
|
+
options.keys.each do |locale|
|
104
|
+
options[locale].each do |key, value|
|
105
|
+
raise NoMethodError.new("unknown attribute: #{key}") unless attribute_names.flatten.include?(key.to_s)
|
106
|
+
translation = translation_for(locale, key.to_s)
|
107
|
+
translation.value = value
|
108
|
+
translation.save
|
109
|
+
end
|
110
|
+
end
|
111
|
+
globalize.reset
|
112
|
+
end
|
113
|
+
|
114
|
+
def reload(options = nil)
|
115
|
+
translation_caches.clear
|
116
|
+
translated_attribute_names.each { |name| @attributes.delete(name.to_s) }
|
117
|
+
globalize.reset
|
118
|
+
super(options)
|
119
|
+
end
|
120
|
+
|
121
|
+
def clone
|
122
|
+
obj = super
|
123
|
+
return obj unless respond_to?(:translated_attribute_names)
|
124
|
+
|
125
|
+
obj.instance_variable_set(:@translations, nil) if new_record? # Reset the collection because of rails bug: http://pastie.org/1521874
|
126
|
+
obj.instance_variable_set(:@globalize, nil )
|
127
|
+
each_locale_and_translated_attribute do |locale, name|
|
128
|
+
obj.globalize.write(locale, name, globalize.fetch(locale, name) )
|
129
|
+
end
|
130
|
+
|
131
|
+
return obj
|
132
|
+
end
|
133
|
+
|
134
|
+
def translation
|
135
|
+
translations_for_locale(::Globalize.locale)
|
136
|
+
end
|
137
|
+
|
138
|
+
def translation_for(locale, name, build_if_missing = true)
|
139
|
+
translation_caches[locale] ||= {}
|
140
|
+
translation_caches[locale][name] ||= (translations.detect{|t| t.locale == locale && t.attribute_name == name.to_s}) ||
|
141
|
+
(translations.attribute(name).with_locale(locale).first) ||
|
142
|
+
(translations.build(:locale => locale, :attribute_name => name) if build_if_missing)
|
143
|
+
end
|
144
|
+
|
145
|
+
def translations_for_locale(locale)
|
146
|
+
translation_caches[locale] = if translations.loaded?
|
147
|
+
translations.select{|t| t.locale.to_s == locale.to_s }
|
148
|
+
else
|
149
|
+
translations.with_locale(locale)
|
150
|
+
end.inject({}) do |hash, t|
|
151
|
+
hash.update(t.attribute_name => t)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def translation_caches
|
156
|
+
@translation_caches ||= {}
|
157
|
+
end
|
158
|
+
|
159
|
+
def globalize_fallbacks(locale)
|
160
|
+
Globalize.fallbacks(locale)
|
161
|
+
end
|
162
|
+
|
163
|
+
private
|
164
|
+
|
165
|
+
def update(*)
|
166
|
+
I18n.with_locale(read_attribute(:locale) || I18n.default_locale) do
|
167
|
+
super
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def create(*)
|
172
|
+
I18n.with_locale(read_attribute(:locale) || I18n.default_locale) do
|
173
|
+
super
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
protected
|
178
|
+
|
179
|
+
def each_locale_and_translated_attribute
|
180
|
+
used_locales.each do |locale|
|
181
|
+
translated_attribute_names.each do |name|
|
182
|
+
yield locale, name
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def used_locales
|
188
|
+
globalize.stash.keys.concat(globalize.stash.keys).concat(translations.translated_locales).uniq
|
189
|
+
end
|
190
|
+
|
191
|
+
def save_translations!
|
192
|
+
globalize.save_translations!
|
193
|
+
translation_caches.clear
|
194
|
+
end
|
195
|
+
|
196
|
+
def with_given_locale(attributes, &block)
|
197
|
+
attributes.symbolize_keys! if attributes.respond_to?(:symbolize_keys!)
|
198
|
+
|
199
|
+
locale = respond_to?(:locale=) ? attributes.try(:[], :locale) :
|
200
|
+
attributes.try(:delete, :locale)
|
201
|
+
|
202
|
+
if locale
|
203
|
+
Globalize.with_locale(locale, &block)
|
204
|
+
else
|
205
|
+
yield
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Globalize
|
2
|
+
module ActiveRecord
|
3
|
+
class Translation < ::ActiveRecord::Base
|
4
|
+
if self.respond_to?(:table_name=)
|
5
|
+
self.table_name = :globalize_translations
|
6
|
+
else
|
7
|
+
set_table_name :globalize_translations
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_accessible :locale, :attribute_name, :value
|
11
|
+
validates :locale, :attribute_name, :presence => true
|
12
|
+
belongs_to :translatable, :polymorphic => true
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def with_locales(*locales)
|
16
|
+
# Avoid using "IN" with SQL queries when only using one locale.
|
17
|
+
locales = locales.flatten.map(&:to_s)
|
18
|
+
locales = locales.first if locales.one?
|
19
|
+
where :locale => locales
|
20
|
+
end
|
21
|
+
alias with_locale with_locales
|
22
|
+
|
23
|
+
def translated_locales
|
24
|
+
select('DISTINCT locale').map(&:locale).sort { |l,r| l.to_s <=> r.to_s }
|
25
|
+
end
|
26
|
+
|
27
|
+
def attribute(attribute)
|
28
|
+
attribute = attribute.to_s
|
29
|
+
where :attribute_name => attribute
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def locale
|
34
|
+
_locale = read_attribute :locale
|
35
|
+
_locale.present? ? _locale.to_sym : _locale
|
36
|
+
end
|
37
|
+
|
38
|
+
def locale=(locale)
|
39
|
+
write_attribute :locale, locale.to_s
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -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
|
data/lib/globalize.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'patches/active_record/xml_attribute_serializer'
|
3
|
+
require 'patches/active_record/query_method'
|
4
|
+
require 'patches/active_record/uniqueness_validator'
|
5
|
+
|
6
|
+
module Globalize
|
7
|
+
autoload :ActiveRecord, 'globalize/active_record'
|
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=(locales)
|
33
|
+
set_fallbacks(locales)
|
34
|
+
end
|
35
|
+
|
36
|
+
def i18n_fallbacks?
|
37
|
+
I18n.respond_to?(:fallbacks)
|
38
|
+
end
|
39
|
+
|
40
|
+
def fallbacks(for_locale = self.locale)
|
41
|
+
read_fallbacks[for_locale] || default_fallbacks(for_locale)
|
42
|
+
end
|
43
|
+
|
44
|
+
def default_fallbacks(for_locale = self.locale)
|
45
|
+
i18n_fallbacks? ? I18n.fallbacks[for_locale] : [for_locale.to_sym]
|
46
|
+
end
|
47
|
+
|
48
|
+
protected
|
49
|
+
|
50
|
+
def read_locale
|
51
|
+
Thread.current[:globalize_locale]
|
52
|
+
end
|
53
|
+
|
54
|
+
def set_locale(locale)
|
55
|
+
Thread.current[:globalize_locale] = locale.try(:to_sym)
|
56
|
+
end
|
57
|
+
|
58
|
+
def read_fallbacks
|
59
|
+
Thread.current[:fallbacks] || HashWithIndifferentAccess.new
|
60
|
+
end
|
61
|
+
|
62
|
+
def set_fallbacks(locales)
|
63
|
+
fallback_hash = HashWithIndifferentAccess.new
|
64
|
+
|
65
|
+
locales.each do |key, value|
|
66
|
+
fallback_hash[key] = value.presence || [key]
|
67
|
+
end if locales.present?
|
68
|
+
|
69
|
+
Thread.current[:fallbacks] = fallback_hash
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
ActiveRecord::Base.extend(Globalize::ActiveRecord::ActMacro)
|
@@ -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,61 @@
|
|
1
|
+
require 'active_record/validations/uniqueness.rb'
|
2
|
+
|
3
|
+
ActiveRecord::Validations::UniquenessValidator.class_eval do
|
4
|
+
def validate_each_with_translations(record, attribute, value)
|
5
|
+
klass = record.class
|
6
|
+
if klass.translates? && klass.translated?(attribute)
|
7
|
+
if methods.include?(:build_relation) || respond_to?(:build_relation)
|
8
|
+
finder_class = klass.translation_class
|
9
|
+
table = finder_class.arel_table
|
10
|
+
|
11
|
+
relation = build_relation(finder_class, table, 'attribute_name', attribute).
|
12
|
+
and(table[:locale].eq(Globalize.locale)).
|
13
|
+
and(table[:value].eq(value)).
|
14
|
+
and(table[:translatable_type].eq(klass.name))
|
15
|
+
relation = relation.and(table[klass.reflect_on_association(:translations).foreign_key].not_eq(record.send(:id))) if record.persisted?
|
16
|
+
|
17
|
+
# TODO: add scope with translated attributes
|
18
|
+
if options[:scope]
|
19
|
+
ActiveRecord::Base.logger.warn("WARNING: SingleTableGlobalize3 does not currently support `scope` option on uniqueness validation for translated attributes.")
|
20
|
+
end
|
21
|
+
|
22
|
+
if finder_class.unscoped.where(relation).exists?
|
23
|
+
record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value))
|
24
|
+
end
|
25
|
+
else
|
26
|
+
finder_class = klass.translation_class
|
27
|
+
table = finder_class.unscoped
|
28
|
+
|
29
|
+
table_name = finder_class.quoted_table_name
|
30
|
+
|
31
|
+
if value && klass.serialized_attributes.key?(attribute.to_s)
|
32
|
+
value = YAML.dump value
|
33
|
+
end
|
34
|
+
|
35
|
+
sql, params = mount_sql_and_params(finder_class, table_name, 'value', value)
|
36
|
+
|
37
|
+
relation = table.where(sql, *params).
|
38
|
+
where(:locale => Globalize.locale).
|
39
|
+
where(:attribute_name => attribute).
|
40
|
+
where(:translatable_type => klass.name)
|
41
|
+
|
42
|
+
Array.wrap(options[:scope]).each do |scope_item|
|
43
|
+
scope_value = record.send(scope_item)
|
44
|
+
relation = relation.where(scope_item => scope_value)
|
45
|
+
end
|
46
|
+
|
47
|
+
if record.persisted?
|
48
|
+
fk = klass.reflect_on_association(:translations).options[:foreign_key]
|
49
|
+
relation = relation.where(finder_class.arel_table[fk].not_eq(record.send(:id)))
|
50
|
+
end
|
51
|
+
|
52
|
+
if relation.exists?
|
53
|
+
record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value))
|
54
|
+
end
|
55
|
+
end
|
56
|
+
else
|
57
|
+
validate_each_without_translations(record, attribute, value)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
alias_method_chain :validate_each, :translations
|
61
|
+
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 @@
|
|
1
|
+
require 'globalize'
|
metadata
ADDED
@@ -0,0 +1,198 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: single_table_globalize3
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sven Fuchs
|
8
|
+
- Joshua Harvey
|
9
|
+
- Clemens Kofler
|
10
|
+
- John-Paul Bader
|
11
|
+
- Tomasz Stachewicz
|
12
|
+
- Philip Arndt
|
13
|
+
- Trong Tran
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
date: 2013-03-04 00:00:00.000000000 Z
|
18
|
+
dependencies:
|
19
|
+
- !ruby/object:Gem::Dependency
|
20
|
+
name: activerecord
|
21
|
+
requirement: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - '>='
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: 3.0.0
|
26
|
+
type: :runtime
|
27
|
+
prerelease: false
|
28
|
+
version_requirements: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 3.0.0
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: activemodel
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - '>='
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 3.0.0
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 3.0.0
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: database_cleaner
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 0.6.0
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ~>
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: 0.6.0
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: mocha
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - '>='
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: pathname_local
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - '>='
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - '>='
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: test_declarative
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - '>='
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - '>='
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
- !ruby/object:Gem::Dependency
|
104
|
+
name: sqlite3
|
105
|
+
requirement: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
type: :development
|
111
|
+
prerelease: false
|
112
|
+
version_requirements: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - '>='
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
- !ruby/object:Gem::Dependency
|
118
|
+
name: rake
|
119
|
+
requirement: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - '>='
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
124
|
+
type: :development
|
125
|
+
prerelease: false
|
126
|
+
version_requirements: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - '>='
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0'
|
131
|
+
- !ruby/object:Gem::Dependency
|
132
|
+
name: rdoc
|
133
|
+
requirement: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - '>='
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
138
|
+
type: :development
|
139
|
+
prerelease: false
|
140
|
+
version_requirements: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - '>='
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '0'
|
145
|
+
description: Rails I18n de-facto standard library for ActiveRecord 3 model/data translation
|
146
|
+
with single table.
|
147
|
+
email: trong.v.tran@gmail.com
|
148
|
+
executables: []
|
149
|
+
extensions: []
|
150
|
+
extra_rdoc_files: []
|
151
|
+
files:
|
152
|
+
- lib/generators/globalize3/migration_generator.rb
|
153
|
+
- lib/generators/globalize3/templates/migration.rb
|
154
|
+
- lib/globalize/active_record/act_macro.rb
|
155
|
+
- lib/globalize/active_record/adapter.rb
|
156
|
+
- lib/globalize/active_record/attributes.rb
|
157
|
+
- lib/globalize/active_record/class_methods.rb
|
158
|
+
- lib/globalize/active_record/exceptions.rb
|
159
|
+
- lib/globalize/active_record/instance_methods.rb
|
160
|
+
- lib/globalize/active_record/translation.rb
|
161
|
+
- lib/globalize/active_record.rb
|
162
|
+
- lib/globalize.rb
|
163
|
+
- lib/i18n/missing_translations_log_handler.rb
|
164
|
+
- lib/i18n/missing_translations_raise_handler.rb
|
165
|
+
- lib/patches/active_record/query_method.rb
|
166
|
+
- lib/patches/active_record/uniqueness_validator.rb
|
167
|
+
- lib/patches/active_record/xml_attribute_serializer.rb
|
168
|
+
- lib/single_table_globalize3/version.rb
|
169
|
+
- lib/single_table_globalize3.rb
|
170
|
+
- Gemfile
|
171
|
+
- Gemfile.lock
|
172
|
+
- LICENSE
|
173
|
+
- Rakefile
|
174
|
+
homepage: http://github.com/trongrg/single_table_globalize3
|
175
|
+
licenses: []
|
176
|
+
metadata: {}
|
177
|
+
post_install_message:
|
178
|
+
rdoc_options: []
|
179
|
+
require_paths:
|
180
|
+
- lib
|
181
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
182
|
+
requirements:
|
183
|
+
- - '>='
|
184
|
+
- !ruby/object:Gem::Version
|
185
|
+
version: '0'
|
186
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
187
|
+
requirements:
|
188
|
+
- - '>='
|
189
|
+
- !ruby/object:Gem::Version
|
190
|
+
version: '0'
|
191
|
+
requirements: []
|
192
|
+
rubyforge_project: '[none]'
|
193
|
+
rubygems_version: 2.0.0
|
194
|
+
signing_key:
|
195
|
+
specification_version: 4
|
196
|
+
summary: Rails I18n de-facto standard library for ActiveRecord 3 model/data translation
|
197
|
+
with single table
|
198
|
+
test_files: []
|