translated_attr 1.0.0 → 1.0.1

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