translated_attributes 0.5.1
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.
- data/MIGRATION +18 -0
- data/README.markdown +49 -0
- data/Rakefile +31 -0
- data/VERSION +1 -0
- data/init.rb +2 -0
- data/lib/translated_attributes.rb +152 -0
- data/spec/integration_spec.rb +42 -0
- data/spec/models.rb +31 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/translated_attributes_spec.rb +304 -0
- data/translated_attributes.gemspec +62 -0
- metadata +77 -0
data/MIGRATION
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
class AddTranslations < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
#you can remove the limit/null constrains
|
4
|
+
#this is simply my recommended way of setting things up (save + limits needed storage space)
|
5
|
+
create_table :translations do |t|
|
6
|
+
t.column :translatable_id, :integer, :null=>false
|
7
|
+
t.column :translatable_type, :string, :limit=>40, :null=>false
|
8
|
+
t.column :language, :string, :limit=>2, :null=>false
|
9
|
+
t.column :attribute, :string, :limit=>40, :null=>false
|
10
|
+
t.column :text, :text, :null=>false
|
11
|
+
end
|
12
|
+
add_index :translations, [:translatable_id, :translatable_type]
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.down
|
16
|
+
drop_table :translations
|
17
|
+
end
|
18
|
+
end
|
data/README.markdown
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
Rails plugin/ActiveRecord gem that creates 'virtual' attributes, which can be added on the fly without overhead or migrations, while storing all the data in a never-changing translations table.
|
2
|
+
This keeps the attatched model light and allows to add/remove fields on the fly without migrations.
|
3
|
+
|
4
|
+
Validations work like normal with current field (e.g. title) or any translation (e.g. title_in_en)
|
5
|
+
|
6
|
+
Usage
|
7
|
+
=====
|
8
|
+
- As Rails plugin `script/plugin install git://github.com/grosser/translated_attributes.git`
|
9
|
+
- As gem `sudo gem install translated_attributes`
|
10
|
+
- execute MIGRATION
|
11
|
+
|
12
|
+
Adding attributes:
|
13
|
+
class Product < ActiveRecord::Base
|
14
|
+
translated_attributes :description, :title, :additional_info
|
15
|
+
end
|
16
|
+
|
17
|
+
Setting / getting
|
18
|
+
#getter
|
19
|
+
product.title -> 'Hello' #when I18n.locale is :en
|
20
|
+
product.title_in_fr -> 'Bonyour'
|
21
|
+
product.title_in_de -> 'Hallo'
|
22
|
+
|
23
|
+
#setter
|
24
|
+
product.title = 'Simple setting' #sets title_in_en when I18n.locale == :en
|
25
|
+
product.title_in_de = 'Spezifisches speichern'
|
26
|
+
|
27
|
+
#generic setter/getter
|
28
|
+
product.set_title('Specific setting', :en)
|
29
|
+
product.get_title(:en) -> 'Specific setting'
|
30
|
+
|
31
|
+
Usage with saving works exactly like normal saving, e.g. new/create/update_attributes...
|
32
|
+
Product.new(:title_in_en=>'Hello').save!
|
33
|
+
product.update_attribute(:title, 'Goodbye')
|
34
|
+
|
35
|
+
- Translations are stored on 'save'
|
36
|
+
- blank translations are NOT stored
|
37
|
+
- translations are accessable via .translations or as hash via .translated_attributes
|
38
|
+
|
39
|
+
Options
|
40
|
+
=======
|
41
|
+
translated_attributes :title, :heading,
|
42
|
+
:table_name => 'user_translations', #default is translations
|
43
|
+
:nil_to_blank => true, #return unfound translations as blank strings ('') instead of nil (default false),
|
44
|
+
:translatable_name => 'translated' #name of the associated translatable (Product has_many :translations a Translation belongs_to XXX), default is :translatable
|
45
|
+
Author
|
46
|
+
======
|
47
|
+
[Michael Grosser](http://pragmatig.wordpress.com)
|
48
|
+
grosser.michael@gmail.com
|
49
|
+
Hereby placed under public domain, do what you want, just do not hold me accountable...
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
desc "Run all specs in spec directory"
|
2
|
+
task :default do
|
3
|
+
options = "--colour --format progress --loadby --reverse"
|
4
|
+
files = FileList['spec/**/*_spec.rb']
|
5
|
+
system("spec #{options} #{files}")
|
6
|
+
end
|
7
|
+
|
8
|
+
begin
|
9
|
+
project_name = 'translated_attributes'
|
10
|
+
require 'jeweler'
|
11
|
+
Jeweler::Tasks.new do |gem|
|
12
|
+
gem.name = project_name
|
13
|
+
gem.summary = "ActiveRecord/Rails simple translatable attributes"
|
14
|
+
gem.email = "grosser.michael@gmail.com"
|
15
|
+
gem.homepage = "http://github.com/grosser/#{project_name}"
|
16
|
+
gem.authors = ["Michael Grosser"]
|
17
|
+
gem.files += (FileList["{lib,spec}/**/*"] + FileList["VERSION"] + FileList["README.markdown"]).to_a.sort
|
18
|
+
gem.add_dependency ['activerecord']
|
19
|
+
gem.rubyforge_project = 'translated-attr'
|
20
|
+
end
|
21
|
+
|
22
|
+
# fake task so that rubyforge:release works
|
23
|
+
task :rdoc do
|
24
|
+
`mkdir rdoc`
|
25
|
+
`echo documentation is at http://github.com/grosser/#{project_name} > rdoc/README.rdoc`
|
26
|
+
end
|
27
|
+
|
28
|
+
Jeweler::RubyforgeTasks.new
|
29
|
+
rescue LoadError
|
30
|
+
puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
31
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.5.1
|
data/init.rb
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
module TranslatedAttributes
|
2
|
+
module ClassMethods
|
3
|
+
# translated_attributes :title, :description, :table_name=>'my_translations'
|
4
|
+
def translated_attributes(*args)
|
5
|
+
#store options
|
6
|
+
cattr_accessor :translated_attributes_options
|
7
|
+
options = args.extract_options! || {}
|
8
|
+
self.translated_attributes_options = options.merge(:fields=>args.map(&:to_sym))
|
9
|
+
|
10
|
+
#create translations class
|
11
|
+
table_name = options[:table_name] || :translations
|
12
|
+
class_name = table_name.to_s.classify
|
13
|
+
|
14
|
+
associated = options[:translatable_name] || :translatable
|
15
|
+
begin
|
16
|
+
klass = Object.const_get(class_name)
|
17
|
+
rescue
|
18
|
+
klass = Class.new(ActiveRecord::Base)
|
19
|
+
Object.const_set(class_name, klass)
|
20
|
+
klass.set_table_name table_name
|
21
|
+
klass.belongs_to associated, :polymorphic => true
|
22
|
+
end
|
23
|
+
|
24
|
+
#set translations
|
25
|
+
has_many :translations, :as => associated, :dependent => :delete_all, :class_name=>klass.name
|
26
|
+
|
27
|
+
#include methods
|
28
|
+
include TranslatedAttributes::InstanceMethods
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module InstanceMethods
|
33
|
+
def self.included(base)
|
34
|
+
fields = base.translated_attributes_options[:fields]
|
35
|
+
fields.each do |field|
|
36
|
+
base.class_eval <<-GETTER_AND_SETTER
|
37
|
+
def get_#{field}(locale=nil)
|
38
|
+
get_translated_attribute(locale, :#{field})
|
39
|
+
end
|
40
|
+
|
41
|
+
def set_#{field}(value, locale=I18n.locale)
|
42
|
+
set_translated_attribute(locale, :#{field}, value)
|
43
|
+
end
|
44
|
+
|
45
|
+
#generic aliases (and since e.g. title=(a,b) would not be possible)
|
46
|
+
alias #{field} get_#{field}
|
47
|
+
alias #{field}= set_#{field}
|
48
|
+
GETTER_AND_SETTER
|
49
|
+
end
|
50
|
+
|
51
|
+
base.after_save :store_translated_attributes
|
52
|
+
end
|
53
|
+
|
54
|
+
def get_translated_attribute(locale, field)
|
55
|
+
text = if locale
|
56
|
+
translated_attributes_for(locale)[field]
|
57
|
+
else
|
58
|
+
#try to find anything...
|
59
|
+
#current, english, anything else
|
60
|
+
found = translated_attributes_for(I18n.locale)[field] || translated_attributes_for(:en)[field]
|
61
|
+
if found
|
62
|
+
found
|
63
|
+
else
|
64
|
+
found = translated_attributes.detect{|locale, attributes| not attributes[field].blank?}
|
65
|
+
found ? found[1][field] : nil
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
if self.class.translated_attributes_options[:nil_to_blank]
|
70
|
+
text || ''
|
71
|
+
else
|
72
|
+
text
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def set_translated_attribute(locale, field, value)
|
77
|
+
return if translated_attributes_for(locale)[field] == value
|
78
|
+
translated_attributes_for(locale)[field] = value
|
79
|
+
@translated_attributes_changed = true
|
80
|
+
end
|
81
|
+
|
82
|
+
def translated_attributes
|
83
|
+
merge_db_translations_with_instance_variable
|
84
|
+
@translated_attributes ||= {}.with_indifferent_access
|
85
|
+
end
|
86
|
+
|
87
|
+
def translated_attributes= hash
|
88
|
+
@db_translations_merged = true #do not overwrite what we set here
|
89
|
+
@translated_attributes_changed = true #store changes we made
|
90
|
+
@translated_attributes = hash.with_indifferent_access
|
91
|
+
end
|
92
|
+
|
93
|
+
def respond_to?(name, *args)
|
94
|
+
return true if parse_translated_attribute_method(name)
|
95
|
+
super
|
96
|
+
end
|
97
|
+
|
98
|
+
def method_missing(name, *args)
|
99
|
+
field, locale = parse_translated_attribute_method(name)
|
100
|
+
return super unless field
|
101
|
+
if name.to_s.include? '=' #is setter ?
|
102
|
+
send("set_#{field}", args[0], locale)
|
103
|
+
else
|
104
|
+
send("get_#{field}", locale)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
protected
|
109
|
+
|
110
|
+
def store_translated_attributes
|
111
|
+
return true unless @translated_attributes_changed
|
112
|
+
translations.delete_all
|
113
|
+
@translated_attributes.each do |locale, attributes|
|
114
|
+
attributes.each do |attribute, value|
|
115
|
+
next if value.blank?
|
116
|
+
next unless self.class.translated_attributes_options[:fields].include? attribute.to_sym
|
117
|
+
translations.create!(:attribute=>attribute, :text=>value, :language=>locale)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
@translated_attributes_changed = false
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
def merge_db_translations_with_instance_variable
|
126
|
+
return if new_record? or @db_translations_merged
|
127
|
+
@db_translations_merged = true
|
128
|
+
translations.all.each do |t|
|
129
|
+
translated_attributes_for(t.language)[t.attribute] = t.text
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def parse_translated_attribute_method(name)
|
134
|
+
return false if name.to_s !~ /^([a-zA-Z_]+)_in_([a-z]{2})[=]?$/
|
135
|
+
field = $1; locale = $2
|
136
|
+
return false unless is_translated_attribute?(field)
|
137
|
+
return field, locale
|
138
|
+
end
|
139
|
+
|
140
|
+
def is_translated_attribute?(method_name)
|
141
|
+
fields = self.class.translated_attributes_options[:fields]
|
142
|
+
fields.include? method_name.sub('=','').to_sym
|
143
|
+
end
|
144
|
+
|
145
|
+
def translated_attributes_for(locale)
|
146
|
+
translated_attributes[locale] ||= {}.with_indifferent_access
|
147
|
+
translated_attributes[locale]
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
ActiveRecord::Base.send :extend, TranslatedAttributes::ClassMethods
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'spec/spec_helper'
|
2
|
+
|
3
|
+
describe 'Integration' do
|
4
|
+
before do
|
5
|
+
Translation.delete_all
|
6
|
+
UserTranslation.delete_all
|
7
|
+
end
|
8
|
+
|
9
|
+
it "manages different translations appropriatly" do
|
10
|
+
User.create(:name_in_en=>'User1NameEn', :name_in_de=>'User1NameDe')
|
11
|
+
User.create(:name_in_en=>'User2NameEn', :name_in_fr=>'User2NameFr')
|
12
|
+
Product.create(:title=>'Product1TitleEn', :title_in_de=>'Product1TitleDe')
|
13
|
+
|
14
|
+
User.first.translated_attributes = {}
|
15
|
+
u = User.last
|
16
|
+
u.translated_attributes = {:fr=>{:name=>'User1NameFr'}}
|
17
|
+
u.save!
|
18
|
+
|
19
|
+
UserTranslation.count.should == 3
|
20
|
+
Translation.count.should == 2
|
21
|
+
User.last.name.should == 'User1NameFr'
|
22
|
+
end
|
23
|
+
|
24
|
+
it "cleans up translations" do
|
25
|
+
User.create!(:name=>'u1')
|
26
|
+
Product.create!(:title=>'p1',:description=>'d1')
|
27
|
+
Product.create!(:title=>'p2')
|
28
|
+
|
29
|
+
Translation.count.should == 3
|
30
|
+
UserTranslation.count.should == 1
|
31
|
+
|
32
|
+
Product.destroy_all
|
33
|
+
|
34
|
+
Translation.count.should == 0
|
35
|
+
UserTranslation.count.should == 1
|
36
|
+
|
37
|
+
User.destroy_all
|
38
|
+
|
39
|
+
Translation.count.should == 0
|
40
|
+
UserTranslation.count.should == 0
|
41
|
+
end
|
42
|
+
end
|
data/spec/models.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
ActiveRecord::Schema.define(:version => 1) do
|
2
|
+
create_table :users do |t|
|
3
|
+
end
|
4
|
+
|
5
|
+
create_table :products do |t|
|
6
|
+
end
|
7
|
+
|
8
|
+
%w[translations user_translations].each do |table|
|
9
|
+
create_table table do |t|
|
10
|
+
t.integer :translatable_id, :null=>false
|
11
|
+
t.string :translatable_type, :limit=>40, :null=>false
|
12
|
+
t.string :language, :limit=>2, :null=>false
|
13
|
+
t.string :attribute, :limit=>40, :null=>false
|
14
|
+
t.text :text, :null=>false
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
#create model
|
20
|
+
class User < ActiveRecord::Base
|
21
|
+
translated_attributes :name, :table_name=>:user_translations, :nil_to_blank=>true
|
22
|
+
end
|
23
|
+
|
24
|
+
class Shop < ActiveRecord::Base
|
25
|
+
set_table_name :products
|
26
|
+
translated_attributes :shop_name
|
27
|
+
end
|
28
|
+
|
29
|
+
class Product < ActiveRecord::Base
|
30
|
+
translated_attributes :title, :description
|
31
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# ---- requirements
|
2
|
+
require 'rubygems'
|
3
|
+
require 'spec'
|
4
|
+
require 'activerecord'
|
5
|
+
|
6
|
+
$LOAD_PATH << File.expand_path("../lib", File.dirname(__FILE__))
|
7
|
+
|
8
|
+
# ---- setup environment/plugin
|
9
|
+
ActiveRecord::Base.establish_connection({
|
10
|
+
:adapter => "sqlite3",
|
11
|
+
:database => ":memory:",
|
12
|
+
})
|
13
|
+
|
14
|
+
#ActiveRecord::Base.logger = Logger.new(STDOUT)
|
15
|
+
|
16
|
+
require File.expand_path("../init", File.dirname(__FILE__))
|
17
|
+
|
18
|
+
require 'spec/models'
|
@@ -0,0 +1,304 @@
|
|
1
|
+
require 'spec/spec_helper'
|
2
|
+
|
3
|
+
describe 'Translated attributes' do
|
4
|
+
before do
|
5
|
+
I18n.locale = :en
|
6
|
+
end
|
7
|
+
|
8
|
+
describe 'caching' do
|
9
|
+
it "is nil when nothing is set" do
|
10
|
+
Product.new.title.should == nil
|
11
|
+
Product.new.description.should == nil
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'can be set' do
|
15
|
+
p = Product.new
|
16
|
+
p.title = 'abc'
|
17
|
+
p.title.should == 'abc'
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'can be overwritten' do
|
21
|
+
p = Product.new
|
22
|
+
p.title = 'abc'
|
23
|
+
p.title = 'def'
|
24
|
+
p.title.should == 'def'
|
25
|
+
end
|
26
|
+
|
27
|
+
it "ca be unset" do
|
28
|
+
p = Product.new
|
29
|
+
p.title = 'abc'
|
30
|
+
p.title = nil
|
31
|
+
p.title.should == nil
|
32
|
+
end
|
33
|
+
|
34
|
+
it "sets the current language" do
|
35
|
+
p = Product.new
|
36
|
+
p.title = 'abc'
|
37
|
+
p.title_in_en.should == 'abc'
|
38
|
+
end
|
39
|
+
|
40
|
+
it "can be set in different languages" do
|
41
|
+
p = Product.new
|
42
|
+
p.title = 'abc'
|
43
|
+
p.title_in_de.should == nil
|
44
|
+
I18n.locale = :de
|
45
|
+
p.title = 'bcd'
|
46
|
+
p.title_in_de.should == 'bcd'
|
47
|
+
end
|
48
|
+
|
49
|
+
it "returns current language when current can be found" do
|
50
|
+
I18n.locale = :de
|
51
|
+
p = Product.new
|
52
|
+
p.title_in_de = 'abc'
|
53
|
+
p.title_in_en = 'def'
|
54
|
+
p.title.should == 'abc'
|
55
|
+
end
|
56
|
+
|
57
|
+
it "returns english translation when current cannot be found" do
|
58
|
+
I18n.locale = :de
|
59
|
+
p = Product.new
|
60
|
+
p.title_in_en = 'abc'
|
61
|
+
p.title.should == 'abc'
|
62
|
+
end
|
63
|
+
|
64
|
+
it "returns any translation when current and english cannot be found" do
|
65
|
+
I18n.locale = :fr
|
66
|
+
p = Product.new
|
67
|
+
p.title_in_de = 'abc'
|
68
|
+
p.title.should == 'abc'
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe 'storing' do
|
73
|
+
it "stores nothing when nothing was set" do
|
74
|
+
lambda{ Product.create! }.should_not change(Translation, :count)
|
75
|
+
Product.last.title.should == nil
|
76
|
+
end
|
77
|
+
|
78
|
+
it "stores column as translation on save" do
|
79
|
+
lambda{ Product.create!(:title=>'x') }.should change(Translation, :count).by(+1)
|
80
|
+
end
|
81
|
+
|
82
|
+
it "stores nothing when blank was set" do
|
83
|
+
lambda{ Product.create! :title=>' ' }.should_not change(Translation, :count)
|
84
|
+
Product.last.title.should == nil
|
85
|
+
end
|
86
|
+
|
87
|
+
it "creates no translation when validations fail" do
|
88
|
+
p = Product.new :title=>'xxx'
|
89
|
+
p.should_receive(:valid?).and_return false
|
90
|
+
lambda{ p.save }.should_not change(Translation, :count)
|
91
|
+
end
|
92
|
+
|
93
|
+
it "is stored after save" do
|
94
|
+
p = Product.create!(:title=>'1')
|
95
|
+
p.title = '2'
|
96
|
+
p.save!
|
97
|
+
Product.last.title.should == '2'
|
98
|
+
end
|
99
|
+
|
100
|
+
it "does not create unecessary translations on change" do
|
101
|
+
p = Product.create!(:title=>'1')
|
102
|
+
lambda{
|
103
|
+
p.title = '2'
|
104
|
+
p.save!
|
105
|
+
}.should_not change(Translation, :count)
|
106
|
+
end
|
107
|
+
|
108
|
+
it "does not update translations when nothing changed" do
|
109
|
+
p = Product.create!(:title=>'xx')
|
110
|
+
p.title = 'xx'
|
111
|
+
lambda{ p.save }.should_not change{Translation.last.id}
|
112
|
+
end
|
113
|
+
|
114
|
+
it "works through update_attribute" do
|
115
|
+
p = Product.create!(:title=>'xx', :description=>'dd')
|
116
|
+
p.update_attribute(:title, 'yy')
|
117
|
+
Product.last.title.should == 'yy'
|
118
|
+
Product.last.description.should == 'dd'
|
119
|
+
end
|
120
|
+
|
121
|
+
it "works through update_attributes" do
|
122
|
+
p = Product.create!(:title=>'xx', :description=>'dd')
|
123
|
+
p.update_attributes(:title=>'yy')
|
124
|
+
Product.last.title.should == 'yy'
|
125
|
+
Product.last.description.should == 'dd'
|
126
|
+
end
|
127
|
+
|
128
|
+
it "works through attributes=" do
|
129
|
+
p = Product.create!(:title=>'xx', :description=>'dd')
|
130
|
+
p.attributes = {:title=>'yy'}
|
131
|
+
p.save!
|
132
|
+
Product.last.title.should == 'yy'
|
133
|
+
Product.last.description.should == 'dd'
|
134
|
+
end
|
135
|
+
|
136
|
+
it "loads translations once" do
|
137
|
+
Product.create!(:title=>'xx', :description=>'yy')
|
138
|
+
p = Product.last
|
139
|
+
p.translations.should_receive(:all).and_return []
|
140
|
+
p.title.should == nil
|
141
|
+
p.description.should == nil
|
142
|
+
end
|
143
|
+
|
144
|
+
it "deletes the existing translation when changing to blank" do
|
145
|
+
p = Product.create!(:title=>'1')
|
146
|
+
lambda{
|
147
|
+
p.title = ''
|
148
|
+
p.save!
|
149
|
+
}.should change(Translation, :count).by(-1)
|
150
|
+
Product.last.title.should == nil
|
151
|
+
end
|
152
|
+
|
153
|
+
it "can store multiple translations at once" do
|
154
|
+
lambda{
|
155
|
+
Product.create!(:title_in_de=>'Hallo', :title_in_en=>'Hello')
|
156
|
+
}.should change(Translation, :count).by(+2)
|
157
|
+
Product.last.title_in_de.should == 'Hallo'
|
158
|
+
Product.last.title.should == 'Hello'
|
159
|
+
end
|
160
|
+
|
161
|
+
it "deletes translations when translatable is destroyed" do
|
162
|
+
Translation.delete_all
|
163
|
+
Product.create!(:title=>'t1')
|
164
|
+
Product.create!(:title=>'t2',:description=>'d2')
|
165
|
+
Translation.count.should == 3
|
166
|
+
|
167
|
+
Product.last.destroy
|
168
|
+
|
169
|
+
Translation.count.should == 1
|
170
|
+
Translation.first.text.should == 't1'
|
171
|
+
end
|
172
|
+
|
173
|
+
it "is not influenced by reloading" do
|
174
|
+
p = Product.create!(:title=>'t1', :description=>'d1')
|
175
|
+
p.title = 't2'
|
176
|
+
p.reload
|
177
|
+
p.description = 'd2'
|
178
|
+
p.save!
|
179
|
+
Product.last.title.should == 't2'
|
180
|
+
Product.last.description.should == 'd2'
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
describe 'classes' do
|
185
|
+
it "does not define them twice" do
|
186
|
+
Translation.instance_variable_set '@test', 1
|
187
|
+
class XXX < ActiveRecord::Base
|
188
|
+
set_table_name :products
|
189
|
+
translated_attributes :name
|
190
|
+
end
|
191
|
+
Translation.instance_variable_get('@test').should == 1
|
192
|
+
end
|
193
|
+
|
194
|
+
it "stores options seperately" do
|
195
|
+
Shop.translated_attributes_options[:fields].should_not == Product.translated_attributes_options[:fields]
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
describe 'different tables' do
|
200
|
+
it "creates the model for each table_name" do
|
201
|
+
Translation
|
202
|
+
UserTranslation
|
203
|
+
end
|
204
|
+
|
205
|
+
it "creates translations in set table" do
|
206
|
+
Product.create!(:title=>'yyy')
|
207
|
+
Translation.last.text.should == 'yyy'
|
208
|
+
|
209
|
+
User.create!(:name=>'xxx')
|
210
|
+
UserTranslation.last.text.should == 'xxx'
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
describe 'nil to blank' do
|
215
|
+
it "converts all unfound fields to blank" do
|
216
|
+
User.new.name.should == ''
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
describe :translated_attributes do
|
221
|
+
it "is a empty hash when nothing was set" do
|
222
|
+
Product.new.translated_attributes.should == {}
|
223
|
+
end
|
224
|
+
|
225
|
+
it "can be modified" do
|
226
|
+
Product.new.translated_attributes.should_not be_frozen
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
describe :translated_attriutes= do
|
231
|
+
it "stores all translations" do
|
232
|
+
p = Product.create!
|
233
|
+
p.translated_attributes = {:de=>{:title=>'de title',:description=>'de descr'}}
|
234
|
+
p.title_in_de.should == 'de title'
|
235
|
+
p.description_in_de.should == 'de descr'
|
236
|
+
p.title_in_en.should == nil
|
237
|
+
end
|
238
|
+
|
239
|
+
it "overwrites existing translations" do
|
240
|
+
p = Product.create!(:title=>'en title')
|
241
|
+
p.translated_attributes = {:de=>{:title=>'de title',:description=>'de descr'}}
|
242
|
+
p.title_in_de.should == 'de title'
|
243
|
+
p.description_in_de.should == 'de descr'
|
244
|
+
p.title_in_en.should == nil
|
245
|
+
end
|
246
|
+
|
247
|
+
it "stores and overwrites on save" do
|
248
|
+
p = Product.create!(:title=>'en title')
|
249
|
+
p.translated_attributes = {:de=>{:title=>'de title',:description=>'de descr'}}
|
250
|
+
p.save!
|
251
|
+
Product.last.title.should == 'de title'
|
252
|
+
Product.last.title_in_de.should == 'de title'
|
253
|
+
end
|
254
|
+
|
255
|
+
it "does not store unknown attributes" do
|
256
|
+
p = Product.new
|
257
|
+
p.translated_attributes = {:de=>{:xxx=>'de title',:description=>'de descr'}}
|
258
|
+
lambda{
|
259
|
+
p.save!
|
260
|
+
}.should change(Translation, :count).by(+1)
|
261
|
+
end
|
262
|
+
|
263
|
+
it "does not alter the given hash" do
|
264
|
+
p = Product.create!(:title=>'en title')
|
265
|
+
hash = {:de=>{:title=>'de title',:description=>'de descr'}}
|
266
|
+
p.translated_attributes = hash
|
267
|
+
hash['de'].should == nil #not converted to indifferent access
|
268
|
+
hash[:de][:title].should == 'de title' #still has all attributes
|
269
|
+
end
|
270
|
+
|
271
|
+
it "stores given hash indifferent" do
|
272
|
+
p = Product.new
|
273
|
+
p.translated_attributes = {'de'=>{'title'=>'title de'}}
|
274
|
+
p.translated_attributes[:de][:title].should == 'title de'
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
describe :method_missing do
|
279
|
+
it "ignores calls without _in_" do
|
280
|
+
lambda{Product.new.title_xxx}.should raise_error
|
281
|
+
end
|
282
|
+
it "ignores calls with a non-supported attribute" do
|
283
|
+
lambda{Product.new.foo_in_de}.should raise_error
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
describe :respond_to? do
|
288
|
+
it "ignores calls without _in_" do
|
289
|
+
Product.new.respond_to?(:title_xx_xx).should == false
|
290
|
+
end
|
291
|
+
|
292
|
+
it "ignores calls with a non-supported attribute" do
|
293
|
+
Product.new.respond_to?(:foo_in_de).should == false
|
294
|
+
end
|
295
|
+
|
296
|
+
it "responds to translated column" do
|
297
|
+
Product.new.respond_to?(:title_in_en).should == true
|
298
|
+
end
|
299
|
+
|
300
|
+
it "responds to normal methods" do
|
301
|
+
Product.new.respond_to?(:new_record?).should == true
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{translated_attributes}
|
8
|
+
s.version = "0.5.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Michael Grosser"]
|
12
|
+
s.date = %q{2009-10-16}
|
13
|
+
s.email = %q{grosser.michael@gmail.com}
|
14
|
+
s.extra_rdoc_files = [
|
15
|
+
"README.markdown"
|
16
|
+
]
|
17
|
+
s.files = [
|
18
|
+
"MIGRATION",
|
19
|
+
"README.markdown",
|
20
|
+
"README.markdown",
|
21
|
+
"Rakefile",
|
22
|
+
"VERSION",
|
23
|
+
"VERSION",
|
24
|
+
"init.rb",
|
25
|
+
"lib/translated_attributes.rb",
|
26
|
+
"lib/translated_attributes.rb",
|
27
|
+
"spec/integration_spec.rb",
|
28
|
+
"spec/integration_spec.rb",
|
29
|
+
"spec/models.rb",
|
30
|
+
"spec/models.rb",
|
31
|
+
"spec/spec_helper.rb",
|
32
|
+
"spec/spec_helper.rb",
|
33
|
+
"spec/translated_attributes_spec.rb",
|
34
|
+
"spec/translated_attributes_spec.rb",
|
35
|
+
"translated_attributes.gemspec"
|
36
|
+
]
|
37
|
+
s.homepage = %q{http://github.com/grosser/translated_attributes}
|
38
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
39
|
+
s.require_paths = ["lib"]
|
40
|
+
s.rubyforge_project = %q{translated-attr}
|
41
|
+
s.rubygems_version = %q{1.3.5}
|
42
|
+
s.summary = %q{ActiveRecord/Rails simple translatable attributes}
|
43
|
+
s.test_files = [
|
44
|
+
"spec/integration_spec.rb",
|
45
|
+
"spec/spec_helper.rb",
|
46
|
+
"spec/translated_attributes_spec.rb",
|
47
|
+
"spec/models.rb"
|
48
|
+
]
|
49
|
+
|
50
|
+
if s.respond_to? :specification_version then
|
51
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
52
|
+
s.specification_version = 3
|
53
|
+
|
54
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
55
|
+
s.add_runtime_dependency(%q<activerecord>, [">= 0"])
|
56
|
+
else
|
57
|
+
s.add_dependency(%q<activerecord>, [">= 0"])
|
58
|
+
end
|
59
|
+
else
|
60
|
+
s.add_dependency(%q<activerecord>, [">= 0"])
|
61
|
+
end
|
62
|
+
end
|
metadata
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: translated_attributes
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.5.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michael Grosser
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-16 00:00:00 +02:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: activerecord
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
description:
|
26
|
+
email: grosser.michael@gmail.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- README.markdown
|
33
|
+
files:
|
34
|
+
- MIGRATION
|
35
|
+
- README.markdown
|
36
|
+
- Rakefile
|
37
|
+
- VERSION
|
38
|
+
- init.rb
|
39
|
+
- lib/translated_attributes.rb
|
40
|
+
- spec/integration_spec.rb
|
41
|
+
- spec/models.rb
|
42
|
+
- spec/spec_helper.rb
|
43
|
+
- spec/translated_attributes_spec.rb
|
44
|
+
- translated_attributes.gemspec
|
45
|
+
has_rdoc: true
|
46
|
+
homepage: http://github.com/grosser/translated_attributes
|
47
|
+
licenses: []
|
48
|
+
|
49
|
+
post_install_message:
|
50
|
+
rdoc_options:
|
51
|
+
- --charset=UTF-8
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: "0"
|
59
|
+
version:
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: "0"
|
65
|
+
version:
|
66
|
+
requirements: []
|
67
|
+
|
68
|
+
rubyforge_project: translated-attr
|
69
|
+
rubygems_version: 1.3.5
|
70
|
+
signing_key:
|
71
|
+
specification_version: 3
|
72
|
+
summary: ActiveRecord/Rails simple translatable attributes
|
73
|
+
test_files:
|
74
|
+
- spec/integration_spec.rb
|
75
|
+
- spec/spec_helper.rb
|
76
|
+
- spec/translated_attributes_spec.rb
|
77
|
+
- spec/models.rb
|