traco 3.1.2 → 3.1.3

Sign up to get free protection for your applications and to get access to all the features.
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