armot 0.3.1 → 0.3.2
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/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
|