translatable 0.2.4 → 0.3.0

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/.travis.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 1.8.7
4
3
  - 1.9.2
5
- - 1.9.3
4
+ - 1.9.3
5
+ bundler_args: --without=debug
data/Gemfile CHANGED
@@ -1,21 +1,25 @@
1
1
  source "http://rubygems.org"
2
2
 
3
3
  gem "activerecord"
4
+ gem "activesupport", :require => false
4
5
  gem "i18n"
5
6
 
7
+ group :debug do
8
+ gem "simplecov", ">= 0.6.0", :platform => :ruby_19
9
+ gem "debugger", "~> 1.1.3", :platform => :ruby_19
10
+ gem "ruby-debug", :platform => :ruby_18
11
+ end
12
+
6
13
  group :development do
7
14
  gem "yard"
8
15
  gem "jeweler", ">= 1.6.0"
9
- gem "bundler", ">= 1.0.0"
10
- gem "simplecov", ">= 0.6.0", :platform => :ruby_19
11
- gem "rcov", ">= 1.0.0", :platform => :ruby_18
16
+ gem "bundler", ">= 1.2.0"
12
17
  end
13
18
 
14
19
  group :test do
15
- gem "test-spec"
16
-
17
- gem "debugger", "~> 1.1.3", :platform => :ruby_19
18
- gem 'ruby-debug', :platform => :ruby_18
20
+ gem "rails"
21
+ gem "test-unit"
22
+ gem "shoulda"
19
23
 
20
24
  gem "database_cleaner"
21
25
  gem "sqlite3"
data/Gemfile.lock CHANGED
@@ -1,6 +1,20 @@
1
1
  GEM
2
2
  remote: http://rubygems.org/
3
3
  specs:
4
+ actionmailer (3.1.8)
5
+ actionpack (= 3.1.8)
6
+ mail (~> 2.3.3)
7
+ actionpack (3.1.8)
8
+ activemodel (= 3.1.8)
9
+ activesupport (= 3.1.8)
10
+ builder (~> 3.0.0)
11
+ erubis (~> 2.7.0)
12
+ i18n (~> 0.6)
13
+ rack (~> 1.3.6)
14
+ rack-cache (~> 1.2)
15
+ rack-mount (~> 0.8.2)
16
+ rack-test (~> 0.6.1)
17
+ sprockets (~> 2.0.4)
4
18
  activemodel (3.1.8)
5
19
  activesupport (= 3.1.8)
6
20
  builder (~> 3.0.0)
@@ -10,6 +24,9 @@ GEM
10
24
  activesupport (= 3.1.8)
11
25
  arel (~> 2.2.3)
12
26
  tzinfo (~> 0.3.29)
27
+ activeresource (3.1.8)
28
+ activemodel (= 3.1.8)
29
+ activesupport (= 3.1.8)
13
30
  activesupport (3.1.8)
14
31
  multi_json (>= 1.0, < 1.3)
15
32
  arel (2.2.3)
@@ -23,29 +40,78 @@ GEM
23
40
  debugger-linecache (1.1.2)
24
41
  debugger-ruby_core_source (>= 1.1.1)
25
42
  debugger-ruby_core_source (1.1.3)
43
+ erubis (2.7.0)
26
44
  git (1.2.5)
45
+ hike (1.2.1)
27
46
  i18n (0.6.0)
28
47
  jeweler (1.6.4)
29
48
  bundler (~> 1.0)
30
49
  git (>= 1.2.5)
31
50
  rake
51
+ json (1.7.5)
32
52
  linecache (0.46)
33
53
  rbx-require-relative (> 0.0.4)
54
+ mail (2.3.3)
55
+ i18n (>= 0.4.0)
56
+ mime-types (~> 1.16)
57
+ treetop (~> 1.4.8)
58
+ mime-types (1.19)
34
59
  multi_json (1.2.0)
