translatable 0.2.4 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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