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 +8 -8
- data/Gemfile +5 -1
- data/README.md +16 -0
- data/benchmarks/overhead.rb +41 -0
- data/lib/traco.rb +51 -5
- data/lib/traco/attributes.rb +71 -0
- data/lib/traco/class_methods.rb +46 -37
- data/lib/traco/locale_fallbacks.rb +46 -0
- data/lib/traco/translates.rb +1 -5
- data/lib/traco/version.rb +1 -1
- data/spec/locale_fallbacks_spec.rb +45 -0
- data/spec/spec_helper.rb +2 -26
- data/spec/spec_helper_models.rb +28 -0
- data/spec/traco_spec.rb +58 -10
- metadata +9 -4
- data/lib/traco/attribute_setup.rb +0 -78
- data/lib/traco/localized_reader.rb +0 -50
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
NGU5NTFmYTU4ZDQ5ZDA5ZWUyMzIyYjNiZDkxOGZiYzkyZDAwOGU1ZA==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
OTI3ODAxOTgwNTE1OGQyZmFjYjUxMTJlZjQ1MmI0YjBkNTY0MjU5OA==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
NGFlOGM1M2QyMjVjZmQzNjQ1MjRkZDYxNDE5ZWQzOGQ2MjFkODFlNjU3ZjI2
|
10
|
+
Y2UxNTJiYTZmYTRiM2FiMDFkYzYxMDRkMGNkMjA5NjVlNzIzYWNmMjJiZWFm
|
11
|
+
MzU4Y2NiZWNlODk1MWMxODEyMTg4NTRmMDQ1ZjBkZmRlNjlkNDg=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
NWQ2MmFlZDZhY2FhZTllMTZkMTUzMWI2ZWRkZDEwNzBkMmY5M2Y2YmM0NjIz
|
14
|
+
MGIwZmY3MmY3YTgxYTY0NzVmODM4M2Q1Mjk0ZTI5MDU1MjYxOTM0N2U3Njll
|
15
|
+
NmQ0MWY4NzBmNjcyMjk2M2M2ZGQwNTdmMjBlNWU5ZmExMjU2MDA=
|
data/Gemfile
CHANGED
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/
|
2
|
+
require "traco/attributes"
|
3
3
|
require "traco/class_methods"
|
4
|
-
require "traco/
|
4
|
+
require "traco/locale_fallbacks"
|
5
|
+
require "traco/translates"
|
5
6
|
|
6
7
|
module Traco
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
data/lib/traco/class_methods.rb
CHANGED
@@ -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
|
-
|
4
|
+
traco_cache(attribute, LocaleFallbacks::ANY_FALLBACK).keys
|
5
|
+
end
|
7
6
|
|
8
|
-
|
9
|
-
|
10
|
-
|
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.
|
15
|
-
|
16
|
-
|
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
|
-
|
19
|
+
Traco.column(attribute, I18n.locale)
|
23
20
|
end
|
24
21
|
|
25
|
-
def human_attribute_name(
|
26
|
-
|
22
|
+
def human_attribute_name(column, options = {})
|
23
|
+
attribute, locale = Traco.split_localized_column(column)
|
27
24
|
|
28
|
-
if
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
36
|
+
translatable_attributes.include?(attribute)
|
54
37
|
end
|
55
38
|
|
56
|
-
|
57
|
-
|
58
|
-
|
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
|
data/lib/traco/translates.rb
CHANGED
@@ -1,11 +1,7 @@
|
|
1
|
-
require "traco/attribute_setup"
|
2
|
-
|
3
1
|
module Traco
|
4
2
|
module Translates
|
5
3
|
def translates(*attributes)
|
6
|
-
|
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
@@ -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
|
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
|
49
|
-
|
50
|
-
expect(Post.locales_for_attribute(:title)).to
|
51
|
-
|
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.
|
100
|
+
I18n.locale = :"pt-BR"
|
60
101
|
end
|
61
102
|
|
62
|
-
it "lists the columns-with-locale for
|
63
|
-
expect(
|
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(
|
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.
|
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-
|
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/
|
85
|
+
- lib/traco/attributes.rb
|
85
86
|
- lib/traco/class_methods.rb
|
86
|
-
- lib/traco/
|
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
|