globalize3 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- globalize3 (0.2.0.beta8)
4
+ globalize3 (0.3.0)
5
5
  activemodel (>= 3.0.0)
6
6
  activerecord (>= 3.0.0)
7
7
  paper_trail (~> 2)
@@ -9,93 +9,73 @@ PATH
9
9
  GEM
10
10
  remote: http://rubygems.org/
11
11
  specs:
12
- actionmailer (3.2.1)
13
- actionpack (= 3.2.1)
14
- mail (~> 2.4.0)
15
- actionpack (3.2.1)
16
- activemodel (= 3.2.1)
17
- activesupport (= 3.2.1)
12
+ actionpack (3.2.9)
13
+ activemodel (= 3.2.9)
14
+ activesupport (= 3.2.9)
18
15
  builder (~> 3.0.0)
19
16
  erubis (~> 2.7.0)
20
- journey (~> 1.0.1)
17
+ journey (~> 1.0.4)
21
18
  rack (~> 1.4.0)
22
- rack-cache (~> 1.1)
19
+ rack-cache (~> 1.2)
23
20
  rack-test (~> 0.6.1)
24
- sprockets (~> 2.1.2)
25
- activemodel (3.2.1)
26
- activesupport (= 3.2.1)
21
+ sprockets (~> 2.2.1)
22
+ activemodel (3.2.9)
23
+ activesupport (= 3.2.9)
27
24
  builder (~> 3.0.0)
28
- activerecord (3.2.1)
29
- activemodel (= 3.2.1)
30
- activesupport (= 3.2.1)
31
- arel (~> 3.0.0)
25
+ activerecord (3.2.9)
26
+ activemodel (= 3.2.9)
27
+ activesupport (= 3.2.9)
28
+ arel (~> 3.0.2)
32
29
  tzinfo (~> 0.3.29)
33
- activeresource (3.2.1)
34
- activemodel (= 3.2.1)
35
- activesupport (= 3.2.1)
36
- activesupport (3.2.1)
30
+ activesupport (3.2.9)
37
31
  i18n (~> 0.6)
38
32
  multi_json (~> 1.0)
39
- arel (3.0.0)
40
- builder (3.0.0)
33
+ arel (3.0.2)
34
+ builder (3.0.4)
41
35
  database_cleaner (0.5.2)
42
36
  erubis (2.7.0)
43
37
  hike (1.2.1)
44
- i18n (0.6.0)
45
- journey (1.0.1)
46
- json (1.6.5)
47
- mail (2.4.1)
48
- i18n (>= 0.4.0)
49
- mime-types (~> 1.16)
50
- treetop (~> 1.4.8)
38
+ i18n (0.6.1)
39
+ journey (1.0.4)
40
+ json (1.7.5)
51
41
  metaclass (0.0.1)
52
- mime-types (1.17.2)
53
- mocha (0.10.3)
42
+ mocha (0.12.3)
54
43
  metaclass (~> 0.0.1)
55
- multi_json (1.0.4)
56
- paper_trail (2.6.0)
57
- rails (~> 3)
44
+ multi_json (1.5.0)
45
+ paper_trail (2.6.4)
46
+ activerecord (~> 3.0)
47
+ railties (~> 3.0)
58
48
  pathname_local (0.0.2)
59
- polyglot (0.3.3)
60
49
  rack (1.4.1)
61
- rack-cache (1.1)
50
+ rack-cache (1.2)
62
51
  rack (>= 0.4)
63
52
  rack-ssl (1.3.2)
64
53
  rack
65
- rack-test (0.6.1)
54
+ rack-test (0.6.2)
66
55
  rack (>= 1.0)
67
- rails (3.2.1)
68
- actionmailer (= 3.2.1)
69
- actionpack (= 3.2.1)
70
- activerecord (= 3.2.1)
71
- activeresource (= 3.2.1)
72
- activesupport (= 3.2.1)
73
- bundler (~> 1.0)
74
- railties (= 3.2.1)
75
- railties (3.2.1)
76
- actionpack (= 3.2.1)
77
- activesupport (= 3.2.1)
56
+ railties (3.2.9)
57
+ actionpack (= 3.2.9)
58
+ activesupport (= 3.2.9)
78
59
  rack-ssl (~> 1.3.2)
79
60
  rake (>= 0.8.7)
80
61
  rdoc (~> 3.4)
81
- thor (~> 0.14.6)
82
- rake (0.9.2.2)
62
+ thor (>= 0.14.6, < 2.0)
63
+ rake (10.0.3)
83
64
  rdoc (3.12)
84
65
  json (~> 1.4)
85
- sprockets (2.1.2)
66
+ sprockets (2.2.2)
86
67
  hike (~> 1.2)
68
+ multi_json (~> 1.0)
87
69
  rack (~> 1.0)
88
70
  tilt (~> 1.1, != 1.3.0)
89
- sqlite3 (1.3.5)
71
+ sqlite3 (1.3.6)
90
72
  test_declarative (0.0.5)
91
- thor (0.14.6)
73
+ thor (0.16.0)
92
74
  tilt (1.3.3)
