globalize-danibachar 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. checksums.yaml +7 -0
  2. data/Appraisals +33 -0
  3. data/CHANGELOG.md +111 -0
  4. data/CONTRIBUTING.md +52 -0
  5. data/Gemfile +3 -0
  6. data/LICENSE +22 -0
  7. data/README.md +437 -0
  8. data/Rakefile +55 -0
  9. data/lib/globalize.rb +98 -0
  10. data/lib/globalize/active_record.rb +14 -0
  11. data/lib/globalize/active_record/act_macro.rb +96 -0
  12. data/lib/globalize/active_record/adapter.rb +108 -0
  13. data/lib/globalize/active_record/adapter_dirty.rb +56 -0
  14. data/lib/globalize/active_record/attributes.rb +26 -0
  15. data/lib/globalize/active_record/class_methods.rb +129 -0
  16. data/lib/globalize/active_record/exceptions.rb +13 -0
  17. data/lib/globalize/active_record/instance_methods.rb +246 -0
  18. data/lib/globalize/active_record/migration.rb +215 -0
  19. data/lib/globalize/active_record/translated_attributes_query.rb +181 -0
  20. data/lib/globalize/active_record/translation.rb +45 -0
  21. data/lib/globalize/interpolation.rb +28 -0
  22. data/lib/globalize/version.rb +3 -0
  23. data/lib/i18n/missing_translations_log_handler.rb +41 -0
  24. data/lib/i18n/missing_translations_raise_handler.rb +25 -0
  25. data/lib/patches/active_record/persistence.rb +17 -0
  26. data/lib/patches/active_record/query_method.rb +3 -0
  27. data/lib/patches/active_record/rails4/query_method.rb +35 -0
  28. data/lib/patches/active_record/rails4/serialization.rb +22 -0
  29. data/lib/patches/active_record/rails4/uniqueness_validator.rb +42 -0
  30. data/lib/patches/active_record/rails5/uniqueness_validator.rb +47 -0
  31. data/lib/patches/active_record/rails5_1/serialization.rb +22 -0
  32. data/lib/patches/active_record/rails5_1/uniqueness_validator.rb +45 -0
  33. data/lib/patches/active_record/relation.rb +12 -0
  34. data/lib/patches/active_record/serialization.rb +5 -0
  35. data/lib/patches/active_record/uniqueness_validator.rb +7 -0
  36. data/lib/patches/active_record/xml_attribute_serializer.rb +23 -0
  37. metadata +264 -0
