armot 0.3.1 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +126 -2
- data/lib/armot/active_record_extensions.rb +45 -11
- data/lib/armot/version.rb +1 -1
- data/lib/armot.rb +2 -0
- data/test/armot_test.rb +161 -0
- data/test/schema.rb +1 -0
- data/test/test_helper.rb +22 -0
- metadata +2 -2
data/README.md
CHANGED
@@ -72,10 +72,134 @@ Your translated model will have different contents for each locale transparently
|
|
72
72
|
I18n.locale = :en
|
73
73
|
car.name #=> A car
|
74
74
|
|
75
|
+
Armot also provides an implementation for the `_changed?` method, so you can
|
76
|
+
normally operate as if it was a standard active_record attribute.
|
77
|
+
|
78
|
+
car = Car.create :name => "Ford"
|
79
|
+
car.name = "Honda"
|
80
|
+
|
81
|
+
car.name_chaned? #=> true
|
82
|
+
car.save!
|
83
|
+
car.name_changed? #=> false
|
84
|
+
|
85
|
+
|
86
|
+
Reloading caches
|
87
|
+
----------------
|
75
88
|
|
76
89
|
Be aware that armot doesn't take care of any cache expiration. If you're using
|
77
|
-
Memoize with I18n ActiveRecord backend you must remember to reload the
|
78
|
-
|
90
|
+
Memoize with I18n ActiveRecord backend you must remember to reload the
|
91
|
+
backend.
|
92
|
+
|
93
|
+
Armot provides the `reload_armot!` callback which is called on the
|
94
|
+
instance after performing the changes. For example:
|
95
|
+
|
96
|
+
class Post < ActiveRecord::Base
|
97
|
+
# ...
|
98
|
+
|
99
|
+
def reload_armot!
|
100
|
+
I18n.backend.reload!
|
101
|
+
Rails.cache.clear
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
Find_by dynamic methods
|
107
|
+
-----------------------
|
108
|
+
|
109
|
+
Armot also writes the dynamic `find_by` and `find_by!` methods in order to
|
110
|
+
fetch a record from database given a specific content for an armotized
|
111
|
+
attribute. It will *only* look for translations in the current language, and
|
112
|
+
it will not perform any kind of fallback mechanism. For example:
|
113
|
+
|
114
|
+
I18n.locale = :en
|
115
|
+
post = Post.create :title => "Title in english"
|
116
|
+
|
117
|
+
Post.find_by_title "Title in english" #=> <post>
|
118
|
+
Post.find_by_title "Not found" #=> nil
|
119
|
+
Post.find_by_title! "Not found" #=> ActiveRecord::RecordNotFound raised
|
120
|
+
|
121
|
+
I18n.locale = :es
|
122
|
+
Post.find_by_title "Title in english" #=> nil
|
123
|
+
|
124
|
+
|
125
|
+
Fallbacks
|
126
|
+
---------
|
127
|
+
|
128
|
+
When reading the contents from an instance (not find_by methods) Armot works
|
129
|
+
with your current I18n setup for fallbacks, just as if you were performing a
|
130
|
+
I18n.t lookup.
|
131
|
+
|
132
|
+
|
133
|
+
Modularized implementation
|
134
|
+
--------------------------
|
135
|
+
|
136
|
+
All the methods Armot provides are implemented in modules injected in your
|
137
|
+
class (ArmotInstanceMethods and ArmotClassMethods). This means that you can
|
138
|
+
override them in order to include custom logic. For instance if you are
|
139
|
+
translating the `slug_url` attribute on your Post model, maybe you have a
|
140
|
+
setter like this:
|
141
|
+
|
142
|
+
class Post
|
143
|
+
def slug_url=(value)
|
144
|
+
self[:slug_url] = ConvertToSafeUrl(value)
|
145
|
+
end
|
146
|
+
|
147
|
+
def to_param
|
148
|
+
slug_url
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
Now if you want to armotize this slug_url attribute and still perform this
|
153
|
+
logic, you could do that:
|
154
|
+
|
155
|
+
class Post
|
156
|
+
def slug_url=(value)
|
157
|
+
super(ConvertToSafeUrl(value))
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
Armotized_attributes
|
162
|
+
--------------------
|
163
|
+
|
164
|
+
You can get a list of all the currently armotized attributes on a class by
|
165
|
+
calling:
|
166
|
+
|
167
|
+
Post.armotized_attributes #=> [:title, :text]
|
168
|
+
|
169
|
+
|
170
|
+
Defining localized accessors
|
171
|
+
----------------------------
|
172
|
+
|
173
|
+
There are situations in which it's useful for you to have localized accessors for
|
174
|
+
your armotized attributes, so you don't need to change the current language in
|
175
|
+
order to get the value for an attribute in that language, for instance:
|
176
|
+
|
177
|
+
I18n.locale = :en
|
178
|
+
post = Post.create :title => "ENG title"
|
179
|
+
I18n.locale = :es
|
180
|
+
post.title = "SP title"
|
181
|
+
post.save!
|
182
|
+
|
183
|
+
I18n.locale = :en
|
184
|
+
post.title_en #=> "ENG title"
|
185
|
+
post.title_es #=> "SP title"
|
186
|
+
|
187
|
+
Armot provides now an automatic way to define these methods:
|
188
|
+
|
189
|
+
class Post
|
190
|
+
define_localized_accessors_for :title
|
191
|
+
end
|
192
|
+
|
193
|
+
This will make available the `title_en` and `title_en=` methods (also in every
|
194
|
+
other languages that may be available, as returned from
|
195
|
+
`I18n.available_locales`). You can also set up these methods for all your
|
196
|
+
armotized attributes using the `:all` keyword:
|
197
|
+
|
198
|
+
|
199
|
+
class Post
|
200
|
+
define_localized_accessors_for :all
|
201
|
+
end
|
202
|
+
|
79
203
|
|
80
204
|
|
81
205
|
Development with armot
|
@@ -2,11 +2,41 @@ module Armot
|
|
2
2
|
module ActiveRecordExtensions
|
3
3
|
module ClassMethods
|
4
4
|
def armotize(*attributes)
|
5
|
-
|
5
|
+
if included_modules.include?(InstanceMethods)
|
6
|
+
raise DoubleDeclarationError, "armotize can only be called once in #{self}"
|
7
|
+
end
|
8
|
+
|
9
|
+
make_it_armot!
|
6
10
|
|
7
11
|
instance_mixin = Module.new
|
8
12
|
class_mixin = Module.new
|
9
13
|
|
14
|
+
class_mixin.module_eval do
|
15
|
+
define_method :armotized_attributes do
|
16
|
+
attributes.map(&:to_sym)
|
17
|
+
end
|
18
|
+
|
19
|
+
define_method :define_localized_accessors_for do |*localizable_attributes|
|
20
|
+
localizable_attributes = armotized_attributes if localizable_attributes == [:all]
|
21
|
+
|
22
|
+
localizable_attributes.each do |attr|
|
23
|
+
I18n.available_locales.each do |locale|
|
24
|
+
define_method "#{attr}_#{locale}" do
|
25
|
+
armot_wrap_in_locale(locale) do
|
26
|
+
send attr
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
define_method "#{attr}_#{locale}=" do |value|
|
31
|
+
armot_wrap_in_locale(locale) do
|
32
|
+
send "#{attr}=", value
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
10
40
|
attributes.each do |attribute|
|
11
41
|
class_mixin.module_eval do
|
12
42
|
define_method :"find_by_#{attribute}" do |value|
|
@@ -52,10 +82,6 @@ module Armot
|
|
52
82
|
res ? res : raise(ActiveRecord::RecordNotFound)
|
53
83
|
end
|
54
84
|
end
|
55
|
-
|
56
|
-
# To implement by armotized classes
|
57
|
-
define_method :"reload_armot!" do
|
58
|
-
end
|
59
85
|
end
|
60
86
|
|
61
87
|
instance_mixin.module_eval do
|
@@ -98,19 +124,19 @@ module Armot
|
|
98
124
|
def make_it_armot!
|
99
125
|
include InstanceMethods
|
100
126
|
|
101
|
-
after_save :
|
102
|
-
after_destroy :
|
127
|
+
after_save :armot_update_translations!
|
128
|
+
after_destroy :armot_remove_i18n_entries
|
103
129
|
end
|
104
130
|
end
|
105
131
|
|
106
132
|
module InstanceMethods
|
133
|
+
private
|
107
134
|
|
108
|
-
private
|
109
135
|
def armot_attributes
|
110
136
|
@armot_attributes ||= Hash.new { |hash, key| hash[key] = {} }
|
111
137
|
end
|
112
138
|
|
113
|
-
def
|
139
|
+
def armot_update_translations!
|
114
140
|
return if armot_attributes.empty?
|
115
141
|
|
116
142
|
armot_attributes.each do |locale, attributes|
|
@@ -120,14 +146,22 @@ module Armot
|
|
120
146
|
end
|
121
147
|
end
|
122
148
|
|
123
|
-
|
149
|
+
reload_armot! if respond_to?(:"reload_armot!")
|
124
150
|
armot_attributes.clear
|
125
151
|
end
|
126
152
|
|
127
|
-
def
|
153
|
+
def armot_remove_i18n_entries
|
128
154
|
t = I18n::Backend::ActiveRecord::Translation.arel_table
|
129
155
|
I18n::Backend::ActiveRecord::Translation.delete_all(t[:key].matches("armot.#{self.class.to_s.underscore.pluralize}%_#{id}"))
|
130
156
|
end
|
157
|
+
|
158
|
+
def armot_wrap_in_locale(locale)
|
159
|
+
aux = I18n.locale
|
160
|
+
I18n.locale = locale.to_sym
|
161
|
+
res = yield
|
162
|
+
I18n.locale = aux
|
163
|
+
res
|
164
|
+
end
|
131
165
|
end
|
132
166
|
end
|
133
167
|
end
|
data/lib/armot/version.rb
CHANGED
data/lib/armot.rb
CHANGED
data/test/armot_test.rb
CHANGED
@@ -10,6 +10,8 @@ def to_method_name(name)
|
|
10
10
|
end
|
11
11
|
|
12
12
|
class ArmotTest < ActiveSupport::TestCase
|
13
|
+
include ActiveSupport::Testing::Pending
|
14
|
+
|
13
15
|
def setup
|
14
16
|
setup_db
|
15
17
|
I18n.locale = I18n.default_locale = :en
|
@@ -326,4 +328,163 @@ class ArmotTest < ActiveSupport::TestCase
|
|
326
328
|
post[:title] = "Hello world"
|
327
329
|
assert_equal "Hello world", post.title
|
328
330
|
end
|
331
|
+
|
332
|
+
test "should fetch all translations with only one query with multiple armotized parameters" do
|
333
|
+
pending "should be implemented in the active_record specific gem" do
|
334
|
+
post = Post.first
|
335
|
+
post.text = "English text"
|
336
|
+
post.save!
|
337
|
+
|
338
|
+
res = count_query_reads_for("I18n::Backend::ActiveRecord::Translation") do
|
339
|
+
a = Post.first
|
340
|
+
a.text
|
341
|
+
a.title
|
342
|
+
end
|
343
|
+
|
344
|
+
assert_equal 1, res
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
test "should not save the record if it has not changed" do
|
349
|
+
pending "should be implemented in the active_record specific gem" do
|
350
|
+
post = Post.last
|
351
|
+
post.title = "ENG title"
|
352
|
+
post.text = "English text"
|
353
|
+
post.save!
|
354
|
+
|
355
|
+
res = count_query_updates_for("I18n::Backend::ActiveRecord::Translation") do
|
356
|
+
a = Post.first
|
357
|
+
a.title = "ENG Second version"
|
358
|
+
a.text = "English text"
|
359
|
+
a.save!
|
360
|
+
end
|
361
|
+
|
362
|
+
assert_equal 1, res
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
test ".armotized_attributes" do
|
367
|
+
assert_equal [:title, :text], Post.armotized_attributes
|
368
|
+
end
|
369
|
+
|
370
|
+
test "multiple armotize calls raise an error" do
|
371
|
+
assert_raise Armot::DoubleDeclarationError do
|
372
|
+
class FooBar < ActiveRecord::Base
|
373
|
+
armotize :foo
|
374
|
+
armotize :bar
|
375
|
+
end
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
test "the setter method shold return the assigned value" do
|
380
|
+
post = Post.last
|
381
|
+
res = (post.title = "Foo bar title")
|
382
|
+
assert_equal "Foo bar title", res
|
383
|
+
end
|
384
|
+
|
385
|
+
test "an armotized class should not have armotized accessors by default" do
|
386
|
+
post = Post.last
|
387
|
+
assert_equal false, post.respond_to?(:title_en)
|
388
|
+
end
|
389
|
+
|
390
|
+
test ".define_localized_accessors_for should define localized accessors for the current locales" do
|
391
|
+
post = Post.last
|
392
|
+
I18n.locale = :es
|
393
|
+
post.title = "SP titulo"
|
394
|
+
post.save! # Just save here to make I18n.available_locales aware of both :es and :en
|
395
|
+
|
396
|
+
assert_equal [:es, :en].sort, I18n.available_locales.sort
|
397
|
+
|
398
|
+
class FuzzBar < Post
|
399
|
+
define_localized_accessors_for :title
|
400
|
+
end
|
401
|
+
|
402
|
+
foo = FuzzBar.new
|
403
|
+
foo.title = "Cucamonga"
|
404
|
+
foo.save!
|
405
|
+
assert_equal true, foo.respond_to?(:title_en)
|
406
|
+
assert_equal true, foo.respond_to?(:"title_en=")
|
407
|
+
assert_equal true, foo.respond_to?(:title_es)
|
408
|
+
assert_equal true, foo.respond_to?(:"title_es=")
|
409
|
+
end
|
410
|
+
|
411
|
+
test "localized getters behaviour" do
|
412
|
+
class FuzzBar < Post
|
413
|
+
define_localized_accessors_for :title
|
414
|
+
end
|
415
|
+
|
416
|
+
foo = FuzzBar.new
|
417
|
+
foo.title = "EN - title"
|
418
|
+
I18n.locale = :es
|
419
|
+
foo.title = "ES - titulo"
|
420
|
+
foo.save!
|
421
|
+
|
422
|
+
I18n.locale = :en
|
423
|
+
assert_equal "EN - title", foo.title_en
|
424
|
+
assert_equal "ES - titulo", foo.title_es
|
425
|
+
end
|
426
|
+
|
427
|
+
test "localized setters behaviour" do
|
428
|
+
class FuzzBar < Post
|
429
|
+
define_localized_accessors_for :title
|
430
|
+
end
|
431
|
+
|
432
|
+
foo = FuzzBar.new
|
433
|
+
foo.title = "EN - title"
|
434
|
+
I18n.locale = :es
|
435
|
+
foo.title = "ES - titulo"
|
436
|
+
foo.save!
|
437
|
+
|
438
|
+
I18n.locale = :en
|
439
|
+
res = (foo.title_es = "Segundo titulo")
|
440
|
+
assert_equal "Segundo titulo", foo.title_es
|
441
|
+
assert_equal "Segundo titulo", res
|
442
|
+
end
|
443
|
+
|
444
|
+
test "after using localized accessors the I18n.locale should remain the same" do
|
445
|
+
class FuzzBar < Post
|
446
|
+
define_localized_accessors_for :title
|
447
|
+
end
|
448
|
+
|
449
|
+
foo = FuzzBar.new
|
450
|
+
foo.title = "EN - title"
|
451
|
+
I18n.locale = :es
|
452
|
+
foo.title = "ES - titulo"
|
453
|
+
foo.save!
|
454
|
+
|
455
|
+
I18n.locale = :klingon
|
456
|
+
foo.title_es = "Segundo titulo"
|
457
|
+
foo.title_en
|
458
|
+
assert_equal :klingon, I18n.locale
|
459
|
+
end
|
460
|
+
|
461
|
+
test "localized accessors should work for more than one attribute" do
|
462
|
+
class FuzzBar < Post
|
463
|
+
define_localized_accessors_for :title, :text
|
464
|
+
end
|
465
|
+
|
466
|
+
foo = FuzzBar.new
|
467
|
+
foo.title = "EN - title"
|
468
|
+
foo.text = "EN - body text"
|
469
|
+
foo.save!
|
470
|
+
assert_equal true, foo.respond_to?(:title_en)
|
471
|
+
assert_equal true, foo.respond_to?(:"title_en=")
|
472
|
+
assert_equal true, foo.respond_to?(:text_en)
|
473
|
+
assert_equal true, foo.respond_to?(:"text_en=")
|
474
|
+
end
|
475
|
+
|
476
|
+
test ".define_localized_accessors_for :all" do
|
477
|
+
class FuzzBar < Post
|
478
|
+
define_localized_accessors_for :all
|
479
|
+
end
|
480
|
+
|
481
|
+
foo = FuzzBar.new
|
482
|
+
foo.title = "EN - title"
|
483
|
+
foo.text = "EN - body text"
|
484
|
+
foo.save!
|
485
|
+
assert_equal true, foo.respond_to?(:title_en)
|
486
|
+
assert_equal true, foo.respond_to?(:"title_en=")
|
487
|
+
assert_equal true, foo.respond_to?(:text_en)
|
488
|
+
assert_equal true, foo.respond_to?(:"text_en=")
|
489
|
+
end
|
329
490
|
end
|
data/test/schema.rb
CHANGED
data/test/test_helper.rb
CHANGED
@@ -47,3 +47,25 @@ end
|
|
47
47
|
# Puret translation model to test migration process
|
48
48
|
class PostTranslation < ActiveRecord::Base
|
49
49
|
end
|
50
|
+
|
51
|
+
def count_query_reads_for(clazz)
|
52
|
+
old = ActiveRecord::Base.logger
|
53
|
+
log_stream = StringIO.new
|
54
|
+
logger = Logger.new(log_stream)
|
55
|
+
ActiveRecord::Base.logger = logger
|
56
|
+
yield
|
57
|
+
ActiveRecord::Base.logger = old
|
58
|
+
logger.close
|
59
|
+
log_stream.string.scan(/#{clazz} Load/).size
|
60
|
+
end
|
61
|
+
|
62
|
+
def count_query_updates_for(clazz)
|
63
|
+
old = ActiveRecord::Base.logger
|
64
|
+
log_stream = StringIO.new
|
65
|
+
logger = Logger.new(log_stream)
|
66
|
+
ActiveRecord::Base.logger = logger
|
67
|
+
yield
|
68
|
+
ActiveRecord::Base.logger = old
|
69
|
+
logger.close
|
70
|
+
log_stream.string.scan(/UPDATE \"#{clazz.constantize.table_name}\"/).size
|
71
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: armot
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-06-
|
12
|
+
date: 2012-06-24 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: i18n-active_record
|