60
+ polyglot (0.3.3)
61
+ rack (1.3.6)
62
+ rack-cache (1.2)
63
+ rack (>= 0.4)
64
+ rack-mount (0.8.3)
65
+ rack (>= 1.0.0)
66
+ rack-ssl (1.3.2)
67
+ rack
68
+ rack-test (0.6.2)
69
+ rack (>= 1.0)
70
+ rails (3.1.8)
71
+ actionmailer (= 3.1.8)
72
+ actionpack (= 3.1.8)
73
+ activerecord (= 3.1.8)
74
+ activeresource (= 3.1.8)
75
+ activesupport (= 3.1.8)
76
+ bundler (~> 1.0)
77
+ railties (= 3.1.8)
78
+ railties (3.1.8)
79
+ actionpack (= 3.1.8)
80
+ activesupport (= 3.1.8)
81
+ rack-ssl (~> 1.3.2)
82
+ rake (>= 0.8.7)
83
+ rdoc (~> 3.4)
84
+ thor (~> 0.14.6)
35
85
  rake (0.9.2.2)
36
86
  rbx-require-relative (0.0.9)
37
- rcov (1.0.0)
87
+ rdoc (3.12)
88
+ json (~> 1.4)
38
89
  ruby-debug (0.10.4)
39
90
  columnize (>= 0.1)
40
91
  ruby-debug-base (~> 0.10.4.0)
41
92
  ruby-debug-base (0.10.4)
42
93
  linecache (>= 0.3)
94
+ shoulda (3.3.2)
95
+ shoulda-context (~> 1.0.1)
96
+ shoulda-matchers (~> 1.4.1)
97
+ shoulda-context (1.0.1)
98
+ shoulda-matchers (1.4.1)
99
+ activesupport (>= 3.0.0)
43
100
  simplecov (0.6.4)
44
101
  multi_json (~> 1.0)
45
102
  simplecov-html (~> 0.5.3)
46
103
  simplecov-html (0.5.3)
104
+ sprockets (2.0.4)
105
+ hike (~> 1.2)
106
+ rack (~> 1.0)
107
+ tilt (~> 1.1, != 1.3.0)
47
108
  sqlite3 (1.3.6)
48
- test-spec (0.10.0)
109
+ test-unit (2.5.3)
110
+ thor (0.14.6)
111
+ tilt (1.3.3)
112
+ treetop (1.4.12)
113
+ polyglot
114
+ polyglot (>= 0.3.1)
49
115
  tzinfo (0.3.33)
50
116
  yard (0.6.8)
51
117
 
@@ -54,14 +120,16 @@ PLATFORMS
54
120
 
55
121
  DEPENDENCIES
56
122
  activerecord
57
- bundler (>= 1.0.0)
123
+ activesupport
124
+ bundler (>= 1.2.0)
58
125
  database_cleaner
59
126
  debugger (~> 1.1.3)
60
127
  i18n
61
128
  jeweler (>= 1.6.0)
62
- rcov (>= 1.0.0)
129
+ rails
63
130
  ruby-debug
131
+ shoulda
64
132
  simplecov (>= 0.6.0)
65
133
  sqlite3
66
- test-spec
134
+ test-unit
67
135
  yard
data/Rakefile CHANGED
@@ -28,12 +28,16 @@ Jeweler::RubygemsDotOrgTasks.new
28
28
  require 'rake/testtask'
29
29
  Rake::TestTask.new(:test) do |test|
30
30
  test.libs << 'lib' << 'test'
31
- test.pattern = 'test/**/test_*.rb'
31
+ test.pattern = 'test/**/*_test.rb'
32
32
  test.verbose = true
33
33
  end
34
34
 
35
35
  task :default => :test
36
36
 
