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