93
- treetop (1.4.10)
94
- polyglot
95
- polyglot (>= 0.3.1)
96
- tzinfo (0.3.31)
75
+ tzinfo (0.3.35)
97
76
 
98
77
  PLATFORMS
78
+ java
99
79
  ruby
100
80
 
101
81
  DEPENDENCIES
data/Rakefile CHANGED
@@ -1,6 +1,6 @@
1
1
  require 'rake'
2
2
  require 'rake/testtask'
3
- require 'rake/rdoctask'
3
+ require 'rdoc/task'
4
4
 
5
5
  desc 'Default: run unit tests.'
6
6
  task :default => :test
data/lib/globalize.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'active_record'
2
2
  require 'patches/active_record/xml_attribute_serializer'
3
3
  require 'patches/active_record/query_method'
4
+ require 'patches/active_record/uniqueness_validator'
4
5
 
5
6
  module Globalize
6
7
  autoload :ActiveRecord, 'globalize/active_record'
@@ -29,12 +30,20 @@ module Globalize
29
30
  end
30
31
  end
31
32
 
32
- def fallbacks?
33
+ def fallbacks=(locales)
34
+ set_fallbacks(locales)
35
+ end
36
+
37
+ def i18n_fallbacks?
33
38
  I18n.respond_to?(:fallbacks)
34
39
  end
35
40
 
36
- def fallbacks(locale = self.locale)
37
- fallbacks? ? I18n.fallbacks[locale] : [locale.to_sym]
41
+ def fallbacks(for_locale = self.locale)
42
+ read_fallbacks[for_locale] || default_fallbacks(for_locale)
43
+ end
44
+
45
+ def default_fallbacks(for_locale = self.locale)
46
+ i18n_fallbacks? ? I18n.fallbacks[for_locale] : [for_locale.to_sym]
38
47
  end
39
48
 
40
49
  protected
@@ -44,7 +53,21 @@ module Globalize
44
53
  end
45
54
 
46
55
  def set_locale(locale)
47
- Thread.current[:globalize_locale] = locale.to_sym rescue nil
56
+ Thread.current[:globalize_locale] = locale.try(:to_sym)
57
+ end
58
+
59
+ def read_fallbacks
60
+ Thread.current[:fallbacks] || HashWithIndifferentAccess.new
61
+ end
62
+
63
+ def set_fallbacks(locales)
64
+ fallback_hash = HashWithIndifferentAccess.new
65
+
66
+ locales.each do |key, value|
67
+ fallback_hash[key] = value.presence || [key]
68
+ end if locales.present?
69
+
70
+ Thread.current[:fallbacks] = fallback_hash
48
71
  end
49
72
  end
50
73
  end
@@ -2,37 +2,62 @@ module Globalize
2
2
  module ActiveRecord
3
3
  module ActMacro
4
4
  def translates(*attr_names)
5
- return if translates?
6
5
 
7
6
  options = attr_names.extract_options!
8
- options[:table_name] ||= "#{table_name.singularize}_translations"
7
+ attr_names = attr_names.map(&:to_sym)
9
8
 
10
- class_attribute :translated_attribute_names, :translation_options, :fallbacks_for_empty_translations
11
- self.translated_attribute_names = attr_names.map(&:to_sym)
12
- self.translation_options = options
13
- self.fallbacks_for_empty_translations = options[:fallbacks_for_empty_translations]
9
+ unless translates?
10
+ options[:table_name] ||= "#{table_name.singularize}_translations"
11
+ options[:foreign_key] ||= class_name.foreign_key
14
12
 
15
- include InstanceMethods
16
- extend ClassMethods, Migration
13
+ class_attribute :translated_attribute_names, :translation_options, :fallbacks_for_empty_translations
14
+ self.translated_attribute_names = []
15
+ self.translation_options = options
16
+ self.fallbacks_for_empty_translations = options[:fallbacks_for_empty_translations]
17
17
 
18
- translation_class.table_name = options[:table_name] if translation_class.table_name.blank?
18
+ include InstanceMethods
19
+ extend ClassMethods, Migration
19
20
 
20
- has_many :translations, :class_name => translation_class.name,
21
- :foreign_key => class_name.foreign_key,
22
- :dependent => :destroy,
23
- :extend => HasManyExtensions
21
+ translation_class.table_name = options[:table_name] if translation_class.table_name.blank?
24
22
 
25
- after_create :save_translations!
26
- after_update :save_translations!
23
+ has_many :translations, :class_name => translation_class.name,
24
+ :foreign_key => options[:foreign_key],
25
+ :dependent => :destroy,
26
+ :extend => HasManyExtensions
27
27
 
28
- if options[:versioning]
29
- ::ActiveRecord::Base.extend(Globalize::Versioning::PaperTrail)
28
+ after_create :save_translations!
29
+ after_update :save_translations!
30
30
 
