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,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