translated_attr 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,7 @@
1
+ == 1.0.1
2
+
3
+ * Removed unused gems in Gemfile
4
+
5
+ == 1.0
6
+
7
+ * First release
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ # A sample Gemfile
2
+ source "http://rubygems.org"
3
+
4
+ group :development do
5
+ gem "jeweler"
6
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,19 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ git (1.2.5)
5
+ jeweler (1.8.3)
6
+ bundler (~> 1.0)
7
+ git (>= 1.2.5)
8
+ rake
9
+ rdoc
10
+ json (1.6.6)
11
+ rake (0.9.2.2)
12
+ rdoc (3.12)
13
+ json (~> 1.4)
14
+
15
+ PLATFORMS
16
+ ruby
17
+
18
+ DEPENDENCIES
19
+ jeweler
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Matteo Canato
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,45 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rdoc/task'
4
+ require File.join(File.dirname(__FILE__), 'lib', 'translated_attr', 'version')
5
+
6
+ =begin
7
+ desc 'Default: run unit tests.'
8
+ task :default => :test
9
+
10
+ desc 'Test the translated_attr plugin.'
11
+ Rake::TestTask.new(:test) do |t|
12
+ t.libs << 'lib'
13
+ t.libs << 'test'
14
+ t.pattern = Dir.glob('test/**/*_test.rb')
15
+ t.verbose = true
16
+ end
17
+ =end
18
+
19
+ desc 'Generate documentation for the translated_attr plugin.'
20
+ RDoc::Task.new(:rdoc) do |rdoc|
21
+ rdoc.rdoc_dir = 'rdoc'
22
+ rdoc.title = 'TranslatedAttr'
23
+ rdoc.options << '--line-numbers' << '--inline-source'
24
+ rdoc.rdoc_files.include('README.rdoc')
25
+ #rdoc.rdoc_files.include('lib/**/*.rb')
26
+ end
27
+
28
+ begin
29
+ require 'jeweler'
30
+ Jeweler::Tasks.new do |s|
31
+ s.name = "translated_attr"
32
+ s.email = "mcanato@gmail.com"
33
+ s.summary = "A minimal translation library for translating database values for Rails 3.x"
34
+ s.homepage = "http://github.com/mcanato/translated_attr"
35
+ s.description = "A minimal translation library for translating database values for Rails 3.x"
36
+ s.authors = ['Matteo Canato']
37
+ s.version = "1.0.1"
38
+ #s.files = FileList["[A-Z]*(.rdoc)", "{generators,lib}/**/*", "init.rb"]
39
+ end
40
+
41
+ Jeweler::GemcutterTasks.new
42
+ rescue LoadError
43
+ puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install jeweler"
44
+ end
45
+
data/TODO.rdoc ADDED
@@ -0,0 +1,13 @@
1
+ == General
2
+
3
+ * formtastic support
4
+ * automatic detection of simple_form/formtastic and creation of the translation field partial with the right helper name
5
+ * helpers for fields_for attribute
6
+ * configurable fallback chain via :fallback => :default or :fallback => true
7
+ * include options for default_scope via config :include => true
8
+ * inclusion of locale.yml for validations errors
9
+
10
+
11
+ == Documentation
12
+
13
+ * documentation for generator rollbacks
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.0
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'translated_attr'
@@ -0,0 +1,4 @@
1
+ require 'translated_attr/active_record_extensions'
2
+
3
+ module TranslatedAttr
4
+ end
@@ -0,0 +1,232 @@
1
+ module TranslatedAttr
2
+
3
+ module Validators
4
+ # Presence Validators
5
+ class TranslationsPresenceValidator < ActiveModel::EachValidator
6
+ def validate_each(record, attribute, value)
7
+ translations_present = record.translations.map{ |t| t.locale.try(:to_sym) }
8
+ I18n.available_locales.each do |locale|
9
+ unless translations_present.include?(locale)
10
+ record.errors[attribute] <<
11
+ I18n.t('translated_attr.errors.translations_presence',
12
+ :attr => I18n.t("activerecord.attributes.#{record.class.name.downcase}.name"),
13
+ :lang => locale)
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ # Uniqueness validator
20
+ class TranslationsUniqValidator < ActiveModel::EachValidator
21
+ def validate_each(record, attribute, value)
22
+ translations_present = record.translations.map{ |t| t.locale.try(:to_sym) }
23
+ # Removes duplicate elements from self.
24
+ # Returns nil if no changes are made (that is, no duplicates are found).
25
+ if translations_present.uniq!
26
+ record.errors[attribute] << I18n.t('translated_attr.errors.translations_uniq')
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ module ActiveRecordExtensions
33
+ module ClassMethods
34
+ # Configure translation model dependency.
35
+ # Eg:
36
+ # class PostTranslation < ActiveRecord::Base
37
+ # translations_for :post
38
+ # end
39
+ def translations_for(model)
40
+ belongs_to model
41
+
42
+ validates :locale,
43
+ :presence => true,
44
+ :uniqueness => { :scope => "#{model}_id" },
45
+ :inclusion => I18n.available_locales.map{ |l| l.to_s }
46
+
47
+ # dynamic class methods
48
+ (class << self; self; end).class_eval do
49
+ define_method "attribute_names_for_translation" do
50
+ attribute_names - ["id", "#{model}_id", "locale", "created_at", "updated_at"]
51
+ end
52
+ end
53
+ end
54
+
55
+ # Configure translated attributes.
56
+ # Eg:
57
+ # class Post < ActiveRecord::Base
58
+ # translated_attr :title, :description
59
+ # end
60
+ def translated_attr(*attributes)
61
+ make_it_translated! unless included_modules.include?(InstanceMethods)
62
+
63
+ attributes.each do |attribute|
64
+ # dynamic validations
65
+ # TODO forse va sul metodo translations_for...
66
+ validates attribute, :translation_presence => true, :translation_uniq => true
67
+
68
+ #dynamic finders
69
+ (class << self; self; end).class_eval do
70
+ define_method "find_by_#{attribute}" do |value|
71
+ self.send("find_all_by_#{attribute}".to_sym, value).first
72
+ end
73
+
74
+ define_method "find_all_by_#{attribute}" do |value|
75
+ joins(:translations).
76
+ where("#{self.to_s.tableize.singularize}_translations.locale" => "#{I18n.locale}",
77
+ "#{self.to_s.tableize.singularize}_translations.#{attribute}" => "#{value}").
78
+ readonly(false)
79
+ end
80
+
81
+ define_method "find_or_new_by_#{attribute}" do |value|
82
+ self.send("find_by_#{attribute}".to_sym, value) || self.new { |r| r.send("#{attribute}=", value) }
83
+ end
84
+ end
85
+
86
+ # this make possible to specify getter and setter methods per locale,
87
+ # eg: given title attribute you can use getter
88
+ # as: title_en or title_it and setter as title_en= and title_it=
89
+ I18n.available_locales.each do |locale|
90
+
91
+ define_method "#{attribute}_#{locale}=" do |value|
92
+ set_attribute(attribute, value, locale)
93
+ end
94
+
95
+ define_method "#{attribute}_#{locale}" do
96
+ return localized_attributes[locale][attribute] if localized_attributes[locale][attribute]
97
+ return if new_record?
98
+ translations.where(:locale => "#{locale}").first.send(attribute.to_sym) rescue nil
99
+ end
100
+
101
+ # extension to the above dynamic finders
102
+ (class << self; self; end).class_eval do
103
+ define_method "find_by_#{attribute}_#{locale}" do |value|
104
+ self.send("find_all_by_#{attribute}_#{locale}".to_sym, value).first
105
+ end
106
+
107
+ define_method "find_all_by_#{attribute}_#{locale}" do |value|
108
+ joins(:translations).
109
+ where("#{self.to_s.tableize.singularize}_translations.locale" => "#{locale}",
110
+ "#{self.to_s.tableize.singularize}_translations.#{attribute}" => "#{value}").
111
+ readonly(false)
112
+ end
113
+
114
+ define_method "find_or_new_by_#{attribute}_#{locale}" do |value|
115
+ self.send("find_by_#{attribute}_#{locale}".to_sym, value) ||
116
+ self.new { |r| r.send("#{attribute}_#{locale}=", value) }
117
+ end
118
+ end
119
+ end
120
+
121
+ # attribute setter
122
+ define_method "#{attribute}=" do |value|
123
+ set_attribute(attribute, value)
124
+ end
125
+
126
+ # attribute getter
127
+ define_method attribute do
128
+ # return previously setted attributes if present
129
+ return localized_attributes[I18n.locale][attribute] if localized_attributes[I18n.locale][attribute]
130
+ return if new_record?
131
+
132
+ # Lookup chain:
133
+ # if translation not present in current locale,
134
+ # use default locale, if present.
135
+ # Otherwise use first translation
136
+ translation = translations.detect { |t| t.locale.to_sym == I18n.locale && t[attribute] } ||
137
+ translations.detect { |t| t.locale.to_sym == translations_default_locale && t[attribute] } ||
138
+ translations.first
139
+
140
+ translation ? translation[attribute] : nil
141
+ end
142
+
143
+ define_method "#{attribute}_before_type_cast" do
144
+ self.send(attribute)
145
+ end
146
+
147
+ define_method "modified?" do
148
+ if valid? # force the update_translations! method
149
+ translations.map_by_changed?.any? || changed?
150
+ end
151
+ end
152
+ end
153
+ end
154
+
155
+ private
156
+
157
+ # Configure model
158
+ def make_it_translated!
159
+ include InstanceMethods
160
+
161
+ has_many :translations,
162
+ :class_name => "#{self.to_s}Translation",
163
+ :dependent => :destroy,
164
+ :order => "created_at DESC"
165
+
166
+ before_validation :update_translations!
167
+
168
+ validates_associated :translations
169
+
170
+ accepts_nested_attributes_for :translations
171
+ end
172
+ end
173
+
174
+ module InstanceMethods
175
+
176
+ def set_attribute(attribute, value, locale = I18n.locale)
177
+ localized_attributes[locale][attribute] = value
178
+ end
179
+
180
+ def find_or_create_translation(locale)
181
+ locale = locale.to_s
182
+ (find_translation(locale) || self.translations.new).tap do |t|
183
+ t.locale = locale
184
+ end
185
+ end
186
+
187
+ def all_translations
188
+ t = I18n.available_locales.map do |locale|
189
+ [locale, find_or_create_translation(locale)]
190
+ end
191
+ ActiveSupport::OrderedHash[t]
192
+ end
193
+
194
+ def find_translation(locale)
195
+ locale = locale.to_s
196
+ translations.detect { |t| t.locale == locale }
197
+ end
198
+
199
+ def translations_default_locale
200
+ return default_locale.to_sym if respond_to?(:default_locale)
201
+ return self.class.default_locale.to_sym if self.class.respond_to?(:default_locale)
202
+ I18n.default_locale
203
+ end
204
+
205
+ # attributes are stored in @localized_attributes instance variable via setter
206
+ def localized_attributes
207
+ @localized_attributes ||= Hash.new { |hash, key| hash[key] = {} }
208
+ end
209
+
210
+ # called before validations
211
+ def update_translations!
212
+ return if @localized_attributes.blank?
213
+ @localized_attributes.each do |locale, attributes|
214
+ translation = find_translation(locale)
215
+ if translation
216
+ attributes.each { |attribute, value| translation.send("#{attribute}=", value) }
217
+ else
218
+ translations.build(attributes.merge(:locale => locale.to_s))
219
+ end
220
+ end
221
+ end
222
+
223
+ end
224
+ end
225
+ end
226
+
227
+ ActiveRecord::Base.extend TranslatedAttr::ActiveRecordExtensions::ClassMethods
228
+
229
+ # Compatibility with ActiveModel validates method which matches option keys to their validator class
230
+ # Used here in 'translated_attr' method
231
+ ActiveModel::Validations::TranslationPresenceValidator = TranslatedAttr::Validators::TranslationsPresenceValidator
232
+ ActiveModel::Validations::TranslationUniqValidator = TranslatedAttr::Validators::TranslationsUniqValidator
@@ -0,0 +1,3 @@
1
+ module TranslatedAttr
2
+ VERSION = "1.0.1".freeze
3
+ end
@@ -0,0 +1,50 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "translated_attr"
8
+ s.version = "1.0.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Matteo Canato"]
12
+ s.date = "2012-04-27"
13
+ s.description = "A minimal translation library for translating database values for Rails 3.x"
14
+ s.email = "mcanato@gmail.com"
15
+ s.extra_rdoc_files = [
16
+ "README.rdoc"
17
+ ]
18
+ s.files = [
19
+ "CHANGELOG.rdoc",
20
+ "Gemfile",
21
+ "Gemfile.lock",
22
+ "MIT-LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "TODO.rdoc",
26
+ "VERSION",
27
+ "init.rb",
28
+ "lib/translated_attr.rb",
29
+ "lib/translated_attr/active_record_extensions.rb",
30
+ "lib/translated_attr/version.rb",
31
+ "translated_attr.gemspec"
32
+ ]
33
+ s.homepage = "http://github.com/mcanato/translated_attr"
34
+ s.require_paths = ["lib"]
35
+ s.rubygems_version = "1.8.23"
36
+ s.summary = "A minimal translation library for translating database values for Rails 3.x"
37
+
38
+ if s.respond_to? :specification_version then
39
+ s.specification_version = 3
40
+
41
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
42
+ s.add_development_dependency(%q<jeweler>, [">= 0"])
43
+ else
44
+ s.add_dependency(%q<jeweler>, [">= 0"])
45
+ end
46
+ else
47
+ s.add_dependency(%q<jeweler>, [">= 0"])
48
+ end
49
+ end
50
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: translated_attr
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -11,38 +11,6 @@ bindir: bin
11
11
  cert_chain: []