31
- translation_class.has_paper_trail
32
- delegate :version, :versions, :to => :translation
31
+ if options[:versioning]
32
+ ::ActiveRecord::Base.extend(Globalize::Versioning::PaperTrail)
33
+
34
+ translation_class.has_paper_trail
35
+ delegate :version, :versions, :to => :translation
36
+ end
37
+
38
+ translation_class.instance_eval %{
39
+ attr_accessible :#{(attr_names | [:locale]).join(', :')}
40
+ }
41
+
42
+ # detect and apply serialization
43
+ attr_names.each do |attr_name|
44
+ serializer = self.serialized_attributes[attr_name.to_s]
45
+
46
+ if serializer.present?
47
+ if defined?(::ActiveRecord::Coders::YAMLColumn) &&
48
+ serializer.is_a?(::ActiveRecord::Coders::YAMLColumn)
49
+
50
+ serializer = serializer.object_class
51
+ end
52
+
53
+ translation_class.send :serialize, attr_name, serializer
54
+ end
55
+ end
33
56
  end
34
57
 
35
- attr_names.each { |attr_name| translated_attr_accessor(attr_name) }
58
+ new_attr_names = attr_names - translated_attribute_names
59
+ new_attr_names.each { |attr_name| translated_attr_accessor(attr_name) }
60
+ self.translated_attribute_names += new_attr_names
36
61
  end
37
62
 
38
63
  def class_name
@@ -20,14 +20,15 @@ module Globalize
20
20
  end
21
21
 
22
22
  def fetch(locale, name)
23
- Globalize.fallbacks(locale).each do |fallback|
24
- value = fetch_stash(fallback, name) || fetch_attribute(fallback, name)
23
+ record.globalize_fallbacks(locale).each do |fallback|
24
+ value = stash.contains?(fallback, name) ? fetch_stash(fallback, name) : fetch_attribute(fallback, name)
25
25
 
26
26
  unless fallbacks_for?(value)
27
27
  set_metadata(value, :locale => fallback, :requested_locale => locale)
28
28
  return value
29
29
  end
30
30
  end
31
+
31
32
  return nil
32
33
  end
33
34
 
@@ -74,7 +75,7 @@ module Globalize
74
75
  end
75
76
 
76
77
  def fetch_attribute(locale, name)
77
- translation = record.translation_for(locale)
78
+ translation = record.translation_for(locale, false)
78
79
  return translation && translation.send(name)
79
80
  end
80
81
 
@@ -45,7 +45,7 @@ module Globalize
45
45
  klass = self.const_set(:Translation, Class.new(Globalize::ActiveRecord::Translation))
46
46
  end
47
47
 
48
- klass.belongs_to name.underscore.gsub('/', '_')
48
+ klass.belongs_to name.underscore.gsub('/', '_').to_sym, :class_name => self.name
49
49
  klass
50
50
  end
51
51
  end
@@ -70,7 +70,7 @@ module Globalize
70
70
 
71
71
  def supported_on_missing?(method_id)
72
72
  return super unless RUBY_VERSION < '1.9' || respond_to?(:translated_attribute_names)
73
- match = ::ActiveRecord::DynamicFinderMatch.match(method_id) || ::ActiveRecord::DynamicScopeMatch.match(method_id)
73
+ match = defined?(::ActiveRecord::DynamicFinderMatch) && (::ActiveRecord::DynamicFinderMatch.match(method_id) || ::ActiveRecord::DynamicScopeMatch.match(method_id))
74
74
  return false if match.nil?
75
75
 
76
76
  attribute_names = match.attribute_names.map(&:to_sym)
@@ -98,12 +98,46 @@ module Globalize
98
98
  scope = scope.send(:"scoped_by_#{unt}", arguments[index])
99
99
  end
100
100
 
101
- return scope.send(match.finder).tap do |found|
102
- found.is_a?(Array) ? found.map { |f| f.translations.reload } : found.translations.reload unless found.nil?
103
- end if match.is_a?(::ActiveRecord::DynamicFinderMatch)
101
+ if defined?(::ActiveRecord::DynamicFinderMatch) && match.is_a?(::ActiveRecord::DynamicFinderMatch)
102
+ if match.instantiator? and scope.blank?
103
+ return scope.find_or_instantiator_by_attributes match, attribute_names, *arguments, &block
104
+ end
105
+ match_finder_method = match.finder.to_s
106
+ match_finder_method << "!" if match.bang? && ::ActiveRecord::VERSION::STRING >= "3.1.0"
107
+ return scope.send(match_finder_method).tap do |found|
108
+ found.is_a?(Array) ? found.map { |f| f.translations.reload } : found.translations.reload unless found.nil?
109
+ end
110
+ end
104
111
  return scope
105
112
  end
106
113
 
