Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Bump to version 1.0.0

  • Loading branch information...
commit 1a33fd60a131da3fa91d4090cf7a79bb96d469a9 1 parent 75cadb3
@mcanato authored
View
3  CHANGELOG.rdoc
@@ -0,0 +1,3 @@
+== 1.0
+
+* First release
View
9 Gemfile
@@ -0,0 +1,9 @@
+# A sample Gemfile
+source "http://rubygems.org"
+
+group :development do
+ gem "jeweler"
+end
+
+gem "rails"
+gem 'sqlite3'
View
94 Gemfile.lock
@@ -0,0 +1,94 @@
+GEM
+ remote: http://rubygems.org/
+ specs:
+ actionmailer (3.2.3)
+ actionpack (= 3.2.3)
+ mail (~> 2.4.4)
+ actionpack (3.2.3)
+ activemodel (= 3.2.3)
+ activesupport (= 3.2.3)
+ builder (~> 3.0.0)
+ erubis (~> 2.7.0)
+ journey (~> 1.0.1)
+ rack (~> 1.4.0)
+ rack-cache (~> 1.2)
+ rack-test (~> 0.6.1)
+ sprockets (~> 2.1.2)
+ activemodel (3.2.3)
+ activesupport (= 3.2.3)
+ builder (~> 3.0.0)
+ activerecord (3.2.3)
+ activemodel (= 3.2.3)
+ activesupport (= 3.2.3)
+ arel (~> 3.0.2)
+ tzinfo (~> 0.3.29)
+ activeresource (3.2.3)
+ activemodel (= 3.2.3)
+ activesupport (= 3.2.3)
+ activesupport (3.2.3)
+ i18n (~> 0.6)
+ multi_json (~> 1.0)
+ arel (3.0.2)
+ builder (3.0.0)
+ erubis (2.7.0)
+ git (1.2.5)
+ hike (1.2.1)
+ i18n (0.6.0)
+ jeweler (1.8.3)
+ bundler (~> 1.0)
+ git (>= 1.2.5)
+ rake
+ rdoc
+ journey (1.0.3)
+ json (1.6.6)
+ mail (2.4.4)
+ i18n (>= 0.4.0)
+ mime-types (~> 1.16)
+ treetop (~> 1.4.8)
+ mime-types (1.18)
+ multi_json (1.3.2)
+ polyglot (0.3.3)
+ rack (1.4.1)
+ rack-cache (1.2)
+ rack (>= 0.4)
+ rack-ssl (1.3.2)
+ rack
+ rack-test (0.6.1)
+ rack (>= 1.0)
+ rails (3.2.3)
+ actionmailer (= 3.2.3)
+ actionpack (= 3.2.3)
+ activerecord (= 3.2.3)
+ activeresource (= 3.2.3)
+ activesupport (= 3.2.3)
+ bundler (~> 1.0)
+ railties (= 3.2.3)
+ railties (3.2.3)
+ actionpack (= 3.2.3)
+ activesupport (= 3.2.3)
+ rack-ssl (~> 1.3.2)
+ rake (>= 0.8.7)
+ rdoc (~> 3.4)
+ thor (~> 0.14.6)
+ rake (0.9.2.2)
+ rdoc (3.12)
+ json (~> 1.4)
+ sprockets (2.1.3)
+ hike (~> 1.2)
+ rack (~> 1.0)
+ tilt (~> 1.1, != 1.3.0)
+ sqlite3 (1.3.6)
+ thor (0.14.6)
+ tilt (1.3.3)
+ treetop (1.4.10)
+ polyglot
+ polyglot (>= 0.3.1)
+ tzinfo (0.3.33)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ jeweler
+ rails
+ sqlite3
View
20 MIT-LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2012 Matteo Canato
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
View
45 Rakefile
@@ -0,0 +1,45 @@
+require 'rake'
+require 'rake/testtask'
+require 'rdoc/task'
+require File.join(File.dirname(__FILE__), 'lib', 'translated_attr', 'version')
+
+=begin
+desc 'Default: run unit tests.'
+task :default => :test
+
+desc 'Test the translated_attr plugin.'
+Rake::TestTask.new(:test) do |t|
+ t.libs << 'lib'
+ t.libs << 'test'
+ t.pattern = Dir.glob('test/**/*_test.rb')
+ t.verbose = true
+end
+=end
+
+desc 'Generate documentation for the translated_attr plugin.'
+RDoc::Task.new(:rdoc) do |rdoc|
+ rdoc.rdoc_dir = 'rdoc'
+ rdoc.title = 'TranslatedAttr'
+ rdoc.options << '--line-numbers' << '--inline-source'
+ rdoc.rdoc_files.include('README.rdoc')
+ #rdoc.rdoc_files.include('lib/**/*.rb')
+end
+
+begin
+ require 'jeweler'
+ Jeweler::Tasks.new do |s|
+ s.name = "translated_attr"
+ s.email = "mcanato@gmail.com"
+ s.summary = "A minimal translation library for translating database values for Rails 3.x"
+ s.homepage = "http://github.com/mcanato/translated_attr"
+ s.description = "A minimal translation library for translating database values for Rails 3.x"
+ s.authors = ['Matteo Canato']
+ s.version = "1.0.0"
+ #s.files = FileList["[A-Z]*(.rdoc)", "{generators,lib}/**/*", "init.rb"]
+ end
+
+ Jeweler::GemcutterTasks.new
+rescue LoadError
+ puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install jeweler"
+end
+
View
13 TODO.rdoc
@@ -0,0 +1,13 @@
+== General
+
+* formtastic support
+* automatic detection of simple_form/formtastic and creation of the translation field partial with the right helper name
+* helpers for fields_for attribute
+* configurable fallback chain via :fallback => :default or :fallback => true
+* include options for default_scope via config :include => true
+* inclusion of locale.yml for validations errors
+
+
+== Documentation
+
+* documentation for generator rollbacks
View
1  VERSION
@@ -0,0 +1 @@
+0.0.0
View
1  init.rb
@@ -0,0 +1 @@
+require 'translated_attr'
View
4 lib/translated_attr.rb
@@ -0,0 +1,4 @@
+require 'translated_attr/active_record_extensions'
+
+module TranslatedAttr
+end
View
232 lib/translated_attr/active_record_extensions.rb
@@ -0,0 +1,232 @@
+module TranslatedAttr
+
+ module Validators
+ # Presence Validators
+ class TranslationsPresenceValidator < ActiveModel::EachValidator
+ def validate_each(record, attribute, value)
+ translations_present = record.translations.map{ |t| t.locale.try(:to_sym) }
+ I18n.available_locales.each do |locale|
+ unless translations_present.include?(locale)
+ record.errors[attribute] <<
+ I18n.t('translated_attr.errors.translations_presence',
+ :attr => I18n.t("activerecord.attributes.#{record.class.name.downcase}.name"),
+ :lang => locale)
+ end
+ end
+ end
+ end
+
+ # Uniqueness validator
+ class TranslationsUniqValidator < ActiveModel::EachValidator
+ def validate_each(record, attribute, value)
+ translations_present = record.translations.map{ |t| t.locale.try(:to_sym) }
+ # Removes duplicate elements from self.
+ # Returns nil if no changes are made (that is, no duplicates are found).
+ if translations_present.uniq!
+ record.errors[attribute] << I18n.t('translated_attr.errors.translations_uniq')
+ end
+ end
+ end
+ end
+
+ module ActiveRecordExtensions
+ module ClassMethods
+ # Configure translation model dependency.
+ # Eg:
+ # class PostTranslation < ActiveRecord::Base
+ # translations_for :post
+ # end
+ def translations_for(model)
+ belongs_to model
+
+ validates :locale,
+ :presence => true,
+ :uniqueness => { :scope => "#{model}_id" },
+ :inclusion => I18n.available_locales.map{ |l| l.to_s }
+
+ # dynamic class methods
+ (class << self; self; end).class_eval do
+ define_method "attribute_names_for_translation" do
+ attribute_names - ["id", "#{model}_id", "locale", "created_at", "updated_at"]
+ end
+ end
+ end
+
+ # Configure translated attributes.
+ # Eg:
+ # class Post < ActiveRecord::Base
+ # translated_attr :title, :description
+ # end
+ def translated_attr(*attributes)
+ make_it_translated! unless included_modules.include?(InstanceMethods)
+
+ attributes.each do |attribute|
+ # dynamic validations
+ # TODO forse va sul metodo translations_for...
+ validates attribute, :translation_presence => true, :translation_uniq => true
+
+ #dynamic finders
+ (class << self; self; end).class_eval do
+ define_method "find_by_#{attribute}" do |value|
+ self.send("find_all_by_#{attribute}".to_sym, value).first
+ end
+
+ define_method "find_all_by_#{attribute}" do |value|
+ joins(:translations).
+ where("#{self.to_s.tableize.singularize}_translations.locale" => "#{I18n.locale}",
+ "#{self.to_s.tableize.singularize}_translations.#{attribute}" => "#{value}").
+ readonly(false)
+ end
+
+ define_method "find_or_new_by_#{attribute}" do |value|
+ self.send("find_by_#{attribute}".to_sym, value) || self.new { |r| r.send("#{attribute}=", value) }
+ end
+ end
+
+ # this make possible to specify getter and setter methods per locale,
+ # eg: given title attribute you can use getter
+ # as: title_en or title_it and setter as title_en= and title_it=
+ I18n.available_locales.each do |locale|
+
+ define_method "#{attribute}_#{locale}=" do |value|
+ set_attribute(attribute, value, locale)
+ end
+
+ define_method "#{attribute}_#{locale}" do
+ return localized_attributes[locale][attribute] if localized_attributes[locale][attribute]
+ return if new_record?
+ translations.where(:locale => "#{locale}").first.send(attribute.to_sym) rescue nil
+ end
+
+ # extension to the above dynamic finders
+ (class << self; self; end).class_eval do
+ define_method "find_by_#{attribute}_#{locale}" do |value|
+ self.send("find_all_by_#{attribute}_#{locale}".to_sym, value).first
+ end
+
+ define_method "find_all_by_#{attribute}_#{locale}" do |value|
+ joins(:translations).
+ where("#{self.to_s.tableize.singularize}_translations.locale" => "#{locale}",
+ "#{self.to_s.tableize.singularize}_translations.#{attribute}" => "#{value}").
+ readonly(false)
+ end
+
+ define_method "find_or_new_by_#{attribute}_#{locale}" do |value|
+ self.send("find_by_#{attribute}_#{locale}".to_sym, value) ||
+ self.new { |r| r.send("#{attribute}_#{locale}=", value) }
+ end
+ end
+ end
+
+ # attribute setter
+ define_method "#{attribute}=" do |value|
+ set_attribute(attribute, value)
+ end
+
+ # attribute getter
+ define_method attribute do
+ # return previously setted attributes if present
+ return localized_attributes[I18n.locale][attribute] if localized_attributes[I18n.locale][attribute]
+ return if new_record?
+
+ # Lookup chain:
+ # if translation not present in current locale,
+ # use default locale, if present.
+ # Otherwise use first translation
+ translation = translations.detect { |t| t.locale.to_sym == I18n.locale && t[attribute] } ||
+ translations.detect { |t| t.locale.to_sym == translations_default_locale && t[attribute] } ||
+ translations.first
+
+ translation ? translation[attribute] : nil
+ end
+
+ define_method "#{attribute}_before_type_cast" do
+ self.send(attribute)
+ end
+
+ define_method "modified?" do
+ if valid? # force the update_translations! method
+ translations.map_by_changed?.any? || changed?
+ end
+ end
+ end
+ end
+
+ private
+
+ # Configure model
+ def make_it_translated!
+ include InstanceMethods
+
+ has_many :translations,
+ :class_name => "#{self.to_s}Translation",
+ :dependent => :destroy,
+ :order => "created_at DESC"
+
+ before_validation :update_translations!
+
+ validates_associated :translations
+
+ accepts_nested_attributes_for :translations
+ end
+ end
+
+ module InstanceMethods
+
+ def set_attribute(attribute, value, locale = I18n.locale)
+ localized_attributes[locale][attribute] = value
+ end
+
+ def find_or_create_translation(locale)
+ locale = locale.to_s
+ (find_translation(locale) || self.translations.new).tap do |t|
+ t.locale = locale
+ end
+ end
+
+ def all_translations
+ t = I18n.available_locales.map do |locale|
+ [locale, find_or_create_translation(locale)]
+ end
+ ActiveSupport::OrderedHash[t]
+ end
+
+ def find_translation(locale)
+ locale = locale.to_s
+ translations.detect { |t| t.locale == locale }
+ end
+
+ def translations_default_locale
+ return default_locale.to_sym if respond_to?(:default_locale)
+ return self.class.default_locale.to_sym if self.class.respond_to?(:default_locale)
+ I18n.default_locale
+ end
+
+ # attributes are stored in @localized_attributes instance variable via setter
+ def localized_attributes
+ @localized_attributes ||= Hash.new { |hash, key| hash[key] = {} }
+ end
+
+ # called before validations
+ def update_translations!
+ return if @localized_attributes.blank?
+ @localized_attributes.each do |locale, attributes|
+ translation = find_translation(locale)
+ if translation
+ attributes.each { |attribute, value| translation.send("#{attribute}=", value) }
+ else
+ translations.build(attributes.merge(:locale => locale.to_s))
+ end
+ end
+ end
+
+ end
+ end
+end
+
+ActiveRecord::Base.extend TranslatedAttr::ActiveRecordExtensions::ClassMethods
+
+# Compatibility with ActiveModel validates method which matches option keys to their validator class
+# Used here in 'translated_attr' method
+ActiveModel::Validations::TranslationPresenceValidator = TranslatedAttr::Validators::TranslationsPresenceValidator
+ActiveModel::Validations::TranslationUniqValidator = TranslatedAttr::Validators::TranslationsUniqValidator
View
3  lib/translated_attr/version.rb
@@ -0,0 +1,3 @@
+module TranslatedAttr
+ VERSION = "1.0.0".freeze
+end
View
41 translated_attr.gemspec
@@ -0,0 +1,41 @@
+# Generated by jeweler
+# DO NOT EDIT THIS FILE DIRECTLY
+# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
+# -*- encoding: utf-8 -*-
+
+Gem::Specification.new do |s|
+ s.name = "translated_attr"
+ s.version = "1.0.0"
+
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
+ s.authors = ["Matteo Canato"]
+ s.date = "2012-04-27"
+ s.description = "A minimal translation library for translating database values for Rails 3.x"
+ s.email = "mcanato@gmail.com"
+ s.extra_rdoc_files = [
+ "README.rdoc"
+ ]
+ s.homepage = "http://github.com/mcanato/translated_attr"
+ s.require_paths = ["lib"]
+ s.rubygems_version = "1.8.11"
+ s.summary = "A minimal translation library for translating database values for Rails 3.x"
+
+ if s.respond_to? :specification_version then
+ s.specification_version = 3
+
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
+ s.add_runtime_dependency(%q<rails>, [">= 0"])
+ s.add_runtime_dependency(%q<sqlite3>, [">= 0"])
+ s.add_development_dependency(%q<jeweler>, [">= 0"])
+ else
+ s.add_dependency(%q<rails>, [">= 0"])
+ s.add_dependency(%q<sqlite3>, [">= 0"])
+ s.add_dependency(%q<jeweler>, [">= 0"])
+ end
+ else
+ s.add_dependency(%q<rails>, [">= 0"])
+ s.add_dependency(%q<sqlite3>, [">= 0"])
+ s.add_dependency(%q<jeweler>, [">= 0"])
+ end
+end
+
Please sign in to comment.
Something went wrong with that request. Please try again.