@@ -0,0 +1,181 @@
1
+ module Globalize
2
+ module ActiveRecord
3
+ module TranslatedAttributesQuery
4
+ class WhereChain < ::ActiveRecord::QueryMethods::WhereChain
5
+ def not(opts, *rest)
6
+ if parsed = @scope.clone.parse_translated_conditions(opts)
7
+ @scope.join_translations.where.not(parsed, *rest)
8
+ else
9
+ super
10
+ end
11
+ end
12
+ end
13
+
14
+ def where(opts = :chain, *rest)
15
+ if opts == :chain
16
+ WhereChain.new(spawn)
17
+ elsif parsed = parse_translated_conditions(opts)
18
+ join_translations(super(parsed, *rest))
19
+ else
20
+ super
21
+ end
22
+ end
23
+
24
+ def having(opts, *rest)
25
+ if parsed = parse_translated_conditions(opts)
26
+ join_translations(super(parsed, *rest))
27
+ else
28
+ super
29
+ end
30
+ end
31
+
32
+ def order(opts, *rest)
33
+ if respond_to?(:translated_attribute_names) && parsed = parse_translated_order(opts)
34
+ join_translations super(parsed)
35
+ else
36
+ super
37
+ end
38
+ end
39
+
40
+ def reorder(opts, *rest)
41
+ if respond_to?(:translated_attribute_names) && parsed = parse_translated_order(opts)
42
+ join_translations super(parsed)
43
+ else
44
+ super
45
+ end
46
+ end
47
+
48
+ def group(*columns)
49
+ if respond_to?(:translated_attribute_names) && parsed = parse_translated_columns(columns)
50
+ join_translations super(parsed)
51
+ else
52
+ super
53
+ end
54
+ end
55
+
56
+ def select(*columns)
57
+ if respond_to?(:translated_attribute_names) && parsed = parse_translated_columns(columns)
58
+ join_translations super(parsed)
59
+ else
60
+ super
61
+ end
62
+ end
63
+
64
+ def exists?(conditions = :none)
65
+ if parsed = parse_translated_conditions(conditions)
66
+ with_translations_in_fallbacks.exists?(parsed)
67
+ else
68
+ super
69
+ end
70
+ end
71
+
72
+ def calculate(*args)
73
+ column_name = args[1]
74
+ if respond_to?(:translated_attribute_names) && translated_column?(column_name)
75
+ args[1] = translated_column_name(column_name)
76
+ join_translations.calculate(*args)
77
+ else
78
+ super
79
+ end
80
+ end
81
+
82
+ def pluck(*column_names)
83
+ if respond_to?(:translated_attribute_names) && parsed = parse_translated_columns(column_names)
84
+ join_translations.pluck(*parsed)
85
+ else
86
+ super
87
+ end
88
+ end
89
+
90
+ def with_translations_in_fallbacks
91
+ with_translations(Globalize.fallbacks)
92
+ end
93
+
94
+ def parse_translated_conditions(opts)
95
+ if opts.is_a?(Hash) && respond_to?(:translated_attribute_names) && (keys = opts.symbolize_keys.keys & translated_attribute_names).present?
96
+ opts = opts.dup
97
+ keys.each { |key| opts[translated_column_name(key)] = opts.delete(key) || opts.delete(key.to_s) }
98
+ opts
99
+ end
100
+ end
101
+
102
+ if ::ActiveRecord::VERSION::STRING < "5.0.0"
103
+ def where_values_hash(*args)
104
+ return super unless respond_to?(:translations_table_name)
105
+ equalities = respond_to?(:with_default_scope) ? with_default_scope.where_values : where_values
106
+ equalities = equalities.grep(Arel::Nodes::Equality).find_all { |node|
107
+ node.left.relation.name == translations_table_name
108
+ }
109
+
110
+ binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }]
111
+
112
+ super.merge(Hash[equalities.map { |where|
113
+ name = where.left.name
114
+ [name, binds.fetch(name.to_s) { right = where.right; right.is_a?(Arel::Nodes::Casted) ? right.val : right }]
115
+ }])
116
+ end
117
+ end
118
+
119
+ def join_translations(relation = self)
120
+ if relation.joins_values.include?(:translations)
121
+ relation
122
+ else
123
+ relation.with_translations_in_fallbacks
124
+ end
125
+ end
126
+
127
+ private
128
+
129
+ def arel_translated_order_node(column, direction)
130
+ unless translated_column?(column)
131
+ return self.arel_table[column].send(direction)
132
+ end
133
+
134
+ full_column = translated_column_name(column)
135
+
136
+ # Inject `full_column` to the select values to avoid
137
+ # PG::InvalidColumnReference errors with distinct queries on Postgres
138
+ if select_values.empty?
139
+ self.select_values = [self.arel_table[Arel.star], full_column]
140
+ else
141
+ self.select_values << full_column
142
+ end
143
+
144
+ translation_class.arel_table[column].send(direction)
145
+ end
146
+
147
+ def parse_translated_order(opts)
148
+ case opts
149
+ when Hash
150
+ # Do not process nothing unless there is at least a translated column
151
+ # so that the `order` statement will be processed by the original
152
+ # ActiveRecord method
153
+ return nil unless opts.find { |col, dir| translated_column?(col) }
154
+
155
+ # Build order arel nodes for translateds and untranslateds statements
156
+ ordering = opts.map do |column, direction|
157
+ arel_translated_order_node(column, direction)
158
+ end
159
+
160
+ order(ordering).order_values
161
+ when Symbol
162
+ parse_translated_order({ opts => :asc })
163
+ when Array
164
+ parse_translated_order(Hash[opts.collect { |opt| [opt, :asc] } ])
165
+ else # failsafe returns nothing
166
+ nil
167
+ end
168
+ end
169
+
170
+ def parse_translated_columns(columns)
171
+ if columns.is_a?(Array) && (columns.flatten & translated_attribute_names).present?
172
+ columns.flatten.map { |column| translated_column?(column) ? translated_column_name(column) : column }
173
+ end
174
+ end
175
+
176
+ def translated_column?(column)
177
+ translated_attribute_names.include?(column)
178
+ end
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,45 @@
1
+ module Globalize
2
+ module ActiveRecord
3
+ class Translation < ::ActiveRecord::Base
4
+
5
+ validates :locale, :presence => true
6
+
7
+ class << self
8
+ # Sometimes ActiveRecord queries .table_exists? before the table name
9
+ # has even been set which results in catastrophic failure.
10
+ def table_exists?
11
+ table_name.present? && super
12
+ end
13
+
14
+ def with_locales(*locales)
15
+ # Avoid using "IN" with SQL queries when only using one locale.
16
+ locales = locales.flatten.map(&:to_s)
17
+ locales = locales.first if locales.one?
18
+ where :locale => locales
19
+ end
20
+ alias with_locale with_locales
21
+
22
+ def translated_locales
23
+ select('DISTINCT locale').order(:locale).map(&:locale)
24
+ end
25
+ end
26
+
27
+ def locale
28
+ _locale = read_attribute :locale
29
+ _locale.present? ? _locale.to_sym : _locale
30
+ end
31
+
32
+ def locale=(locale)
33
+ write_attribute :locale, locale.to_s
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ # Setting this will force polymorphic associations to subclassed objects
40
+ # to use their table_name rather than the parent object's table name,
41
+ # which will allow you to get their models back in a more appropriate
42
+ # format.
43
+ #
44
+ # See http://www.ruby-forum.com/topic/159894 for details.
45
+ Globalize::ActiveRecord::Translation.abstract_class = true
@@ -0,0 +1,28 @@
1
+ module Globalize
2
+ module Interpolation
3
+ def interpolate(name, model, args)
4
+ translation = model.read_attribute(name, {:locale => locale_from(args)})
5
+ try_interpolation translation, interpolation_args_from(args)
6
+ end
7
+
8
+ private
9
+
10
+ def interpolation_args_from(args)
11
+ args.detect {|a| a.is_a? Hash }
12
+ end
13
+
14
+ def locale_from(args)
15
+ args.detect {|a| !a.is_a? Hash }
16
+ end
17
+
18
+ def try_interpolation(translation,args)
19
+ if args
20
+ I18n.interpolate(translation,args)
21
+ else
22
+ translation
23
+ end
24
+ end
25
+
26
+ extend self
27
+ end
28
+ end
@@ -0,0 +1,3 @@
1
+ module Globalize
2
+ Version = '5.2.0'
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,17 @@
1
+ module Globalize
2
+ module Persistence
3
+ # Updates the associated record with values matching those of the instance attributes.
4
+ # Returns the number of affected rows.
5
+ def _update_record(attribute_names = self.attribute_names)
6
+ attribute_names_without_translated = attribute_names.select{ |k| not respond_to?('translated?') or not translated?(k) }
7
+ super(attribute_names_without_translated)
8
+ end
9
+
10
+ def _create_record(attribute_names = self.attribute_names)
11
+ attribute_names_without_translated = attribute_names.select{ |k| not respond_to?('translated?') or not translated?(k) }
12
+ super(attribute_names_without_translated)
13
+ end
14
+ end
15
+ end
16
+
17
+ ActiveRecord::Persistence.send(:prepend, Globalize::Persistence)
@@ -0,0 +1,3 @@
1
+ if ::ActiveRecord::VERSION::STRING < "5.0.0"
2
+ require_relative 'rails4/query_method'
3
+ 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,22 @@
1
+ module Globalize
2
+ module AttributeMethods
3
+ module Serialization
4
+ def serialize(attr_name, class_name_or_coder = Object)
5
+ super(attr_name, class_name_or_coder)
6
+
7
+ coder = if class_name_or_coder == ::JSON
8
+ ::ActiveRecord::Coders::JSON
9
+ elsif [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
10
+ class_name_or_coder
11
+ else
12
+ ::ActiveRecord::Coders::YAMLColumn.new(class_name_or_coder)
13
+ end
14
+
15
+ self.globalize_serialized_attributes = globalize_serialized_attributes.dup
16
+ self.globalize_serialized_attributes[attr_name] = coder
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ ActiveRecord::AttributeMethods::Serialization::ClassMethods.send(:prepend, Globalize::AttributeMethods::Serialization)
@@ -0,0 +1,42 @@
1
+ require 'active_record/validations/uniqueness.rb'
2
+
3
+ module Globalize
4
+ module UniquenessValidatorOverride
5
+ def validate_each(record, attribute, value)
6
+ klass = record.class
7
+ if klass.translates? && klass.translated?(attribute)
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
+ translated_scopes = Array(options[:scope]) & klass.translated_attribute_names
15
+ untranslated_scopes = Array(options[:scope]) - translated_scopes
16
+
17
+ untranslated_scopes.each do |scope_item|
18
+ scope_value = record.send(scope_item)
19
+ reflection = klass.reflect_on_association(scope_item)
20
+ if reflection
21
+ scope_value = record.send(reflection.foreign_key)
22
+ scope_item = reflection.foreign_key
23
+ end
24
+ relation = relation.and(find_finder_class_for(record).arel_table[scope_item].eq(scope_value))
25
+ end
26
+
27
+ translated_scopes.each do |scope_item|
28
+ scope_value = record.send(scope_item)
29
+ relation = relation.and(table[scope_item].eq(scope_value))
30
+ end
31
+
32
+ if klass.unscoped.with_translations.where(relation).exists?
33
+ record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value))
34
+ end
35
+ else
36
+ super
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ ActiveRecord::Validations::UniquenessValidator.send :prepend, Globalize::UniquenessValidatorOverride