114
+ def find_or_instantiator_by_attributes(match, attributes, *args)
115
+ options = args.size > 1 && args.last(2).all?{ |a| a.is_a?(Hash) } ? args.extract_options! : {}
116
+ protected_attributes_for_create, unprotected_attributes_for_create = {}, {}
117
+ args.each_with_index do |arg, i|
118
+ if arg.is_a?(Hash)
119
+ protected_attributes_for_create = args[i].with_indifferent_access
120
+ else
121
+ unprotected_attributes_for_create[attributes[i]] = args[i]
122
+ end
123
+ end
124
+
125
+ record = if ::ActiveRecord::VERSION::STRING < "3.1.0"
126
+ new do |r|
127
+ r.send(:attributes=, protected_attributes_for_create, true) unless protected_attributes_for_create.empty?
128
+ r.send(:attributes=, unprotected_attributes_for_create, false) unless unprotected_attributes_for_create.empty?
129
+ end
130
+ else
131
+ new(protected_attributes_for_create, options) do |r|
132
+ r.assign_attributes(unprotected_attributes_for_create, :without_protection => true)
133
+ end
134
+ end
135
+ yield(record) if block_given?
136
+ record.send(match.bang? ? :save! : :save) if match.instantiator.eql?(:create)
137
+
138
+ record
139
+ end
140
+
107
141
  protected
108
142
 
109
143
  def translated_attr_accessor(name)
@@ -37,19 +37,16 @@ module Globalize
37
37
  end
38
38
 
39
39
  def write_attribute(name, value, options = {})
40
- # raise 'y' if value.nil? # TODO.
41
-
42
40
  if translated?(name)
43
41
  # Deprecate old use of locale
44
42
  unless options.is_a?(Hash)
45
43
  warn "[DEPRECATION] passing 'locale' as #{options.inspect} is deprecated. Please use {:locale => #{options.inspect}} instead."
46
44
  options = {:locale => options}
47
45
  end
48
- options = {:locale => nil}.merge(options)
46
+ options = {:locale => Globalize.locale}.merge(options)
49
47
  attribute_will_change! name.to_s
50
- the_locale = options[:locale] || Globalize.locale
51
- self.translations.reject!{|t| t.new_record? && t.locale != the_locale}
52
- globalize.write(the_locale, name, value)
48
+
49
+ globalize.write(options[:locale], name, value)
53
50
  else
54
51
  super(name, value)
55
52
  end
@@ -64,7 +61,11 @@ module Globalize
64
61
 
65
62
  options = {:translated => true, :locale => nil}.merge(options)
66
63
  if self.class.translated?(name) and options[:translated]
67
- globalize.fetch(options[:locale] || Globalize.locale, name)
64
+ if (value = globalize.fetch(options[:locale] || Globalize.locale, name))
65
+ value
66
+ elsif respond_to?(:serialized_attributes) && serialized_attributes.has_key?(name.to_s)
67
+ super(name)
68
+ end
68
69
  else
69
70
  super(name)
70
71
  end
@@ -97,12 +98,17 @@ module Globalize
97
98
  def set_translations(options)
98
99
  options.keys.each do |locale|
99
100
  translation = translation_for(locale) ||
100
- translations.build(:locale => locale.to_s)
101
- translation.update_attributes!(options[locale])
101
+ translations.build(:locale => locale.to_s)
102
+
103
+ options[locale].each do |key, value|
104
+ translation.send :"#{key}=", value
105
+ end
106
+ translation.save
102
107
  end
103
108
  end
104
109
 
105
110
  def reload(options = nil)
111
+ translation_caches.clear
106
112
  translated_attribute_names.each { |name| @attributes.delete(name.to_s) }
107
113
  globalize.reset
108
114
  super(options)
@@ -125,20 +131,27 @@ module Globalize
125
131
  translation_for(::Globalize.locale)
126
132
  end
127
133
 
128
- def translation_for(locale)
129
- @translation_caches ||= {}
130
- unless @translation_caches[locale]
134
+ def translation_for(locale, build_if_missing = true)
135
+ unless translation_caches[locale]
131
136
  # Fetch translations from database as those in the translation collection may be incomplete
132
137
  _translation = translations.detect{|t| t.locale.to_s == locale.to_s}
133
- _translation ||= translations.with_locale(locale).first
134
- _translation ||= translations.build(:locale => locale)
135
- @translation_caches[locale] = _translation
138
+ _translation ||= translations.with_locale(locale).first unless translations.loaded?
139
+ _translation ||= translations.build(:locale => locale) if build_if_missing
140
+ translation_caches[locale] = _translation if _translation
136
141
  end
137
- @translation_caches[locale]
142
+ translation_caches[locale]
143
+ end
144
+
145
+ def translation_caches
146
+ @translation_caches ||= {}
147
+ end
148
+
149
+ def globalize_fallbacks(locale)
150
+ Globalize.fallbacks(locale)
138
151
  end
139
152
 
140
153
  def rollback
141
- @translation_caches[::Globalize.locale] = translation.previous_version
154
+ translation_caches[::Globalize.locale] = translation.previous_version
142
155
  end
143
156
 
144
157
  protected
@@ -159,7 +172,7 @@ module Globalize
159
172
 
160
173
  def save_translations!
161
174
  globalize.save_translations!
162
- @translation_caches = {}
175
+ translation_caches.clear
163
176
  end
164
177
 
165
178
  def with_given_locale(attributes, &block)
@@ -10,7 +10,8 @@ module Globalize
10
10
  end
11
11
 
12
12
  delegate :create_translation_table!, :drop_translation_table!,
