├── .gitignore ├── .travis.yml ├── Gemfile ├── MIT-LICENSE ├── Rakefile ├── globalize-accessors.gemspec ├── lib ├── globalize-accessors.rb └── globalize-accessors │ └── version.rb ├── readme.md └── test ├── db ├── database.yml.example ├── database.yml.travis └── schema.rb ├── globalize_accessors_test.rb └── test_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | pkg/* 2 | *.gem 3 | .bundle 4 | nbproject 5 | *.sqlite3 6 | test/debug.log 7 | test/db/database.yml 8 | Gemfile.lock 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | sudo: false 3 | cache: bundler 4 | before_script: 5 | - "cp test/db/database.yml.travis test/db/database.yml" 6 | script: "bundle exec rake" 7 | rvm: 8 | - 2.2 9 | - 2.1 10 | - 2.0.0 11 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009, 2010, 2011, 2012, 2013 Tomek "Tomash" Stachewicz, 2 | Robert Pankowecki, Chris Salzberg 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | begin 3 | require 'bundler/setup' 4 | rescue LoadError 5 | puts 'You must `gem install bundler` and `bundle install` to run rake tasks' 6 | end 7 | 8 | Bundler::GemHelper.install_tasks 9 | require 'rdoc/task' 10 | require 'rake/testtask' 11 | require 'rake' 12 | 13 | desc 'Default: run unit tests.' 14 | task :default => :test 15 | 16 | desc 'Test globalize-accessors gem.' 17 | Rake::TestTask.new(:test) do |t| 18 | t.libs << 'lib' 19 | t.libs << 'test' 20 | t.pattern = 'test/**/*_test.rb' 21 | t.verbose = true 22 | end 23 | 24 | desc 'Generate documentation for globalize-accessors gem.' 25 | Rake::RDocTask.new(:rdoc) do |rdoc| 26 | rdoc.rdoc_dir = 'rdoc' 27 | rdoc.title = 'EasyGlobalizeAccessors' 28 | rdoc.options << '--line-numbers' << '--inline-source' 29 | rdoc.rdoc_files.include('README') 30 | rdoc.rdoc_files.include('lib/**/*.rb') 31 | end 32 | -------------------------------------------------------------------------------- /globalize-accessors.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require File.expand_path("../lib/globalize-accessors/version", __FILE__) 3 | 4 | Gem::Specification.new do |s| 5 | s.name = "globalize-accessors" 6 | s.version = Globalize::Accessors::VERSION 7 | s.platform = Gem::Platform::RUBY 8 | s.authors = ["Tomasz Stachewicz", "Wojciech Pietrzak", "Steve Verlinden", "Robert Pankowecki", "Chris Salzberg"] 9 | s.email = ["tomekrs@o2.pl", "steve.verlinden@gmail.com", "robert.pankowecki@gmail.com", "rpa@gavdi.com", "chrissalzberg@gmail.com"] 10 | s.homepage = "http://rubygems.org/gems/globalize-accessors" 11 | s.summary = "Define methods for accessing translated attributes" 12 | s.description = "Define methods for accessing translated attributes" 13 | 14 | s.required_rubygems_version = ">= 1.3" 15 | s.rubyforge_project = "globalize-accessors" 16 | 17 | s.add_dependency 'activesupport', '>= 4.2' 18 | s.add_dependency "globalize", ">= 5.0.0" 19 | 20 | s.add_development_dependency "sqlite3" 21 | s.add_development_dependency "rake" 22 | s.add_development_dependency "minitest", "~> 5.1" 23 | 24 | s.files = `git ls-files`.split("\n") 25 | s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact 26 | s.require_path = 'lib' 27 | end 28 | -------------------------------------------------------------------------------- /lib/globalize-accessors.rb: -------------------------------------------------------------------------------- 1 | require 'globalize' 2 | 3 | module Globalize::Accessors 4 | def globalize_accessors(options = {}) 5 | options.reverse_merge!(:locales => I18n.available_locales, :attributes => translated_attribute_names) 6 | class_attribute :globalize_locales, :globalize_attribute_names, :instance_writer => false 7 | 8 | self.globalize_locales = options[:locales] 9 | self.globalize_attribute_names = [] 10 | 11 | each_attribute_and_locale(options) do |attr_name, locale| 12 | define_accessors(attr_name, locale) 13 | end 14 | 15 | include InstanceMethods 16 | end 17 | 18 | def localized_attr_name_for(attr_name, locale) 19 | "#{attr_name}_#{locale.to_s.underscore}" 20 | end 21 | 22 | private 23 | 24 | def define_accessors(attr_name, locale) 25 | attribute("#{attr_name}_#{locale}", ::ActiveRecord::Type::Value.new) if ::ActiveRecord::VERSION::STRING >= "5.0" 26 | define_getter(attr_name, locale) 27 | define_setter(attr_name, locale) 28 | end 29 | 30 | def define_getter(attr_name, locale) 31 | define_method localized_attr_name_for(attr_name, locale) do 32 | globalize.stash.contains?(locale, attr_name) ? globalize.send(:fetch_stash, locale, attr_name) : globalize.send(:fetch_attribute, locale, attr_name) 33 | end 34 | end 35 | 36 | def define_setter(attr_name, locale) 37 | localized_attr_name = localized_attr_name_for(attr_name, locale) 38 | 39 | define_method :"#{localized_attr_name}=" do |value| 40 | attribute_will_change!(localized_attr_name) if value != send(localized_attr_name) 41 | write_attribute(attr_name, value, :locale => locale) 42 | translation_for(locale)[attr_name] = value 43 | end 44 | if respond_to?(:accessible_attributes) && accessible_attributes.include?(attr_name) 45 | attr_accessible :"#{localized_attr_name}" 46 | end 47 | self.globalize_attribute_names << localized_attr_name.to_sym 48 | end 49 | 50 | def each_attribute_and_locale(options) 51 | options[:attributes].each do |attr_name| 52 | options[:locales].each do |locale| 53 | yield attr_name, locale 54 | end 55 | end 56 | end 57 | 58 | module InstanceMethods 59 | def localized_attr_name_for(attr_name, locale) 60 | self.class.localized_attr_name_for(attr_name, locale) 61 | end 62 | end 63 | end 64 | 65 | ActiveSupport.on_load(:active_record) do 66 | ActiveRecord::Base.extend Globalize::Accessors 67 | end 68 | -------------------------------------------------------------------------------- /lib/globalize-accessors/version.rb: -------------------------------------------------------------------------------- 1 | module Globalize 2 | module Accessors 3 | VERSION = '0.3.0' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Globalize Accessors [![Build Status](https://travis-ci.org/globalize/globalize-accessors.png?branch=master)](https://travis-ci.org/globalize/globalize-accessors) 2 | 3 | ## Introduction 4 | 5 | Generator of accessor methods for models using Globalize. Use `globalize-accessors` with a list of translated fields you want easily access to and extra `locales` array listing locales for which you want the accessors to be generated. 6 | 7 | This way a single form can be used to edit given model fields with all anticipated translations. 8 | 9 | `globalize-accessors` is compatible with both Rails 3.x and Rails 4. 10 | 11 | 12 | ## Installation 13 | 14 | ````ruby 15 | gem install globalize-accessors 16 | ```` 17 | 18 | ## Example 19 | 20 | Definition like this: 21 | 22 | ````ruby 23 | class Product 24 | translates :title, :description 25 | globalize_accessors :locales => [:en, :pl], :attributes => [:title] 26 | end 27 | ```` 28 | 29 | Gives you access to methods: `title_pl`, `title_en`, `title_pl=`, `title_en=`. These work seamlessly with Globalize (not even touching the "core" `title`, `title=` methods used by Globalize itself). 30 | 31 | The `:locales` and `:attributes` options are optional. Their default values are: 32 | 33 | ````ruby 34 | :locales => I18n.available_locales 35 | :attributes => translated_attribute_names 36 | ```` 37 | 38 | Calling `globalize_accessors` with no options will therefore generate accessor methods for all translated fields and available languages. 39 | 40 | You can also get the accessor locales for a class with the `globalize_locales` method: 41 | 42 | ````ruby 43 | Product.globalize_locales # => [:en, :pl] 44 | ```` 45 | 46 | You can also get modified attribute names -- ideal for use with strong parameters -- with the `globalize_attribute_names` method: 47 | 48 | ````ruby 49 | Product.globalize_attribute_names # => [:title_en, :title_pl] 50 | ```` 51 | 52 | Example with strong parameters: 53 | 54 | ````ruby 55 | params.require(:product).permit(*Product.globalize_attribute_names) 56 | ```` 57 | 58 | If you need to permit non-translatable attributes as well, you could include them with: 59 | 60 | ````ruby 61 | permitted = Product.globalize_attribute_names + [:position] 62 | params.require(:product).permit(*permitted) 63 | ```` 64 | 65 | ## Always define accessors 66 | 67 | If you wish to always define accessors and don't want to call the `globalize_accessors` method in every class, you can extend `ActiveRecord::Base` with a module: 68 | 69 | ````ruby 70 | module TranslatesWithAccessors 71 | 72 | def translates(*params) 73 | options = params.dup.extract_options! 74 | options.reverse_merge!(:globalize_accessors => true) 75 | accessors = options.delete(:globalize_accessors) 76 | super 77 | globalize_accessors if accessors 78 | end 79 | 80 | end 81 | ```` 82 | 83 | ## Licence 84 | 85 | Copyright (c) 2009-2013 Tomek "Tomash" Stachewicz (http://tomash.wrug.eu), 86 | Robert Pankowecki (http://robert.pankowecki.pl), Chris Salzberg (http://dejimata.com) 87 | released under the MIT license 88 | -------------------------------------------------------------------------------- /test/db/database.yml.example: -------------------------------------------------------------------------------- 1 | sqlite3: 2 | adapter: sqlite3 3 | dbfile: globalize.sqlite3 4 | database: globalize.sqlite3 5 | sqlite3mem: 6 | adapter: sqlite3 7 | dbfile: ":memory:" 8 | database: globalize.sqlite3 9 | postgresql: 10 | adapter: postgresql 11 | username: postgres 12 | password: postgres 13 | database: globalize 14 | min_messages: ERROR 15 | mysql: 16 | adapter: mysql 17 | host: localhost 18 | username: root 19 | password: 20 | database: globalize 21 | 22 | -------------------------------------------------------------------------------- /test/db/database.yml.travis: -------------------------------------------------------------------------------- 1 | sqlite3: 2 | adapter: sqlite3 3 | dbfile: globalize.sqlite3 4 | database: globalize.sqlite3 5 | sqlite3mem: 6 | adapter: sqlite3 7 | dbfile: ":memory:" 8 | database: globalize.sqlite3 9 | postgresql: 10 | adapter: postgresql 11 | username: postgres 12 | password: postgres 13 | database: globalize 14 | min_messages: ERROR 15 | mysql: 16 | adapter: mysql 17 | host: localhost 18 | username: root 19 | password: 20 | database: globalize 21 | 22 | -------------------------------------------------------------------------------- /test/db/schema.rb: -------------------------------------------------------------------------------- 1 | ActiveRecord::Schema.define(:version => 0) do 2 | 3 | create_table "units", :force => true do |t| 4 | t.integer "capacity" 5 | end 6 | 7 | create_table "unit_translations", :force => true do |t| 8 | t.references :unit 9 | t.string "name" 10 | t.string "title" 11 | t.string "locale" 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /test/globalize_accessors_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class GlobalizeAccessorsTest < ActiveSupport::TestCase 4 | 5 | class Unit < ActiveRecord::Base 6 | translates :name, :title 7 | globalize_accessors 8 | end 9 | 10 | class UnitTranslatedWithOptions < ActiveRecord::Base 11 | self.table_name = :units 12 | translates :name 13 | globalize_accessors :locales => [:pl], :attributes => [:name] 14 | end 15 | 16 | class UnitInherited < UnitTranslatedWithOptions 17 | end 18 | 19 | class UnitInheritedWithOptions < ActiveRecord::Base 20 | self.table_name = :units 21 | translates :color 22 | globalize_accessors :locales => [:de], :attributes => [:color] 23 | end 24 | 25 | class UnitWithDashedLocales < ActiveRecord::Base 26 | self.table_name = :units 27 | translates :name 28 | globalize_accessors :locales => [:"pt-BR", :"en-AU"], :attributes => [:name] 29 | end 30 | 31 | class UnitWithoutAccessors < ActiveRecord::Base 32 | self.table_name = :units 33 | end 34 | 35 | setup do 36 | assert_equal :en, I18n.locale 37 | end 38 | 39 | test "return localized attribute names" do 40 | u = UnitWithDashedLocales.new 41 | 42 | assert_equal "name_en", u.localized_attr_name_for(:name, :en) 43 | assert_equal "name_pt_br", u.localized_attr_name_for(:name, :"pt-BR") 44 | assert_equal "name_zh_cn", u.class.localized_attr_name_for(:name, :"zh-CN") 45 | end 46 | 47 | test "read and write on new object" do 48 | u = Unit.new(:name_en => "Name en", :title_pl => "Title pl") 49 | 50 | assert_equal "Name en", u.name 51 | assert_equal "Name en", u.name_en 52 | assert_equal "Title pl", u.title_pl 53 | 54 | assert_nil u.name_pl 55 | assert_nil u.title_en 56 | end 57 | 58 | test "build translations for a new object" do 59 | u = Unit.new(:name_en => "Name en", :title_pl => "Title pl") 60 | assert_equal 2, u.translations.size # size of the collection 61 | assert_equal 0, u.translations.count # count from database 62 | end 63 | 64 | test "write on new object and read on saved" do 65 | u = Unit.create!(:name_en => "Name en", :title_pl => "Title pl") 66 | 67 | assert_equal "Name en", u.name 68 | assert_equal "Name en", u.name_en 69 | assert_equal "Title pl", u.title_pl 70 | 71 | assert_nil u.name_pl 72 | assert_nil u.title_en 73 | end 74 | 75 | test "updates translations for existing object" do 76 | u = Unit.create!(:name_en => "Name en", :title_pl => "Title pl") 77 | u.name_en = "New name en" 78 | assert_equal "New name en", u.translations.find {|t| t.locale == :en }.name 79 | assert_equal "Title pl", u.translations.find {|t| t.locale == :pl }.title 80 | assert_equal 2, u.translations.count 81 | end 82 | 83 | test "read on existing object" do 84 | u = Unit.create!(:name_en => "Name en", :title_pl => "Title pl") 85 | u = Unit.find(u.id) 86 | 87 | assert_equal "Name en", u.name 88 | assert_equal "Name en", u.name_en 89 | assert_equal "Title pl", u.title_pl 90 | 91 | assert_nil u.name_pl 92 | assert_nil u.title_en 93 | end 94 | 95 | test "read and write on existing object" do 96 | u = Unit.create!(:name_en => "Name en", :title_pl => "Title pl") 97 | u = Unit.find(u.id) 98 | 99 | u.name_pl = "Name pl" 100 | u.name_en = "Name en2" 101 | u.save! 102 | 103 | assert_equal "Name en2", u.name 104 | assert_equal "Name en2", u.name_en 105 | assert_equal "Name pl", u.name_pl 106 | assert_equal "Title pl", u.title_pl 107 | 108 | assert_nil u.title_en 109 | end 110 | 111 | test "read and write on class with options" do 112 | u = UnitTranslatedWithOptions.new() 113 | 114 | assert u.respond_to?(:name_pl) 115 | assert u.respond_to?(:name_pl=) 116 | 117 | assert ! u.respond_to?(:name_en) 118 | assert ! u.respond_to?(:name_en=) 119 | 120 | u.name = "Name en" 121 | u.name_pl = "Name pl" 122 | 123 | assert_equal "Name en", u.name 124 | assert_equal "Name pl", u.name_pl 125 | end 126 | 127 | test "globalize locales on class without locales specified in options" do 128 | assert_equal [:en, :pl], Unit.globalize_locales 129 | end 130 | 131 | test "globalize locales on class with locales specified in options" do 132 | assert_equal [:pl], UnitTranslatedWithOptions.globalize_locales 133 | end 134 | 135 | test "read and write on class with dashed locales" do 136 | u = UnitWithDashedLocales.new() 137 | 138 | assert u.respond_to?(:name_pt_br) 139 | assert u.respond_to?(:name_pt_br=) 140 | 141 | assert u.respond_to?(:name_en_au) 142 | assert u.respond_to?(:name_en_au=) 143 | 144 | u.name = "Name en" 145 | u.name_pt_br = "Name pt-BR" 146 | u.name_en_au = "Name en-AU" 147 | 148 | assert_equal "Name en", u.name 149 | assert_equal "Name pt-BR", u.name_pt_br 150 | assert_equal "Name en-AU", u.name_en_au 151 | end 152 | 153 | test "read when fallbacks present" do 154 | u = Unit.create!(:name_en => "Name en", :title_pl => "Title pl") 155 | u = Unit.find(u.id) 156 | def u.globalize_fallbacks(locale) 157 | locale == :pl ? [:pl, :en] : [:en, :pl] 158 | end 159 | 160 | assert_equal "Name en", u.name 161 | assert_nil u.title_en 162 | end 163 | 164 | test "globalize attribute names on class without attributes specified in options" do 165 | assert_equal [:name_en, :name_pl, :title_en, :title_pl], Unit.globalize_attribute_names 166 | end 167 | 168 | test "globalize attribute names on class with attributes specified in options" do 169 | assert_equal [:name_pl], UnitTranslatedWithOptions.globalize_attribute_names 170 | end 171 | 172 | test "inherit globalize locales and attributes" do 173 | assert_equal [:name_pl], UnitInherited.globalize_attribute_names 174 | assert_equal [:pl], UnitInherited.globalize_locales 175 | end 176 | 177 | test "overwrite inherited globalize locales and attributes" do 178 | assert_equal [:color_de], UnitInheritedWithOptions.globalize_attribute_names 179 | assert_equal [:de], UnitInheritedWithOptions.globalize_locales 180 | end 181 | 182 | test "instance cannot set globalize locales or attributes" do 183 | assert_raise(NoMethodError) { Unit.new.globalize_attribute_names = [:name] } 184 | assert_raise(NoMethodError) { Unit.new.globalize_locales = [:en, :de] } 185 | end 186 | 187 | test "instance without accessors" do 188 | refute UnitWithoutAccessors.new.respond_to?(:localized_attr_name_for) 189 | end 190 | 191 | test "dirty checking" do 192 | u = Unit.create!(:name_en => "Name en") 193 | u.name_en = "New name en" 194 | assert u.changed.include?("name_en") 195 | assert ["Name en", "New name en"], u.changes["name_en"] 196 | u.save! 197 | assert ! u.changed.include?("name_en") 198 | end 199 | end 200 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'i18n' 2 | I18n.available_locales = [:en, :pl] 3 | 4 | require 'minitest/autorun' 5 | require 'active_support' 6 | require 'active_support/test_case' 7 | require 'logger' 8 | 9 | require 'globalize-accessors' 10 | 11 | plugin_test_dir = File.dirname(__FILE__) 12 | 13 | ActiveRecord::Base.logger = Logger.new(File.join(plugin_test_dir, "debug.log")) 14 | ActiveRecord::Base.configurations = YAML::load( IO.read( File.join(plugin_test_dir, 'db', 'database.yml') ) ) 15 | ActiveRecord::Base.establish_connection(:sqlite3mem) 16 | ActiveRecord::Migration.verbose = false 17 | ActiveSupport::TestCase.test_order = :sorted 18 | load(File.join(plugin_test_dir, "db", "schema.rb")) 19 | --------------------------------------------------------------------------------