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