globalize-danibachar 5.2.0

Sign up to get free protection for your applications and to get access to all the features.
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,55 @@
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 = 'Globalize'
19
+ rdoc.options << '--line-numbers' << '--inline-source'
20
+ rdoc.rdoc_files.include('README')
21
+ rdoc.rdoc_files.include('lib/**/*.rb')
22
+ end
23
+
24
+ task :load_path do
25
+ %w(lib test).each do |path|
26
+ $LOAD_PATH.unshift(File.expand_path("../#{path}", __FILE__))
27
+ end
28
+ end
29
+
30
+ namespace :db do
31
+ desc 'Create the database'
32
+ task :create => :load_path do
33
+ require 'support/database'
34
+
35
+ Globalize::Test::Database.create!
36
+ end
37
+
38
+ desc "Drop the database"
39
+ task :drop => :load_path do
40
+ require 'support/database'
41
+
42
+ Globalize::Test::Database.drop!
43
+ end
44
+
45
+ desc "Set up the database schema"
46
+ task :migrate => :load_path do
47
+ require 'support/database'
48
+
49
+ Globalize::Test::Database.migrate!
50
+ # ActiveRecord::Schema.migrate :up
51
+ end
52
+
53
+ desc "Drop and recreate the database schema"
54
+ task :reset => [:drop, :create]
55
+ end
@@ -0,0 +1,98 @@
1
+ require 'request_store'
2
+ require 'active_record'
3
+ require 'patches/active_record/xml_attribute_serializer'
4
+ require 'patches/active_record/query_method'
5
+ require 'patches/active_record/relation'
6
+ require 'patches/active_record/serialization'
7
+ require 'patches/active_record/uniqueness_validator'
8
+ require 'patches/active_record/persistence'
9
+
10
+ module Globalize
11
+ autoload :ActiveRecord, 'globalize/active_record'
12
+ autoload :Interpolation, 'globalize/interpolation'
13
+
14
+ class << self
15
+ def locale
16
+ read_locale || I18n.locale
17
+ end
18
+
19
+ def locale=(locale)
20
+ set_locale(locale)
21
+ end
22
+
23
+ def with_locale(locale, &block)
24
+ previous_locale = read_locale
25
+ begin
26
+ set_locale(locale)
27
+ result = yield(locale)
28
+ ensure
29
+ set_locale(previous_locale)
30
+ end
31
+ result
32
+ end
33
+
34
+ def with_locales(*locales, &block)
35
+ locales.flatten.map do |locale|
36
+ with_locale(locale, &block)
37
+ end
38
+ end
39
+
40
+ def fallbacks=(locales)
41
+ set_fallbacks(locales)
42
+ end
43
+
44
+ def i18n_fallbacks?
45
+ I18n.respond_to?(:fallbacks)
46
+ end
47
+
48
+ def fallbacks(for_locale = self.locale)
49
+ read_fallbacks[for_locale] || default_fallbacks(for_locale)
50
+ end
51
+
52
+ def default_fallbacks(for_locale = self.locale)
53
+ i18n_fallbacks? ? I18n.fallbacks[for_locale] : [for_locale.to_sym]
54
+ end
55
+
56
+ # Thread-safe global storage
57
+ def storage
58
+ RequestStore.store
59
+ end
60
+
61
+ def rails_5?
62
+ ::ActiveRecord.version >= Gem::Version.new('5.1.0')
63
+ end
64
+
65
+ def rails_52?
66
+ ::ActiveRecord.version >= Gem::Version.new('5.2.0')
67
+ end
68
+
69
+ protected
70
+
71
+ def read_locale
72
+ storage[:globalize_locale]
73
+ end
74
+
75
+ def set_locale(locale)
76
+ storage[:globalize_locale] = locale.try(:to_sym)
77
+ end
78
+
79
+ def read_fallbacks
80
+ storage[:globalize_fallbacks] || HashWithIndifferentAccess.new
81
+ end
82
+
83
+ def set_fallbacks(locales)
84
+ fallback_hash = HashWithIndifferentAccess.new
85
+
86
+ locales.each do |key, value|
87
+ fallback_hash[key] = value.presence || [key]
88
+ end if locales.present?
89
+
90
+ storage[:globalize_fallbacks] = fallback_hash
91
+ end
92
+ end
93
+ end
94
+
95
+ ActiveRecord::Base.class_attribute :globalize_serialized_attributes, instance_writer: false
96
+ ActiveRecord::Base.globalize_serialized_attributes = {}
97
+
98
+ ActiveRecord::Base.extend(Globalize::ActiveRecord::ActMacro)
@@ -0,0 +1,14 @@
1
+ module Globalize
2
+ module ActiveRecord
3
+ autoload :ActMacro, 'globalize/active_record/act_macro'
4
+ autoload :Adapter, 'globalize/active_record/adapter'
5
+ autoload :AdapterDirty, 'globalize/active_record/adapter_dirty'
6
+ autoload :Attributes, 'globalize/active_record/attributes'
7
+ autoload :ClassMethods, 'globalize/active_record/class_methods'
8
+ autoload :Exceptions, 'globalize/active_record/exceptions'
9
+ autoload :InstanceMethods, 'globalize/active_record/instance_methods'
10
+ autoload :Migration, 'globalize/active_record/migration'
11
+ autoload :Translation, 'globalize/active_record/translation'
12
+ autoload :TranslatedAttributesQuery, 'globalize/active_record/translated_attributes_query'
13
+ end
14
+ end
@@ -0,0 +1,96 @@
1
+ module Globalize
2
+ module ActiveRecord
3
+ module ActMacro
4
+ def translates(*attr_names)
5
+ options = attr_names.extract_options!
6
+ # Bypass setup_translates! if the initial bootstrapping is done already.
7
+ setup_translates!(options) unless translates?
8
+
9
+
10
+ # Add any extra translatable attributes.
11
+ attr_names = attr_names.map(&:to_sym)
12
+ attr_names -= translated_attribute_names if defined?(translated_attribute_names)
13
+
14
+ allow_translation_of_attributes(attr_names) if attr_names.present?
15
+ end
16
+
17
+ def class_name
18
+ @class_name ||= begin
19
+ class_name = table_name[table_name_prefix.length..-(table_name_suffix.length + 1)].downcase.camelize
20
+ pluralize_table_names ? class_name.singularize : class_name
21
+ end
22
+ end
23
+
24
+ def translates?
25
+ included_modules.include?(InstanceMethods)
26
+ end
27
+
28
+ protected
29
+
30
+ def allow_translation_of_attributes(attr_names)
31
+ attr_names.each do |attr_name|
32
+ # Detect and apply serialization.
33
+ enable_serializable_attribute(attr_name)
34
+
35
+ # Create accessors for the attribute.
36
+ define_translated_attr_accessor(attr_name)
37
+ define_translations_accessor(attr_name)
38
+
39
+ # Add attribute to the list.
40
+ self.translated_attribute_names << attr_name
41
+ end
42
+ if ::ActiveRecord::VERSION::STRING > "5.0" && connected? && table_exists? && translation_class.table_exists?
43
+ self.ignored_columns += translated_attribute_names.map(&:to_s)
44
+ reset_column_information
45
+ end
46
+ end
47
+
48
+ def apply_globalize_options(options)
49
+ options[:table_name] ||= "#{table_name.singularize}_translations"
50
+ options[:foreign_key] ||= class_name.foreign_key
51
+
52
+ class_attribute :translated_attribute_names, :translation_options, :fallbacks_for_empty_translations
53
+ self.translated_attribute_names = []
54
+ self.translation_options = options
55
+ self.fallbacks_for_empty_translations = options[:fallbacks_for_empty_translations]
56
+ end
57
+
58
+ def enable_serializable_attribute(attr_name)
59
+ serializer = self.globalize_serialized_attributes[attr_name]
60
+ if serializer.present?
61
+ if defined?(::ActiveRecord::Coders::YAMLColumn) &&
62
+ serializer.is_a?(::ActiveRecord::Coders::YAMLColumn)
63
+ serializer = serializer.object_class
64
+ end
65
+
66
+ translation_class.send :serialize, attr_name, serializer
67
+ end
68
+ end
69
+
70
+ def setup_translates!(options)
71
+ apply_globalize_options(options)
72
+
73
+ include InstanceMethods
74
+ extend ClassMethods, Migration
75
+
76
+ translation_class.table_name = options[:table_name]
77
+
78
+ has_many :translations, :class_name => translation_class.name,
79
+ :foreign_key => options[:foreign_key],
80
+ :dependent => :destroy,
81
+ :extend => HasManyExtensions,
82
+ :autosave => false,
83
+ :inverse_of => :globalized_model
84
+
85
+ after_create :save_translations!
86
+ after_update :save_translations!
87
+ end
88
+ end
89
+
90
+ module HasManyExtensions
91
+ def find_or_initialize_by_locale(locale)
92
+ with_locale(locale.to_s).first || build(:locale => locale.to_s)
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,108 @@
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
7
+ private :record=, :stash=
8
+
9
+ delegate :translation_class, :to => :'record.class'
10
+
11
+ def initialize(record)
12
+ @record = record
13
+ @stash = Attributes.new
14
+ end
15
+
16
+ def fetch_stash(locale, name)
17
+ stash.read(locale, name)
18
+ end
19
+
20
+ delegate :contains?, :to => :stash, :prefix => :stash
21
+ delegate :write, :to => :stash
22
+
23
+ def fetch(locale, name)
24
+ record.globalize_fallbacks(locale).each do |fallback|
25
+ value = stash.contains?(fallback, name) ? fetch_stash(fallback, name) : fetch_attribute(fallback, name)
26
+
27
+ unless fallbacks_for?(value)
28
+ set_metadata(value, :locale => fallback, :requested_locale => locale)
29
+ return value
30
+ end
31
+ end
32
+
33
+ return nil
34
+ end
35
+
36
+ def save_translations!
37
+ stash.each do |locale, attrs|
38
+ next if attrs.empty?
39
+
40
+ translation = record.translations_by_locale[locale] ||
41
+ record.translations.build(locale: locale.to_s)
42
+ attrs.each do |name, value|
43
+ value = value.val if value.is_a?(Arel::Nodes::Casted)
44
+ translation[name] = value
45
+ end
46
+
47
+ ensure_foreign_key_for(translation)
48
+ translation.save!
49
+ end
50
+
51
+ reset
52
+ end
53
+
54
+ def reset
55
+ stash.clear
56
+ end
57
+
58
+ protected
59
+
60
+ # Sometimes the translation is initialised before a foreign key can be set.
61
+ def ensure_foreign_key_for(translation)
62
+ # AR >= 4.1 reflections renamed to _reflections
63
+ translation[translation.class.reflections.stringify_keys["globalized_model"].foreign_key] = record.id
64
+ end
65
+
66
+ def type_cast(name, value)
67
+ return value.presence unless column = column_for_attribute(name)
68
+
69
+ column.type_cast value
70
+ end
71
+
72
+ def column_for_attribute(name)
73
+ translation_class.columns_hash[name.to_s]
74
+ end
75
+
76
+ def unserializable_attribute?(name, column)
77
+ column.text? && translation_class.serialized_attributes[name.to_s]
78
+ end
79
+
80
+ def fetch_attribute(locale, name)
81
+ translation = record.translation_for(locale, false)
82
+ if translation
83
+ translation.send(name)
84
+ else
85
+ record.class.translation_class.new.send(name)
86
+ end
87
+ end
88
+
89
+ def set_metadata(object, metadata)
90
+ object.translation_metadata.merge!(metadata) if object.respond_to?(:translation_metadata)
91
+ object
92
+ end
93
+
94
+ def translation_metadata_accessor(object)
95
+ return if obj.respond_to?(:translation_metadata)
96
+ class << object; attr_accessor :translation_metadata end
97
+ object.translation_metadata ||= {}
98
+ end
99
+
100
+ def fallbacks_for?(object)
101
+ object.nil? || (fallbacks_for_empty_translations? && object.blank?)
102
+ end
103
+
104
+ delegate :fallbacks_for_empty_translations?, :to => :record, :prefix => false
105
+ prepend AdapterDirty
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,56 @@
1
+ module Globalize
2
+ module ActiveRecord
3
+ module AdapterDirty
4
+ def write locale, name, value
5
+ # Dirty tracking, paraphrased from
6
+ # ActiveRecord::AttributeMethods::Dirty#write_attribute.
7
+ name = name.to_s
8
+ store_old_value name, locale
9
+ old_values = dirty[name]
10
+ old_value = old_values[locale]
11
+ is_changed = record.send :attribute_changed?, name
12
+ if is_changed && value == old_value
13
+ # If there's already a change, delete it if this undoes the change.
14
+ old_values.delete locale
15
+ if old_values.empty?
16
+ _reset_attribute name
17
+ end
18
+ elsif !is_changed
19
+ # If there's not a change yet, record it.
20
+ record.send(:attribute_will_change!, name) if old_value != value
21
+ end
22
+
23
+ super locale, name, value
24
+ end
25
+
26
+ attr_writer :dirty
27
+ def dirty
28
+ @dirty ||= {}
29
+ end
30
+
31
+ def store_old_value name, locale
32
+ dirty[name] ||= {}
33
+ unless dirty[name].key? locale
34
+ old = fetch(locale, name)
35
+ old = old.dup if old.duplicable?
36
+ dirty[name][locale] = old
37
+ end
38
+ end
39
+
40
+ def clear_dirty
41
+ self.dirty = {}
42
+ end
43
+
44
+ def _reset_attribute name
45
+ record.send("#{name}=", record.changed_attributes[name])
46
+ record.send(:clear_attribute_changes, [name])
47
+ end
48
+
49
+ def reset
50
+ clear_dirty
51
+ super
52
+ end
53
+
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,26 @@
1
+ # Helper class for storing values per locale. Used by Globalize::Adapter
2
+ # to stash and cache attribute values.
3
+
4
+ module Globalize
5
+ module ActiveRecord
6
+ class Attributes < Hash # 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
+ self[locale][name.to_s] = value
23
+ end
24
+ end
25
+ end
26
+ end