13
- :translation_index_name, :to => :globalize_migrator
13
+ :translation_index_name, :translation_locale_index_name,
14
+ :to => :globalize_migrator
14
15
 
15
16
  class Migrator
16
17
  include Globalize::ActiveRecord::Exceptions
@@ -58,7 +59,13 @@ module Globalize
58
59
  connection.create_table(translations_table_name) do |t|
59
60
  t.references table_name.sub(/^#{table_name_prefix}/, '').singularize
60
61
  t.string :locale
61
- fields.each { |name, type| t.column name, type }
62
+ fields.each do |name, options|
63
+ if options.is_a? Hash
64
+ t.column name, options.delete(:type), options
65
+ else
66
+ t.column name, options
67
+ end
68
+ end
62
69
  t.timestamps
63
70
  end
64
71
  end
@@ -72,7 +79,8 @@ module Globalize
72
79
  # index for select('DISTINCT locale') call in translation.rb
73
80
  connection.add_index(
74
81
  translations_table_name,
75
- :locale
82
+ :locale,
83
+ :name => translation_locale_index_name
76
84
  )
77
85
  end
78
86
 
@@ -112,9 +120,13 @@ module Globalize
112
120
  end
113
121
 
114
122
  def validate_translated_fields
115
- fields.each do |name, type|
123
+ fields.each do |name, options|
116
124
  raise BadFieldName.new(name) unless valid_field_name?(name)
117
- raise BadFieldType.new(name, type) unless valid_field_type?(name, type)
125
+ if options.is_a? Hash
126
+ raise BadFieldType.new(name, options[:type]) unless valid_field_type?(name, options[:type])
127
+ else
128
+ raise BadFieldType.new(name, options) unless valid_field_type?(name, options)
129
+ end
118
130
  end
119
131
  end
120
132
 
@@ -131,9 +143,13 @@ module Globalize
131
143
  end
132
144
 
133
145
  def translation_index_name
134
- # FIXME what's the max size of an index name?
135
146
  index_name = "index_#{translations_table_name}_on_#{table_name.singularize}_id"
136
- index_name.size < 50 ? index_name : "index_#{Digest::SHA1.hexdigest(index_name)}"
147
+ index_name.size < connection.index_name_length ? index_name : "index_#{Digest::SHA1.hexdigest(index_name)}"
148
+ end
149
+
150
+ def translation_locale_index_name
151
+ index_name = "index_#{translations_table_name}_on_locale"
152
+ index_name.size < connection.index_name_length ? index_name : "index_#{Digest::SHA1.hexdigest(index_name)}"
137
153
  end
138
154
 
139
155
  def clear_schema_cache!
@@ -1,6 +1,10 @@
1
1
  module Globalize
2
2
  module ActiveRecord
3
3
  class Translation < ::ActiveRecord::Base
4
+
5
+ attr_accessible :locale
6
+ validates :locale, :presence => true
7
+
4
8
  class << self
5
9
  # Sometimes ActiveRecord queries .table_exists? before the table name
6
10
  # has even been set which results in catastrophic failure.
@@ -12,7 +16,7 @@ module Globalize
12
16
  # Avoid using "IN" with SQL queries when only using one locale.
13
17
  locales = locales.flatten.map(&:to_s)
14
18
  locales = locales.first if locales.one?
15
- where(:locale => locales)
19
+ where :locale => locales
16
20
  end
17
21
  alias with_locale with_locales
18
22
 
@@ -22,11 +26,12 @@ module Globalize
22
26
  end
23
27
 
24
28
  def locale
25
- read_attribute(:locale).to_sym
29
+ _locale = read_attribute :locale
30
+ _locale.present? ? _locale.to_sym : _locale
26
31
  end
27
32
 
28
33
  def locale=(locale)
29
- write_attribute(:locale, locale.to_s)
34
+ write_attribute :locale, locale.to_s
30
35
  end
31
36
  end
32
37
  end
@@ -1,3 +1,3 @@
1
1
  module Globalize3
2
- VERSION = '0.2.0'
2
+ VERSION = '0.3.0'
3
3
  end
@@ -0,0 +1,55 @@
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, value).and(table[:locale].eq(Globalize.locale))
12
+ relation = relation.and(table[klass.reflect_on_association(:translations).foreign_key].not_eq(record.send(:id))) if record.persisted?
13
+
14
+ # TODO: add scope with translated attributes
15
+ if options[:scope]
16
+ ActiveRecord::Base.logger.warn("WARNING: Globalize3 does not currently support `scope` option on uniqueness validation for translated attributes.")
17
+ end
18
+
19
+ if finder_class.unscoped.where(relation).exists?
20
+ record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value))
21
+ end
22
+ else
23
+ finder_class = klass.translation_class
24
+ table = finder_class.unscoped
25
+
26
+ table_name = finder_class.quoted_table_name
27
+
28
+ if value && klass.serialized_attributes.key?(attribute.to_s)
29
+ value = YAML.dump value
30
+ end
31
+
32
+ sql, params = mount_sql_and_params(finder_class, table_name, attribute, value)
33
+
34
+ relation = table.where(sql, *params).where(:locale => Globalize.locale)
35
+
36
+ Array.wrap(options[:scope]).each do |scope_item|
37
+ scope_value = record.send(scope_item)
38
+ relation = relation.where(scope_item => scope_value)
39
+ end
40
+
41
+ if record.persisted?
42
+ fk = klass.reflect_on_association(:translations).options[:foreign_key]
43
+ relation = relation.where(finder_class.arel_table[fk].not_eq(record.send(:id)))
44
+ end
45
+
46
+ if relation.exists?
47
+ record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value))
48
+ end
49
+ end
50
+ else
51
+ validate_each_without_translations(record, attribute, value)
52
+ end
53
+ end
54
+ alias_method_chain :validate_each, :translations
55
+ end
metadata CHANGED
@@ -1,15 +1,10 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: globalize3
3
- version: !ruby/object:Gem::Version
4
- hash: 23
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
5
  prerelease:
6
- segments:
7
- - 0
8
- - 2
9
- - 0
10
- version: 0.2.0
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - Sven Fuchs
14
9
  - Joshua Harvey
15
10
  - Clemens Kofler
@@ -19,136 +14,142 @@ authors:
19
14
  autorequire:
20
15
  bindir: bin
21
16
  cert_chain: []
22
-
23
- date: 2012-02-29 00:00:00 Z
24
- dependencies:
25
- - !ruby/object:Gem::Dependency
17
+ date: 2012-12-20 00:00:00.000000000 Z
18
+ dependencies:
19
+ - !ruby/object:Gem::Dependency
26
20
  name: activerecord
27
21
  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
22
+ requirement: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ! '>='
25
+ - !ruby/object:Gem::Version
38
26
  version: 3.0.0
27
+ none: false
39
28
  type: :runtime
40
- version_requirements: *id001
41
- - !ruby/object:Gem::Dependency
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: 3.0.0
34
+ none: false
35
+ - !ruby/object:Gem::Dependency
42
36
  name: activemodel
43
37
  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
38
+ requirement: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ! '>='
41
+ - !ruby/object:Gem::Version
54
42
  version: 3.0.0
43
+ none: false
55
44
  type: :runtime
56
- version_requirements: *id002
57
- - !ruby/object:Gem::Dependency
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ! '>='
48
+ - !ruby/object:Gem::Version
49
+ version: 3.0.0
50
+ none: false
51
+ - !ruby/object:Gem::Dependency
58
52
  name: paper_trail
59
53
  prerelease: false
60
- requirement: &id003 !ruby/object:Gem::Requirement
61
- none: false
62
- requirements:
54
+ requirement: !ruby/object:Gem::Requirement
55
+ requirements:
63
56
  - - ~>
64
- - !ruby/object:Gem::Version
65
- hash: 7
66
- segments:
67
- - 2
68
- version: "2"
57
+ - !ruby/object:Gem::Version
58
+ version: '2'
59
+ none: false
69
60
  type: :runtime
70
- version_requirements: *id003
71
- - !ruby/object:Gem::Dependency
61
+ version_requirements: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ~>
64
+ - !ruby/object:Gem::Version
65
+ version: '2'
66
+ none: false
67
+ - !ruby/object:Gem::Dependency
72
68
  name: database_cleaner
73
69
  prerelease: false
74
- requirement: &id004 !ruby/object:Gem::Requirement
75
- none: false
76
- requirements:
77
- - - "="
78
- - !ruby/object:Gem::Version
79
- hash: 15
80
- segments:
81
- - 0
82
- - 5
83
- - 2
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - '='
73
+ - !ruby/object:Gem::Version
84
74
  version: 0.5.2
75
+ none: false
85
76
  type: :development
86
- version_requirements: *id004
87
- - !ruby/object:Gem::Dependency
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - '='
80
+ - !ruby/object:Gem::Version
81
+ version: 0.5.2
82
+ none: false
83
+ - !ruby/object:Gem::Dependency
88
84
  name: mocha
89
85
  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"
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ! '>='
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ none: false
99
92
  type: :development
100
- version_requirements: *id005
101
- - !ruby/object:Gem::Dependency
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ! '>='
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ none: false
99
+ - !ruby/object:Gem::Dependency
102
100
  name: pathname_local
103
101
  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"
102
+ requirement: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ! '>='
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ none: false
113
108
  type: :development
114
- version_requirements: *id006
115
- - !ruby/object:Gem::Dependency
109
+ version_requirements: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ! '>='
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ none: false
115
+ - !ruby/object:Gem::Dependency
116
116
  name: test_declarative
117
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"
118
+ requirement: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ! '>='
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ none: false
127
124
  type: :development
128
- version_requirements: *id007
129
- - !ruby/object:Gem::Dependency
125
+ version_requirements: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ! '>='
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ none: false
131
+ - !ruby/object:Gem::Dependency
130
132
  name: sqlite3
131
133
  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"
134
+ requirement: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ! '>='
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ none: false
141
140
  type: :development