12
12
  date: 2012-04-27 00:00:00.000000000 Z
13
13
  dependencies:
14
- - !ruby/object:Gem::Dependency
15
- name: rails
16
- requirement: !ruby/object:Gem::Requirement
17
- none: false
18
- requirements:
19
- - - ! '>='
20
- - !ruby/object:Gem::Version
21
- version: '0'
22
- type: :runtime
23
- prerelease: false
24
- version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
- requirements:
27
- - - ! '>='
28
- - !ruby/object:Gem::Version
29
- version: '0'
30
- - !ruby/object:Gem::Dependency
31
- name: sqlite3
32
- requirement: !ruby/object:Gem::Requirement
33
- none: false
34
- requirements:
35
- - - ! '>='
36
- - !ruby/object:Gem::Version
37
- version: '0'
38
- type: :runtime
39
- prerelease: false
40
- version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
- requirements:
43
- - - ! '>='
44
- - !ruby/object:Gem::Version
45
- version: '0'
46
14
  - !ruby/object:Gem::Dependency
47
15
  name: jeweler
48
16
  requirement: !ruby/object:Gem::Requirement
@@ -67,7 +35,19 @@ extensions: []
67
35
  extra_rdoc_files:
68
36
  - README.rdoc
69
37
  files:
38
+ - CHANGELOG.rdoc
39
+ - Gemfile
40
+ - Gemfile.lock
41
+ - MIT-LICENSE
70
42
  - README.rdoc
43
+ - Rakefile
44
+ - TODO.rdoc
45
+ - VERSION
46
+ - init.rb
47
+ - lib/translated_attr.rb
48
+ - lib/translated_attr/active_record_extensions.rb
49
+ - lib/translated_attr/version.rb
50
+ - translated_attr.gemspec
71
51
  homepage: http://github.com/mcanato/translated_attr
72
52
  licenses: []
73
53
  post_install_message: