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 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 backend
78
- with an observer, for example.
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
- make_it_armot! unless included_modules.include?(InstanceMethods)
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 :update_translations!
102
- after_destroy :remove_i18n_entries
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 update_translations!
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
- self.class.reload_armot!
149
+ reload_armot! if respond_to?(:"reload_armot!")
124
150
  armot_attributes.clear
125
151
  end
126
152
 
127
- def remove_i18n_entries
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
@@ -1,3 +1,3 @@
1
1
  module Armot
2
- VERSION = "0.3.1"
2
+ VERSION = "0.3.2"
3
3
  end
data/lib/armot.rb CHANGED
@@ -4,5 +4,7 @@ require 'armot/railtie' if defined?(Rails)
4
4
  module Armot
5
5
  mattr_accessor :token
6
6
  @@token = "aXqvyiainsvQWivbpo13/asf/tTG27cbASBFPOQ"
7
+
8
+ class DoubleDeclarationError < RuntimeError; end
7
9
  end
8
10
 
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
@@ -3,6 +3,7 @@ ActiveRecord::Schema.define(:version => 1) do
3
3
  t.string :title
4
4
  t.text :text
5
5
  t.string :header
6
+ t.string :type
6
7
  end
7
8
 
8
9
  create_table :products do |t|
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.1
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-19 00:00:00.000000000 Z
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