142
- version_requirements: *id008
143
- description: "Rails I18n: de-facto standard library for ActiveRecord 3 model/data translation."
141
+ version_requirements: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ! '>='
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ none: false
147
+ description: Rails I18n de-facto standard library for ActiveRecord 3 model/data translation.
144
148
  email: nobody@globalize-rails.org
145
149
  executables: []
146
-
147
150
  extensions: []
148
-
149
151
  extra_rdoc_files: []
150
-
151
- files:
152
+ files:
152
153
  - lib/globalize/active_record/act_macro.rb
153
154
  - lib/globalize/active_record/adapter.rb
154
155
  - lib/globalize/active_record/attributes.rb
@@ -166,45 +167,35 @@ files:
166
167
  - lib/i18n/missing_translations_log_handler.rb
167
168
  - lib/i18n/missing_translations_raise_handler.rb
168
169
  - lib/patches/active_record/query_method.rb
170
+ - lib/patches/active_record/uniqueness_validator.rb
169
171
  - lib/patches/active_record/xml_attribute_serializer.rb
170
172
  - Gemfile
171
173
  - Gemfile.lock
172
174
  - LICENSE
173
175
  - Rakefile
174
- - README.textile
175
176
  homepage: http://github.com/svenfuchs/globalize3
176
177
  licenses: []
177
-
178
178
  post_install_message:
179
179
  rdoc_options: []
180
-
181
- require_paths:
180
+ require_paths:
182
181
  - lib
183
- required_ruby_version: !ruby/object:Gem::Requirement
182
+ required_ruby_version: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - ! '>='
185
+ - !ruby/object:Gem::Version
186
+ version: '0'
184
187
  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
188
+ required_rubygems_version: !ruby/object:Gem::Requirement
189
+ requirements:
190
+ - - ! '>='
191
+ - !ruby/object:Gem::Version
192
+ version: '0'
193
193
  none: false
194
- requirements:
195
- - - ">="
196
- - !ruby/object:Gem::Version
197
- hash: 3
198
- segments:
199
- - 0
200
- version: "0"
201
194
  requirements: []
202
-
203
- rubyforge_project: "[none]"
204
- rubygems_version: 1.8.17
195
+ rubyforge_project: ! '[none]'
196
+ rubygems_version: 1.8.24
205
197
  signing_key:
206
198
  specification_version: 3
207
- summary: "Rails I18n: de-facto standard library for ActiveRecord 3 model/data translation"
199
+ summary: Rails I18n de-facto standard library for ActiveRecord 3 model/data translation
208
200
  test_files: []
209
-
210
201
  has_rdoc:
data/README.textile DELETED
@@ -1,205 +0,0 @@
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
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
- <em>Note that if you're using rails ~> 3.1.0.rc migrations has only the <tt>change</tt> instance method</em>
43
-
44
- h3. Rails 3.0
45
-
46
- <pre><code>
47
- class CreatePosts < ActiveRecord::Migration
48
- def self.up
49
- create_table :posts do |t|
50
- t.timestamps
51
- end
52
- Post.create_translation_table! :title => :string, :text => :text
53
- end
54
- def self.down
55
- drop_table :posts
56
- Post.drop_translation_table!
57
- end
58
- end
59
- </code></pre>
60
-
61
- h3. Rails ~> 3.1.0.rc
62
-
63
- <em>Do not use the change method</em>
64
-
65
- <pre><code>
66
- class CreatePosts < ActiveRecord::Migration
67
- def up
68
- create_table :posts do |t|
69
- t.timestamps
70
- end
71
- Post.create_translation_table! :title => :string, :text => :text
72
- end
73
- def down
74
- drop_table :posts
75
- Post.drop_translation_table!
76
- end
77
- end
78
- </code></pre>
79
-
80
- Note that the ActiveRecord model @Post@ must already exist and have a @translates@ directive listing the translated fields.
81
-
82
- h2. Migrating existing data to and from the translated version
83
-
84
- As well as creating a translation table, you can also use @create_translation_table!@ to migrate across any
85
- existing data to the default locale. This can also operate in reverse to restore any translations from the default locale
86
- back to the model when you don't want to use a translation table anymore using @drop_translation_table!@
87
-
88
- This feature makes use of @untranslated_attributes@ which allows access to the model's attributes as they were before
89
- the translation was applied. Here's an example (which assumes you already have a model called @Post@ and its table exists):
90
-
91
- <pre><code>
92
- class TranslatePosts < ActiveRecord::Migration
93
- def self.up
94
- Post.create_translation_table!({
95
- :title => :string,
96
- :text => :text
97
- }, {
98
- :migrate_data => true
99
- })
100
- end
101
- def self.down
102
- Post.drop_translation_table! :migrate_data => true
103
- end
104
- end
105
- </code></pre>
106
-
107
- h2. Versioning with Globalize3
108
-
109
- Globalize3 nicely integrates with
110
- "paper_trail":https://github.com/airblade/paper_trail. To add versioning
111
- support to your model, you'll want to add the <code>:versioning => true</code>
112
- option to your call to <code>translates</code>. An example from our test suite:
113
-
114
- <pre><code>
115
- translates :title, :content, :published, :published_at, :versioning => true
116
- </code></pre>
117
-
118
- You will also need to have already generated the versions table that paper_trail
119
- expects. See the paper_trail README for more details.
120
-
121
- Also, please see the tests in test/globalize3/versioning_test.rb for some current gotchas.
122
-
123
- h2. I18n fallbacks for empty translations
124
-
125
- It is possible to enable fallbacks for empty translations. It will depend on the configuration setting you have set for I18n translations in your Rails config.
126
-
127
- You can enable them by adding the next line to @config/application.rb@ (or only @config/environments/production.rb@ if you only want them in production)
128
-
129
- <pre><code>config.i18n.fallbacks = true</code></pre>
130
-
131
- By default, globalize3 will only use fallbacks when your translation model does not exist or the translation value for the item you've requested is @nil@. However it is possible to also use fallbacks for @blank@ translations by adding @:fallbacks_for_empty_translations => true@ to the @translates@ method.
132
-
133
- <pre><code>
134
- class Post < ActiveRecord::Base
135
- translates :title, :name
136
- end
137
-
138
- puts post.translations.inspect
139
- # => [#<Post::Translation id: 1, post_id: 1, locale: "en", title: "Globalize3 rocks!", name: "Globalize3">, #<Post::Translation id: 2, post_id: 1, locale: "nl", title: '', name: nil>]
140
-
141
- I18n.locale = :en
142
- post.title # => 'Globalize3 rocks!'
143
- post.name # => 'Globalize3'
144
-
145
- I18n.locale = :nl
146
- post.title # => ''
147
- post.name # => 'Globalize3'
148
- </code></pre>
149
- <pre><code>
150
- class Post < ActiveRecord::Base
151
- translates :title, :name, :fallbacks_for_empty_translations => true
152
- end
153
-
154
- puts post.translations.inspect
155
- # => [#<Post::Translation id: 1, post_id: 1, locale: "en", title: "Globalize3 rocks!", name: "Globalize3">, #<Post::Translation id: 2, post_id: 1, locale: "nl", title: '', name: nil>]
156
-
157
- I18n.locale = :en
158
- post.title # => 'Globalize3 rocks!'
159
- post.name # => 'Globalize3'
160
-
161
- I18n.locale = :nl
162
- post.title # => 'Globalize3 rocks!'
163
- post.name # => 'Globalize3'
164
- </code></pre>
165
-
166
-
167
- h2. Scoping objects by those with translations
168
-
169
- To only return objects that have a translation for the given locale we can use the `with_translations` scope. This will only return records that have a translations for the passed in locale.
170
-
171
- <pre><code>
172
-
173
- Post.with_translations('en') # => [#<Post::Translation id: 1, post_id: 1, locale: "en", title: "Globalize3 rocks!", name: "Globalize3">, #<Post::Translation id: 2, post_id: 1, locale: "nl", title: '', name: nil>]
174
-
175
- Post.with_translations(I18n.locale) # => [#<Post::Translation id: 1, post_id: 1, locale: "en", title: "Globalize3 rocks!", name: "Globalize3">, #<Post::Translation id: 2, post_id: 1, locale: "nl", title: '', name: nil>]
176
-
177
- Post.with_translations('de') # => []
178
-
179
- </code></pre>
180
-
181
- h2. Changes since Globalize2
182
-
183
- * `translation_table_name` was renamed to `translations_table_name`
184
- * `available_locales` has been removed. please use `translated_locales`
185
-
186
- h2. Migration from Globalize for Rails (version 1)
187
-
188
- See this script by Tomasz Stachewicz: http://gist.github.com/120867
189
-
190
- h2. Alternative Solutions
191
-
192
- * "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
193
- * "TranslatableColumns":http://github.com/iain/translatable_columns - have multiple languages of the same attribute in a model (Iain Hecker)
194
- * "localized_record":http://github.com/glennpow/localized_record - allows records to have localized attributes without any modifications to the database (Glenn Powell)
195
- * "model_translations":http://github.com/janne/model_translations - Minimal implementation of Globalize2 style model translations (Jan Andersson)
196
-
197
- h2. Related solutions
198
-
199
- * "globalize2_versioning":http://github.com/joshmh/globalize2_versioning - acts_as_versioned style versioning for globalize2 (Joshua Harvey)
200
- * "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)
201
- * "globalize2 Demo App":http://github.com/svenfuchs/globalize2-demo - demo application for globalize2 (Sven Fuchs)</li>
202
- * "migrate_from_globalize1":http://gist.github.com/120867 - migrate model translations from Globalize1 to globalize2 (Tomasz Stachewicz)</li>
203
- * "easy_globalize2_accessors":http://github.com/astropanic/easy_globalize2_accessors - easily access (read and write) globalize2-translated fields (astropanic, Tomasz Stachewicz)</li>
204
- * "globalize2-easy-translate":http://github.com/bsamman/globalize2-easy-translate - adds methods to easily access or set translated attributes to your model (bsamman)</li>
205
- * "batch_translations":http://github.com/alvarezrilla/batch_translations - allow saving multiple globalize2 translations in the same request (Jose Alvarez Rilla)</li>