traco 3.1.2 → 3.1.3

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.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- YTc4OWNjMzkxOTcwNzE3NmE4NDM4MDQ4MWM5MGIyMzJkNjRlZWY0OQ==
4
+ NGU5NTFmYTU4ZDQ5ZDA5ZWUyMzIyYjNiZDkxOGZiYzkyZDAwOGU1ZA==
5
5
  data.tar.gz: !binary |-
6
- NTYzYTFhZmFjYzk1Yjk1ZTM4NGJhN2QyOGIwNGM1ZTYzZDg0OGI2NQ==
6
+ OTI3ODAxOTgwNTE1OGQyZmFjYjUxMTJlZjQ1MmI0YjBkNTY0MjU5OA==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- MWRjNTM2ZjcyYTZhNTliYmUzNjg4OWJlZTBiMTdjMTA3NzM1ODBkMTJlOGVi
10
- NjQzZmJhMzhkMTA5YjdkYmQzY2NiODk3YzM2YzMzZWNiMGMzNGYzYWEyYWZj
11
- Nzg3NzE4OGE4NDE5OWE2YjQyNzI1OTAyN2I2MWMwZTk5ZmNiYmY=
9
+ NGFlOGM1M2QyMjVjZmQzNjQ1MjRkZDYxNDE5ZWQzOGQ2MjFkODFlNjU3ZjI2
10
+ Y2UxNTJiYTZmYTRiM2FiMDFkYzYxMDRkMGNkMjA5NjVlNzIzYWNmMjJiZWFm
11
+ MzU4Y2NiZWNlODk1MWMxODEyMTg4NTRmMDQ1ZjBkZmRlNjlkNDg=
12
12
  data.tar.gz: !binary |-
13
- YzY1OGI0MTc3OTNhOGU2NzJhYWQxYWU3YzI2N2IwZWM0MGQxNDY4Yjc1MGE3
14
- NDY3ZTBiNTc5MDNiNzNlNzEyNTEwNTNhNjc2MTEwOWVmZDgyNDkzNmVkNjdi
15
- NDhmMDBkMmFlMjNiNWI4NTJmMTRlMTM5N2NmYzliMTU1ZjExODc=
13
+ NWQ2MmFlZDZhY2FhZTllMTZkMTUzMWI2ZWRkZDEwNzBkMmY5M2Y2YmM0NjIz
14
+ MGIwZmY3MmY3YTgxYTY0NzVmODM4M2Q1Mjk0ZTI5MDU1MjYxOTM0N2U3Njll
15
+ NmQ0MWY4NzBmNjcyMjk2M2M2ZGQwNTdmMjBlNWU5ZmExMjU2MDA=
data/Gemfile CHANGED
@@ -1,4 +1,8 @@
1
- source "http://rubygems.org"
1
+ source "https://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in the .gemspec.
4
4
  gemspec
5
+
6
+ group :benchmark do
7
+ gem "benchmark-ips"
8
+ end
data/README.md CHANGED
@@ -134,6 +134,11 @@ to install it.
134
134
  rake
135
135
 
136
136
 
137
+ ## Benchmark
138
+
139
+ ruby benchmarks/overhead.rb
140
+
141
+
137
142
  <!-- Keeping this a hidden brain dump for now.
138
143
 
139
144
  ## TODO
@@ -150,6 +155,17 @@ Possible improvements to make:
150
155
 
151
156
  -->
152
157
 
158
+ ## Contributors
159
+
160
+ * [Henrik Nyh](http://henrik.nyh.se)
161
+ * Andrii Malyshko
162
+ * Tobias Bohwalli
163
+ * Mario Alberto Chavez
164
+ * Philip Arndt
165
+ * Leung Ho Kuen
166
+ * Fernando Morgenstern
167
+ * Tomáš Horáček
168
+ * Joakim Kolsjö
153
169
 
154
170
  ## Credits and license
155
171
 
@@ -0,0 +1,41 @@
1
+ # This benchmark tests how fast a Traco-wrapped attribute is
2
+ # compared to the plain Active Record attribute.
3
+
4
+ require "bundler/setup"
5
+ require "benchmark/ips"
6
+ require "active_record"
7
+ require "traco"
8
+
9
+ ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
10
+
11
+ I18n.enforce_available_locales = false
12
+ I18n.available_locales = [ :en, :de, :sv ]
13
+ I18n.default_locale = :en
14
+ I18n.locale = :sv
15
+
16
+ COLUMNS = %w(title body long_title seo_title)
17
+
18
+ silence_stream(STDOUT) do
19
+ ActiveRecord::Schema.define(version: 0) do
20
+ create_table :posts, force: true do |t|
21
+ I18n.available_locales.each do |locale|
22
+ COLUMNS.each do |column|
23
+ t.string "#{column}_#{locale}"
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ class Post < ActiveRecord::Base
31
+ translates *COLUMNS
32
+ end
33
+
34
+ post = Post.new(title_en: "hello", title_sv: "Hej")
35
+
36
+ Benchmark.ips do |x|
37
+ x.report("activerecord") { post.title_sv }
38
+ x.report("traco") { post.title }
39
+
40
+ x.compare!
41
+ end
data/lib/traco.rb CHANGED
@@ -1,11 +1,57 @@
1
1
  require "traco/version"
2
- require "traco/translates"
2
+ require "traco/attributes"
3
3
  require "traco/class_methods"
4
- require "traco/localized_reader"
4
+ require "traco/locale_fallbacks"
5
+ require "traco/translates"
5
6
 
6
7
  module Traco
7
- # E.g. :sv -> :sv, :"pt-BR" -> :pt_br
8
- def self.locale_suffix(locale)
9
- locale.to_s.downcase.sub("-", "_").to_sym
8
+ COLUMN_RE =
9
+ /\A
10
+ (?<attribute>\w+?) # title
11
+ _ # _
12
+ (?<primary>[a-z]{2}) # pt
13
+ (
14
+ _ # _
15
+ (?<extended>[a-z]{2}) # br
16
+ )?
17
+ \z/x
18
+
19
+ # @example
20
+ # Traco.column("title", :sv) # => :title_sv
21
+ # Traco.column("title", :"pt-BR") # => :title_pt_br
22
+ def self.column(attribute, locale)
23
+ normalized_locale = locale.to_s.downcase.sub("-", "_")
24
+ "#{attribute}_#{normalized_locale}".to_sym
25
+ end
26
+
27
+ # @example
28
+ # Traco.split_localized_column("title_sv") # => [:title, :sv]
29
+ # Traco.split_localized_column("title_pt_br") # => [:title, :"pt-BR"]
30
+ # Traco.split_localized_column("unlocalized") # => nil
31
+ def self.split_localized_column(column)
32
+ match_data = column.to_s.match(COLUMN_RE)
33
+ return unless match_data
34
+
35
+ attribute = match_data[:attribute]
36
+ primary_locale = match_data[:primary]
37
+ extended_locale = match_data[:extended]
38
+
39
+ if extended_locale
40
+ locale = "#{primary_locale}-#{extended_locale.upcase}"
41
+ else
42
+ locale = primary_locale
43
+ end
44
+
45
+ [ attribute.to_sym, locale.to_sym ]
46
+ end
47
+
48
+ def self.locale_name(locale)
49
+ default = locale.to_s.upcase.sub("_", "-")
50
+ I18n.t(locale, scope: :"i18n.languages", default: default)
51
+ end
52
+
53
+ def self.locale_with_fallbacks(locale, fallback_option)
54
+ locale_fallbacks_resolver = LocaleFallbacks.new(fallback_option)
55
+ locale_fallbacks_resolver[locale]
10
56
  end
11
57
  end
@@ -0,0 +1,71 @@
1
+ module Traco
2
+ class Attributes < Module
3
+ def initialize(*attributes)
4
+ @options = attributes.extract_options!
5
+ @attributes = attributes.map(&:to_sym)
6
+
7
+ attributes.each do |attribute|
8
+ define_reader(attribute)
9
+ define_writer(attribute)
10
+ end
11
+ end
12
+
13
+ def included(base)
14
+ self.class.ensure_translatable_attributes(base)
15
+ base.translatable_attributes |= @attributes
16
+
17
+ self.class.warm_up_activerecord_methods(base)
18
+
19
+ base.extend ClassMethods
20
+ end
21
+
22
+ private
23
+
24
+ def define_reader(attribute)
25
+ class_eval <<-EOM, __FILE__, __LINE__ + 1
26
+ def #{attribute}(options = {})
27
+ default_fallback = #{@options.fetch(:fallback, LocaleFallbacks::DEFAULT_FALLBACK).inspect}
28
+ fallback = options.fetch(:fallback, default_fallback)
29
+
30
+ columns_to_try = self.class._locale_columns_for_attribute(:#{attribute}, fallback)
31
+ columns_to_try.each do |column|
32
+ value = send(column)
33
+ return value if value.present?
34
+ end
35
+
36
+ nil
37
+ end
38
+ EOM
39
+ end
40
+
41
+ def define_writer(attribute)
42
+ class_eval <<-EOM, __FILE__, __LINE__ + 1
43
+ def #{attribute}=(value)
44
+ column = Traco.column(:#{attribute}, I18n.locale).to_s + "="
45
+ send(column, value)
46
+ end
47
+ EOM
48
+ end
49
+
50
+ # Only called once per class or inheritance chain (e.g. once
51
+ # for the superclass, not at all for subclasses). The separation
52
+ # is important if we don't want to overwrite values if running
53
+ # multiple times in the same class or in different classes of
54
+ # an inheritance chain.
55
+ def self.ensure_translatable_attributes(base)
56
+ return if base.respond_to?(:translatable_attributes)
57
+
58
+ base.class_attribute :translatable_attributes
59
+ base.translatable_attributes = []
60
+ end
61
+
62
+ # Force AR to create methods `title_en` etc, which are lazily evaluated,
63
+ # otherwise we can't safely check `.instance_methods`.
64
+ def self.warm_up_activerecord_methods(base)
65
+ random_attribute = base.translatable_attributes.first
66
+
67
+ instance = base.new
68
+ instance.send(Traco.column(random_attribute, I18n.default_locale))
69
+ end
70
+ end
71
+ end
@@ -1,61 +1,70 @@
1
1
  module Traco
2
2
  module ClassMethods
3
- LOCALE_RE = /[a-zA-Z]{2}(?:_[a-zA-Z]{2})?/
4
-
5
3
  def locales_for_attribute(attribute)
6
- re = /\A#{attribute}_(#{LOCALE_RE})\z/
4
+ traco_cache(attribute, LocaleFallbacks::ANY_FALLBACK).keys
5
+ end
7
6
 
8
- column_names.
9
- grep(re) { $1.to_sym }.
10
- sort_by(&locale_sort_value)
7
+ # Consider this method internal.
8
+ def _locale_columns_for_attribute(attribute, fallback)
9
+ traco_cache(attribute, fallback).values
11
10
  end
12
11
 
13
12
  def locale_columns(*attributes)
14
- attributes.inject([]) { |memo, attribute|
15
- memo += locales_for_attribute(attribute).map { |locale|
16
- :"#{attribute}_#{locale}"
17
- }
18
- }
13
+ attributes.each_with_object([]) do |attribute, columns|
14
+ columns.concat(_locale_columns_for_attribute(attribute, LocaleFallbacks::ANY_FALLBACK))
15
+ end
19
16
  end
20
17
 
21
18
  def current_locale_column(attribute)
22
- :"#{attribute}_#{I18n.locale.to_s.downcase.sub("-", "_")}"
19
+ Traco.column(attribute, I18n.locale)
23
20
  end
24
21
 
25
- def human_attribute_name(attribute, options = {})
26
- default = super(attribute, options.merge(default: ""))
22
+ def human_attribute_name(column, options = {})
23
+ attribute, locale = Traco.split_localized_column(column)
27
24
 
28
- if default.blank? && attribute.to_s.match(/\A(\w+?)_(#{LOCALE_RE})\z/)
29
- column, locale = $1, $2.to_sym
30
- if translates?(column)
31
- return "#{super(column, options)} (#{locale_name(locale)})"
32
- end
25
+ if translates?(attribute)
26
+ default = super(column, options.merge(default: ""))
27
+ default.presence || "#{super(attribute, options)} (#{Traco.locale_name(locale)})"
28
+ else
29
+ super
33
30
  end
34
-
35
- super
36
31
  end
37
32
 
38
33
  private
39
34
 
40
- def locale_sort_value
41
- lambda { |locale|
42
- if locale == Traco.locale_suffix(I18n.default_locale)
43
- # Sort the default locale first.
44
- "0"
45
- else
46
- # Sort the rest alphabetically.
47
- locale.to_s
48
- end
49
- }
50
- end
51
-
52
35
  def translates?(attribute)
53
- translatable_attributes.include?(attribute.to_sym)
36
+ translatable_attributes.include?(attribute)
54
37
  end
55
38
 
56
- def locale_name(locale)
57
- default = locale.to_s.upcase.sub("_", "-")
58
- I18n.t(locale, scope: :"i18n.languages", default: default)
39
+ # Structured by (current) locale => attribute => fallback option => {locale_to_try => column}.
40
+ # @example
41
+ # {
42
+ # :"pt-BR" => {
43
+ # title: {
44
+ # default: { :"pt-BR" => :title_pt_br, :en => :title_en },
45
+ # any: { :"pt-BR" => :title_pt_br, :en => :title_en, :sv => :title_sv }
46
+ # },
47
+ # },
48
+ # :sv => {
49
+ # title: {
50
+ # default: { :sv => :title_sv, :en => :title_en },
51
+ # any: { :sv => :title_sv, :en => :title_en, :"pt-BR" => :title_pt_br }
52
+ # }
53
+ # }
54
+ # }
55
+ def traco_cache(attribute, fallback)
56
+ cache = @traco_cache ||= {}
57
+ per_locale_cache = cache[I18n.locale] ||= {}
58
+ per_attribute_cache = per_locale_cache[attribute] ||= {}
59
+
60
+ per_attribute_cache[fallback] ||= begin
61
+ locales_to_try = Traco.locale_with_fallbacks(I18n.locale, fallback)
62
+
63
+ locales_to_try.each_with_object({}) do |locale, columns|
64
+ column = Traco.column(attribute, locale)
65
+ columns[locale] = column if instance_methods.include?(column)
66
+ end
67
+ end
59
68
  end
60
69
  end
61
70
  end
@@ -0,0 +1,46 @@
1
+ # Intended to be API compatible with https://github.com/svenfuchs/i18n/blob/master/lib/i18n/locale/fallbacks.rb
2
+
3
+ module Traco
4
+ class LocaleFallbacks
5
+ OPTIONS = [
6
+ DEFAULT_FALLBACK = :default,
7
+ ANY_FALLBACK = :any,
8
+ NO_FALLBACK = false,
9
+ ]
10
+
11
+ attr_reader :fallback_option
12
+ private :fallback_option
13
+
14
+ def initialize(fallback_option)
15
+ @fallback_option = validate_option(fallback_option)
16
+ @default_locale = I18n.default_locale
17
+ @available_locales = I18n.available_locales.sort
18
+ end
19
+
20
+ def [](for_locale)
21
+ chain = [for_locale]
22
+ chain << @default_locale if include_default_locale?
23
+ chain |= @available_locales if include_available_locales?
24
+ chain
25
+ end
26
+
27
+ private
28
+
29
+ def include_default_locale?
30
+ [ DEFAULT_FALLBACK, ANY_FALLBACK ].include?(fallback_option)
31
+ end
32
+
33
+ def include_available_locales?
34
+ ANY_FALLBACK == fallback_option
35
+ end
36
+
37
+ def validate_option(fallback_option)
38
+ if OPTIONS.include?(fallback_option)
39
+ fallback_option
40
+ else
41
+ valids = OPTIONS.map(&:inspect).join(", ")
42
+ raise ArgumentError.new("Unsupported fallback: #{fallback_option.inspect} (expected one of #{valids})")
43
+ end
44
+ end
45
+ end
46
+ end
@@ -1,11 +1,7 @@
1
- require "traco/attribute_setup"
2
-
3
1
  module Traco
4
2
  module Translates
5
3
  def translates(*attributes)
6
- options = attributes.extract_options!
7
- attributes = attributes.map(&:to_sym)
8
- AttributeSetup.new(self).set_up(attributes, options)
4
+ include Traco::Attributes.new(*attributes)
9
5
  end
10
6
  end
11
7
  end
data/lib/traco/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Traco
2
- VERSION = "3.1.2"
2
+ VERSION = "3.1.3"
3
3
  end
@@ -0,0 +1,45 @@
1
+ require "spec_helper"
2
+ require "traco/locale_fallbacks"
3
+
4
+ describe Traco::LocaleFallbacks do
5
+ describe "#initialize" do
6
+ it "accepts :default, :any, and false as valid arguments" do
7
+ expect {
8
+ Traco::LocaleFallbacks.new(:default)
9
+ Traco::LocaleFallbacks.new(:any)
10
+ Traco::LocaleFallbacks.new(false)
11
+ }.not_to raise_error
12
+ end
13
+
14
+ it "raises ArgumentError if invalid argument passed in" do
15
+ expect { Traco::LocaleFallbacks.new(:invalid) }.to raise_error(ArgumentError)
16
+ expect { Traco::LocaleFallbacks.new(nil) }.to raise_error(ArgumentError)
17
+ end
18
+ end
19
+
20
+ describe "#[]" do
21
+ context "with the :default option" do
22
+ it "returns given locale, then default locale" do
23
+ I18n.default_locale = :en
24
+ subject = Traco::LocaleFallbacks.new(:default)
25
+ expect(subject[:sv]).to eq [ :sv, :en ]
26
+ end
27
+ end
28
+
29
+ context "with the :any option" do
30
+ it "returns given locale, then default locale, then remaining available locales alphabetically" do
31
+ I18n.default_locale = :en
32
+ I18n.available_locales = [ :en, :sv, :uk, :de ]
33
+ subject = Traco::LocaleFallbacks.new(:any)
34
+ expect(subject[:sv]).to eq [ :sv, :en, :de, :uk ]
35
+ end
36
+ end
37
+
38
+ context "with the false option" do
39
+ it "returns only given locale" do
40
+ subject = Traco::LocaleFallbacks.new(false)
41
+ expect(subject[:sv]).to eq [ :sv ]
42
+ end
43
+ end
44
+ end
45
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,33 +1,9 @@
1
+ require "i18n"
2
+
1
3
  RSpec.configure do |config|
2
4
  config.treat_symbols_as_metadata_keys_with_true_values = true
3
5
  config.filter_run focus: true
4
6
  config.run_all_when_everything_filtered = true
5
-
6
- # Clear class state before each spec.
7
- config.before(:each) do
8
- Object.send(:remove_const, 'Post')
9
- Object.send(:remove_const, 'SubPost')
10
- load 'app/post.rb'
11
- end
12
- end
13
-
14
- # Test against real ActiveRecord models.
15
- # Very much based on the test setup in
16
- # https://github.com/iain/translatable_columns/
17
-
18
- require "active_record"
19
- require "app/post.rb"
20
-
21
- ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
22
-
23
- silence_stream(STDOUT) do
24
- ActiveRecord::Schema.define(version: 0) do
25
- create_table :posts, force: true do |t|
26
- t.string :title_sv, :title_en, :title_pt_br
27
- t.string :body_sv, :body_en, :body_pt_br
28
- end
29
- end
30
7
  end
31
8
 
32
9
  I18n.enforce_available_locales = false
33
- I18n.load_path << "spec/app/sv.yml"
@@ -0,0 +1,28 @@
1
+ RSpec.configure do |config|
2
+ # Clear class state before each spec.
3
+ config.before(:each) do
4
+ Object.send(:remove_const, 'Post')
5
+ Object.send(:remove_const, 'SubPost')
6
+ load "app/post.rb"
7
+ end
8
+ end
9
+
10
+ # Test against real ActiveRecord models.
11
+ # Very much based on the test setup in
12
+ # https://github.com/iain/translatable_columns/
13
+
14
+ require "active_record"
15
+ require "app/post.rb"
16
+
17
+ ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
18
+
19
+ silence_stream(STDOUT) do
20
+ ActiveRecord::Schema.define(version: 0) do
21
+ create_table :posts, force: true do |t|
22
+ t.string :title_sv, :title_en, :title_pt_br
23
+ t.string :body_sv, :body_en, :body_pt_br
24
+ end
25
+ end
26
+ end
27
+
28
+ I18n.load_path << "spec/app/sv.yml"
data/spec/traco_spec.rb CHANGED
@@ -1,8 +1,29 @@
1
1
  # encoding: utf-8
2
2
 
3
+ require "spec_helper_models"
3
4
  require "spec_helper"
4
5
  require "traco"
5
6
 
7
+ describe Traco, ".split_localized_column" do
8
+ subject { described_class }
9
+
10
+ it "returns attribute and locale" do
11
+ expect(subject.split_localized_column("title_sv")).to eq [:title, :sv]
12
+ end
13
+
14
+ it "handles normalized locales" do
15
+ expect(subject.split_localized_column("title_pt_br")).to eq [:title, :"pt-BR"]
16
+ end
17
+
18
+ it "returns nil if column is not localized" do
19
+ expect(subject.split_localized_column("title")).to be_nil
20
+ end
21
+
22
+ it "returns nil if column is not localized but with undercores" do
23
+ expect(subject.split_localized_column("long_title")).to be_nil
24
+ end
25
+ end
26
+
6
27
  describe ActiveRecord::Base, ".translates" do
7
28
  it "is available" do
8
29
  expect(Post).to respond_to :translates
@@ -21,6 +42,12 @@ describe ActiveRecord::Base, ".translates" do
21
42
  expect(Post.new).to respond_to :title, :body
22
43
  end
23
44
 
45
+ it "allows to define several attributes at once" do
46
+ expect(Post.new).not_to respond_to :title, :body
47
+ Post.translates :title, :body
48
+ expect(Post.new).to respond_to :title, :body
49
+ end
50
+
24
51
  it "inherits columns from the superclass" do
25
52
  Post.translates :title
26
53
  SubPost.translates :body
@@ -36,38 +63,59 @@ describe Post, ".translatable_attributes" do
36
63
  end
37
64
 
38
65
  it "lists the translatable attributes" do
39
- expect(Post.translatable_attributes).to match_array [ :title ]
66
+ expect(Post.translatable_attributes).to eq [ :title ]
67
+ end
68
+
69
+ it "inherits attributes from superclass to a subclass" do
70
+ SubPost.translates :body
71
+ expect(SubPost.translatable_attributes).to eq [ :title, :body ]
72
+ end
73
+
74
+ it "doesn't inherit attributes from a subclass to superclass" do
75
+ SubPost.translates :body
76
+ expect(Post.translatable_attributes).to eq [ :title ]
40
77
  end
41
78
  end
42
79
 
43
80
  describe Post, ".locales_for_attribute" do
44
81
  before do
45
82
  Post.translates :title
83
+ I18n.locale = :"pt-BR"
46
84
  end
47
85
 
48
- it "lists the locales, default first and then alphabetically" do
49
- I18n.default_locale = :"pt-BR"
50
- expect(Post.locales_for_attribute(:title)).to match_array [
51
- :pt_br, :en, :sv
52
- ]
86
+ it "lists the current locale with :any locale fallback" do
87
+ expect(Traco).to receive(:locale_with_fallbacks).with(:"pt-BR", :any).and_return([:"pt-BR", :en, :sv])
88
+ expect(Post.locales_for_attribute(:title)).to eq [ :"pt-BR", :en, :sv ]
89
+ end
90
+
91
+ it "doesn't include a locale if there's no corresponding column for it" do
92
+ expect(Traco).to receive(:locale_with_fallbacks).with(:"pt-BR", :any).and_return([:"pt-BR", :ru])
93
+ expect(Post.locales_for_attribute(:title)).to eq [ :"pt-BR" ]
53
94
  end
54
95
  end
55
96
 
56
97
  describe Post, ".locale_columns" do
57
98
  before do
58
99
  Post.translates :title
59
- I18n.default_locale = :"pt-BR"
100
+ I18n.locale = :"pt-BR"
60
101
  end
61
102
 
62
- it "lists the columns-with-locale for that attribute, default locale first and then alphabetically" do
63
- expect(Post.locale_columns(:title)).to match_array [
103
+ it "lists the columns-with-locale for current locale with :any locale fallback" do
104
+ expect(Traco).to receive(:locale_with_fallbacks).with(:"pt-BR", :any).and_return([:"pt-BR", :en, :sv])
105
+ expect(Post.locale_columns(:title)).to eq [
64
106
  :title_pt_br, :title_en, :title_sv
65
107
  ]
66
108
  end
67
109
 
110
+ it "doesn't include a column-with-locale if it doesn't exist" do
111
+ expect(Traco).to receive(:locale_with_fallbacks).with(:"pt-BR", :any).and_return([:"pt-BR", :ru])
112
+ expect(Post.locale_columns(:title)).to eq [ :title_pt_br ]
113
+ end
114
+
68
115
  it "supports multiple attributes" do
69
116
  Post.translates :body
70
- expect(Post.locale_columns(:body, :title)).to match_array [
117
+ expect(Traco).to receive(:locale_with_fallbacks).with(:"pt-BR", :any).twice.and_return([:"pt-BR", :en, :sv])
118
+ expect(Post.locale_columns(:body, :title)).to eq [
71
119
  :body_pt_br, :body_en, :body_sv,
72
120
  :title_pt_br, :title_en, :title_sv
73
121
  ]
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: traco
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.2
4
+ version: 3.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Henrik Nyh
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-07-09 00:00:00.000000000 Z
11
+ date: 2014-08-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -80,15 +80,18 @@ files:
80
80
  - Gemfile
81
81
  - README.md
82
82
  - Rakefile
83
+ - benchmarks/overhead.rb
83
84
  - lib/traco.rb
84
- - lib/traco/attribute_setup.rb
85
+ - lib/traco/attributes.rb
85
86
  - lib/traco/class_methods.rb
86
- - lib/traco/localized_reader.rb
87
+ - lib/traco/locale_fallbacks.rb
87
88
  - lib/traco/translates.rb
88
89
  - lib/traco/version.rb
89
90
  - spec/app/post.rb
90
91
  - spec/app/sv.yml
92
+ - spec/locale_fallbacks_spec.rb
91
93
  - spec/spec_helper.rb
94
+ - spec/spec_helper_models.rb
92
95
  - spec/traco_spec.rb
93
96
  - traco.gemspec
94
97
  homepage: ''
@@ -117,5 +120,7 @@ summary: Translatable columns for Rails 3 or better, stored in the model table i
117
120
  test_files:
118
121
  - spec/app/post.rb
119
122
  - spec/app/sv.yml
123
+ - spec/locale_fallbacks_spec.rb
120
124
  - spec/spec_helper.rb
125
+ - spec/spec_helper_models.rb
121
126
  - spec/traco_spec.rb
@@ -1,78 +0,0 @@
1
- module Traco
2
- class AttributeSetup
3
- INSTANCE_METHODS_MODULE_NAME = "TracoInstanceMethods"
4
-
5
- def initialize(klass)
6
- @klass = klass
7
- end
8
-
9
- def set_up(attributes, options)
10
- ensure_class_methods
11
- ensure_attribute_list
12
- ensure_instance_methods_module
13
- add_attributes attributes, options
14
- end
15
-
16
- private
17
-
18
- def ensure_class_methods
19
- klass.extend Traco::ClassMethods
20
- end
21
-
22
- # Only called once per class or inheritance chain (e.g. once
23
- # for the superclass, not at all for subclasses). The separation
24
- # is important if we don't want to overwrite values if running
25
- # multiple times in the same class or in different classes of
26
- # an inheritance chain.
27
- def ensure_attribute_list
28
- return if klass.respond_to?(:translatable_attributes)
29
- klass.class_attribute :translatable_attributes
30
- klass.translatable_attributes = []
31
- end
32
-
33
- def ensure_instance_methods_module
34
- # Instance methods are defined on an included module, so your class
35
- # can just redefine them and call `super`, if you need to.
36
- # http://thepugautomatic.com/2013/07/dsom/
37
- unless klass.const_defined?(INSTANCE_METHODS_MODULE_NAME, _search_ancestors = false)
38
- klass.send :include, klass.const_set(INSTANCE_METHODS_MODULE_NAME, Module.new)
39
- end
40
- end
41
-
42
- def add_attributes(attributes, options)
43
- klass.translatable_attributes |= attributes
44
-
45
- attributes.each do |attribute|
46
- define_localized_reader attribute, options
47
- define_localized_writer attribute, options
48
- end
49
- end
50
-
51
- def define_localized_reader(attribute, options)
52
- default_fallback = options.fetch(:fallback, Traco::LocalizedReader::DEFAULT_FALLBACK)
53
-
54
- custom_define_method(attribute) do |method_opts = {}|
55
- fallback = method_opts.fetch(:fallback, default_fallback)
56
-
57
- Traco::LocalizedReader.new(self, attribute, fallback: fallback).value
58
- end
59
- end
60
-
61
- def define_localized_writer(attribute, options)
62
- custom_define_method("#{attribute}=") do |value|
63
- suffix = Traco.locale_suffix(I18n.locale)
64
- send("#{attribute}_#{suffix}=", value)
65
- end
66
- end
67
-
68
- def custom_define_method(name, &block)
69
- klass.const_get(INSTANCE_METHODS_MODULE_NAME).module_eval do
70
- define_method(name, &block)
71
- end
72
- end
73
-
74
- def klass
75
- @klass
76
- end
77
- end
78
- end
@@ -1,50 +0,0 @@
1
- module Traco
2
- class LocalizedReader
3
- FALLBACK_OPTIONS = [
4
- DEFAULT_FALLBACK = :default,
5
- ANY_FALLBACK = :any,
6
- NO_FALLBACK = false,
7
- ]
8
-
9
- def initialize(record, attribute, options)
10
- @record = record
11
- @attribute = attribute
12
- @fallback = options[:fallback]
13
- validate_fallback
14
- end
15
-
16
- def value
17
- locales_to_try.each do |locale|
18
- value = @record.send("#{@attribute}_#{Traco.locale_suffix(locale)}")
19
- return value if value.present?
20
- end
21
-
22
- nil
23
- end
24
-
25
- private
26
-
27
- def locales_to_try
28
- locale_chain & locales_for_attribute
29
- end
30
-
31
- def locale_chain
32
- chain = []
33
- chain << I18n.locale
34
- chain << I18n.default_locale if [DEFAULT_FALLBACK, ANY_FALLBACK].include?(@fallback)
35
- chain += I18n.available_locales if @fallback == ANY_FALLBACK
36
- chain.map { |locale| Traco.locale_suffix(locale) }
37
- end
38
-
39
- def locales_for_attribute
40
- @record.class.locales_for_attribute(@attribute)
41
- end
42
-
43
- def validate_fallback
44
- unless FALLBACK_OPTIONS.include?(@fallback)
45
- valids = FALLBACK_OPTIONS.map(&:inspect).join(", ")
46
- raise "Unsupported fallback: #{@fallback.inspect} (expected one of #{valids})"
47
- end
48
- end
49
- end
50
- end