37
- require 'yard'
38
- YARD::Rake::YardocTask.new
37
+ begin
38
+ require 'yard'
39
+ YARD::Rake::YardocTask.new
40
+ rescue LoadError
41
+ $stderr.puts "Skipping Yard tasks"
42
+ end
39
43
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.4
1
+ 0.3.0
@@ -0,0 +1,45 @@
1
+ require "translatable/generator_helper"
2
+
3
+ module Translatable
4
+ module Generators
5
+ class ModelGenerator < Rails::Generators::NamedBase
6
+ include Translatable::GeneratorHelper
7
+
8
+ desc "Creates ActiveRecord model and injects translatable block into it"
9
+
10
+ class_option :translated_model, :type => :string, :desc => "Defines the model responsible for translations"
11
+ class_option :origin, :type => :string, :desc => "Defines the association name for translation record that deals with origin"
12
+ class_option :locale, :type => :string, :desc => "Defines the column for translation record that keeps the locale"
13
+
14
+ def create_model
15
+ self.attributes = attrs
16
+ parse_attributes!
17
+ invoke "active_record:model", [class_name], {migration: true, timestamps: true} unless model_exists?
18
+ end
19
+
20
+ # all public methods in here will be run in order
21
+ def inject_translatable_block
22
+ inject_into_class model_path, class_name, generate_translatable_block
23
+ end
24
+
25
+ protected
26
+
27
+ def generate_translatable_block
28
+ block = " translatable do"
29
+ attributes.each do |attr|
30
+ block << "\n translatable :#{attr.name}, :presence => true#, :uniqueness => true"
31
+ end
32
+ block << (options[:translated_model].nil? ?
33
+ "\n #translatable_model 'Translated#{class_name}'" :
34
+ "\n translatable_model '#{options[:translated_model]}'")
35
+ block << (options[:origin].nil? ?
36
+ "\n #translatable_origin :#{singular_table_name}" :
37
+ "\n translatable_origin :#{options[:origin]}")
38
+ block << (options[:locale].nil? ?
39
+ "\n #translatable_locale :locale" :
40
+ "\n translatable_locale :#{options[:locale]}")
41
+ block << "\n end\n"
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,45 @@
1
+ require "translatable/generator_helper"
2
+
3
+ module Translatable
4
+ module Generators
5
+ class TranslationGenerator < Rails::Generators::NamedBase
6
+ include Translatable::GeneratorHelper
7
+
8
+ desc "Creates ActiveRecord for translation dealer"
9
+
10
+ class_option :prefix, :type => :string, :default => "translatable", :desc => "Specifies the prefix to used tof translation dealer (Default: translatable)"
11
+ class_option :origin, :type => :string, :default => "origin", :desc => "Specifies the association name to be use for origin (Default: origin)"
12
+ class_option :locale, :type => :string, :default => "locale", :desc => "Specifies the column to be use for locale (Default: locale)"
13
+
14
+ def create_model
15
+ self.attributes = attrs
16
+ parse_attributes!
17
+ invoke "active_record:model", [class_name] + attrs + ["#{options[:origin]}_id:integer:true", "#{options[:locale]}:string"], {migration: true, timestamps: true} unless model_exists?
18
+ end
19
+
20
+ # all public methods in here will be run in order
21
+ def inject_translatable_block
22
+ inject_into_class model_path, class_name, generate_translatable_block
23
+ end
24
+
25
+ protected
26
+
27
+ def generate_translatable_block
28
+ block = <<CONTENT
29
+ # This class deals purely with translations themselves. Hence, any edition of
30
+ # should be avoided.
31
+ # In later gem version its existance might not be necessary.
32
+ CONTENT
33
+ unless attributes.empty?
34
+ block << " attr_accessible :#{attributes.map(&:name).join(", :")}\n"
35
+ end
36
+ block << " #attr_protected :#{options[:origin]}_id, :#{options[:locale]}\n"
37
+ block
38
+ end
39
+
40
+ def file_name
41
+ "#{options[:prefix].downcase}_#{@file_name}"
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,254 @@
1
+ require 'active_record'
2
+ require 'i18n'
3
+
4
+ module Translatable
5
+ ###
6
+ # In order to made the model Translatable, an additional fields should
7
+ # should be added first to it. Here is an example of it might be implemented:
8
+ #
9
+ # Examples:
10
+ #
11
+ # class Author < ActiveRecord::Base
12
+ # validates :name, :presence => true
13
+ # end
14
+ #
15
+ # class TranslatableNews < ActiveRecord::Base #
16
+ # attr_accessible :title, :content
17
+ # end
18
+ #
19
+ # class News < ActiveRecord::Base
20
+ #
21
+ # belongs_to :author
22
+ #
23
+ # translatable do
24
+ # translatable :title, :presence => true, :uniqueness => true
25
+ # translatable :content, :presence => true
26
+ # translatable_model "TranslatedNews"
27
+ # translatable_origin :origin_id
28
+ # end
29
+ #
30
+ # attr_accessible :author_id, :author
31
+ # end
32
+ #
33
+ # An example of application:
34
+ #
35
+ # news = News.create :translations_attributes => [{title: "Resent News", content: "That is where the text goes", locale: "en"}]
36
+ # news.translations.create title: "Заголовок", content: "Содержание",locale: "ru"
37
+ #
38
+ # news.content
39
+ # # => "That is where the text goes"
40
+ #
41
+ # ::I18n.locale = "ru"
42
+ # news.content
43
+ # # => "Сюди идет текст"
44
+ #
45
+ # ::I18n.locale = "de"
46
+ # news.content
47
+ # # => nil
48
+ #
49
+ # ::I18n.locale = ::I18n.default_locale
50
+ # news.content
51
+ # # => "That is where the text goes"
52
+ #
53
+ module ActiveRecord
54
+
55
+ def translatable
56
+ extend Translatable::ActiveRecord::ClassMethods
57
+ include Translatable::ActiveRecord::InstanceMethods
58
+
59
+ translatable_define_hash
60
+ yield
61
+ translatable_register
62
+ end
63
+
64
+ module ClassMethods
65
+
66
+ protected
67
+
68
+ ###
69
+ # Fields that are translatable.
70
+ # Those fields should be defined in the original model including all the related params.
71
+ # Examples:
72
+ #
73
+ # translatable_property :title, String, required: true, unique: true
74
+ # translatable_property :content, Text
75
+ #
76
+ # NB! Will raise an error if there was no fields specified
77
+ #
78
+ def translatable *args
79
+ (@translatable[:properties] ||= []) << args
80
+ end
81
+
82
+ ###
83
+ # Defines model that will be treated as translation handler.
84
+ # Model can be defined as String, Symbol or Constant.
85
+ # Examples:
86
+ #
87
+ # translated_model TranslatedNews
88
+ # translated_model "TranslatedNews"
89
+ # translated_model :TranslatedNews
90
+ #
91
+ # Default: Translatable<ModelName>
92
+ #
93
+ def translatable_model model_name
94
+ @translatable[:model] = translatable_model_prepared model_name
95
+ end
96
+
97
+ ###
98
+ # Define the key that the translation will be used for belongs_to association,
99
+ # to communicate with original model
100
+ # Example:
101
+ #
102
+ # translatable_origin :news
103
+ #
104
+ # Default: :origin
105
+ #
106
+ def translatable_origin origin_key
107
+ @translatable[:origin] = translatable_origin_prepared origin_key
108
+ end
109
+
110
+ ###
111
+ # Will not register the attributes as accessible.
112
+ # IMPORTANT: Translatable block will be evaluated on the model after it
113
+ # was loaded, so it will modify certain thing on final version. Hence this thing is needed.
114
+ # Examples:
115
+ #
116
+ # translatable_attr_protected
117
+ #
118
+ # Default: false
119
+ #
120
+ def translatable_attr_protected
121
+ @translatable[:attr_accessible] = false
122
+ end
123
+
124
+ ###
125
+ # Will not register the attributes as accessible.
126
+ # IMPORTANT: Translatable block will be evaluated on the model after it
127
+ # was loaded, so it will modify certain thing on final version. Hence this thing is needed.
128
+ # Examples:
129
+ #
130
+ # translatable_attr_protected
131
+ #
132
+ # Default: false
133
+ #
134
+ def translatable_attr_accessible
135
+ @translatable[:attr_accessible] = true
136
+ end
137
+
138
+ ###
139
+ # Define the key that the translation will be used for belongs_to association,
140
+ # to communicate with original model
141
+ # Example:
142
+ #
143
+ # translatable_origin :language
144
+ #
145
+ # Default: :locale
146
+ #
147
+ def translatable_locale locale_attr
148
+ @translatable[:locale] = translatable_locale_prepared locale_attr
149
+ end
150
+
151
+ ###
152
+ # Returns Model as a constant that deals with translations
153
+ def translatable_model_prepared model_name = nil
154
+ model_constant = model_name
155
+ model_constant ||= "Translatable#{self.name}"
156
+ model_constant.to_s.constantize
157
+ end
158
+
159
+
160
+ def translatable_origin_prepared origin_key = nil
161
+ origin_key || "origin"
162
+ end
163
+
164
+ def translatable_locale_prepared locale = nil
165
+ locale || "locale"
166
+ end
167
+
168
+ ###
169
+ # Define hash that contains all the translations
170
+ def translatable_define_hash
171
+ @translatable = {}
172
+ end
173
+
174
+ ###
175
+ # Handles all the registring routine, defining methods,
176
+ # properties, and everything else
177
+ def translatable_register
178
+ raise ArgumentError.new("At least one property should be defined") if [nil, []].include?(@translatable[:properties])
179
+ [:model,:origin,:locale].each { |hash_key| @translatable[hash_key] ||= send "translatable_#{hash_key}_prepared" }
180
+
181
+ translatable_register_properties_for_origin
182
+ translatable_register_properties_for_translatable
183
+ end
184
+
185
+ ###
186
+ # Handle the routine to define all th required stuff on the original maodel
187
+ def translatable_register_properties_for_origin
188
+ has_many :translations, :class_name => @translatable[:model].name, :foreign_key => :"#{@translatable[:origin]}_id"
189
+ accepts_nested_attributes_for :translations
190
+ attr_accessible :translations_attributes
191
+
192
+ @translatable[:properties].each do |p|
193
+ accessible_as = (p.last.delete(:as) || p.first rescue p.first)
194
+ self.module_eval <<-RUBY, __FILE__, __LINE__ + 1
195
+ def #{accessible_as}
196
+ current_translation.try(:#{p.first})
197
+ end
198
+ RUBY
199
+ end
200
+
201
+ self.module_eval <<-RUBY, __FILE__, __LINE__ + 1
202
+ def translatable_set_current(locale = ::I18n.locale)
203
+ locale = locale.to_s
204
+ @current_translation = if translations.loaded?
205
+ translations.select { |t| t.send(:"#{@translatable[:locale]}") == locale }
206
+ else
207
+ translations.where(:"#{@translatable[:locale]}" => locale)
208
+ end.first
209
+ end
210
+ alias_method :set_current_translation, :translatable_set_current
211
+ RUBY
212
+ end
213
+
214
+ def translatable_register_properties_for_translatable
215
+ @translatable[:model].module_eval <<-RUBY, __FILE__, __LINE__ + 1
216
+ validates :#{@translatable[:locale]}, :presence => true
217
+ validates :#{@translatable[:locale]}, :format => { :with => /[a-z]{2}/}, :if => Proc.new {|record| !record.#{@translatable[:locale]}.blank? }
218
+ validates :#{@translatable[:locale]}, :uniqueness => { :scope => :#{@translatable[:origin]}_id }
219
+
220
+ belongs_to :#{@translatable[:origin]}, :class_name => "#{self.name}"
221
+ RUBY
222
+
223
+ unless @translatable[:attr_accessible].nil?
224
+ @translatable[:model].module_eval <<-RUBY, __FILE__, __LINE__ + 1
225
+ attr_#{!!@translatable[:attr_accessible] ? "accessible" : "protected" } :#{@translatable[:locale]}, :#{@translatable[:origin]}_id
226
+ RUBY
227
+ end
228
+
229
+ @translatable[:properties].each do |p|
230
+ if p.size > 1
231
+ @translatable[:model].module_eval <<-RUBY, __FILE__, __LINE__ + 1
232
+ validates :#{p.first}, #{p.last.inspect}
233
+ RUBY
234
+ end
235
+ end
236
+ end
237
+ end
238
+
239
+ module InstanceMethods
240
+ def current_translation
241
+ update_current_translation unless @translatable_locale
242
+ @current_translation
243
+ end
244
+
245
+ def update_current_translation
246
+ translatable_set_current(@translatable_locale = ::I18n.locale.to_s)
247
+ end
248
+
249
+ def other_translations
250
+ translations - [current_translation]
251
+ end
252
+ end
253
+ end
254
+ end
@@ -0,0 +1,6 @@
1
+ require 'rails/engine'
2
+
3
+ module Translatable
4
+ class Engine < ::Rails::Engine
5
+ end
6
+ end
@@ -0,0 +1,20 @@
1
+ module Translatable
2
+ module GeneratorHelper
3
+ def self.included(base)
4
+ base.class_eval do
5
+ attr_accessor :attributes
6
+ argument :attrs, type: :array, default: [], banner: "field[:type][:index] field[:type][:index]"
7
+ end
8
+ end
9
+
10
+ protected
11
+
12
+ def model_path
13
+ File.join(destination_root, 'app/models', class_path, "#{file_name}.rb")
14
+ end
15
+
16
+ def model_exists?
17
+ File.exists?(model_path)
18
+ end
19
+ end
20
+ end