localizable_model 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a2cfa395a30db8062f472845636f6fbfe118c7d5
4
+ data.tar.gz: 8d6e6c2b743b69648ef92990260e18de313c6672
5
+ SHA512:
6
+ metadata.gz: bb2536b98ca7556e2fcbdfe26faadfd13b8912fd9e9499f2a4858fa3be2e8f1a289a57aaa59ca0128ca9982a54099017941b8b50d3371c402ed1fc4ea29badb3
7
+ data.tar.gz: 35a202be39a802512a60f138acbd56a23f74de8d8118a205fbb8aedbdc9ca7d9a518bf8dd91538d9d957a3f99a281db13afe41f209e77e3ca2459251dbae5a56
@@ -0,0 +1,23 @@
1
+ # LocalizableModel
2
+
3
+ [![Build Status](https://travis-ci.org/kord-as/localizable_model.svg?branch=master)](https://travis-ci.org/kord-as/localizable_model) [![Code Climate](https://codeclimate.com/github/kord-as/localizable_model/badges/gpa.svg)](https://codeclimate.com/github/kord-as/localizable_model) [![Test Coverage](https://codeclimate.com/github/kord-as/localizable_model/badges/coverage.svg)](https://codeclimate.com/github/kord-as/localizable_model) [![Dependency Status](https://gemnasium.com/kord-as/localizable_model.svg)](https://gemnasium.com/kord-as/localizable_model)
4
+
5
+ ## Usage
6
+
7
+ Add the `localizable_model` gem to your Gemfile:
8
+
9
+ ``` ruby
10
+ gem "localizable_model"
11
+ ```
12
+
13
+ Generate the migration:
14
+
15
+ ``` shell
16
+ bin/rails g localizable_model:migration
17
+ ```
18
+
19
+
20
+ ## License
21
+
22
+ LocalizableModel is licensed under the
23
+ [MIT License](http://www.opensource.org/licenses/MIT).
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ APP_RAKEFILE = "spec/internal/Rakefile".freeze
5
+ load "rails/tasks/engine.rake"
6
+
7
+ RSpec::Core::RakeTask.new
8
+
9
+ task default: :spec
10
+ task test: :spec
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+
3
+ class Localization < ActiveRecord::Base
4
+ belongs_to :localizable, polymorphic: true
5
+
6
+ class << self
7
+ def locales
8
+ order("locale ASC").pluck("DISTINCT locale")
9
+ end
10
+
11
+ def names
12
+ order("name ASC").pluck("DISTINCT name")
13
+ end
14
+ end
15
+
16
+ def to_s
17
+ value || ""
18
+ end
19
+
20
+ delegate :empty?, to: :to_s
21
+
22
+ def translate(locale)
23
+ localizable.localizations.find_by(name: name, locale: locale)
24
+ end
25
+ end
@@ -0,0 +1,49 @@
1
+ # encoding: utf-8
2
+
3
+ load "localizable_model/active_record_extension.rb"
4
+ load "localizable_model/class_methods.rb"
5
+ load "localizable_model/configuration.rb"
6
+ load "localizable_model/engine.rb"
7
+ load "localizable_model/instance_methods.rb"
8
+ load "localizable_model/localizer.rb"
9
+ load "localizable_model/scope_extension.rb"
10
+ load "localizable_model/version.rb"
11
+
12
+ # = Localizable
13
+ #
14
+ # Localizable allows any model to have localized attributes.
15
+ #
16
+ # == Configuring the model
17
+ #
18
+ # class Page < ActiveRecord::Base
19
+ # localizable do
20
+ # attribute :name
21
+ # attribute :body
22
+ # end
23
+ # end
24
+ #
25
+ # == Usage
26
+ #
27
+ # page = Page.create(name: 'Hello', locale: 'en')
28
+ # page.name? # => true
29
+ # page.name.to_s # => 'Hello'
30
+ #
31
+ # The localized attributes always return an instance of Localization.
32
+ #
33
+ # To get a localized version of a page, call .localize on it:
34
+ #
35
+ # page = Page.first.localize('en')
36
+ #
37
+ # .localize also takes a block argument:
38
+ #
39
+ # page.localize('nb') do |p|
40
+ # p.locale # => 'nb'
41
+ # end
42
+ # page.locale # => 'en'
43
+ #
44
+ # Multiple locales can be updated at the same time:
45
+ #
46
+ # page.name = {'en' => 'Hello', 'nb' => 'Hallo'}
47
+ #
48
+ module LocalizableModel
49
+ end
@@ -0,0 +1,37 @@
1
+ # encoding: utf-8
2
+
3
+ module LocalizableModel
4
+ # = LocalizableModel::ActiveRecordExtension
5
+ #
6
+ # Extends ActiveRecord::Base with the localizable setup method.
7
+ #
8
+ module ActiveRecordExtension
9
+ # Extends the model with Localizable features.
10
+ # It takes an optional block as argument, which yields an instance of
11
+ # LocalizableModel::Configuration.
12
+ #
13
+ # Example:
14
+ #
15
+ # class Page < ActiveRecord::Base
16
+ # localizable do
17
+ # attribute :name
18
+ # attribute :body
19
+ # end
20
+ # end
21
+ #
22
+ def localizable(&block)
23
+ unless is_a?(LocalizableModel::ClassMethods)
24
+ send :extend, LocalizableModel::ClassMethods
25
+ send :include, LocalizableModel::InstanceMethods
26
+ has_many(:localizations,
27
+ as: :localizable,
28
+ dependent: :destroy,
29
+ autosave: true)
30
+ before_save :cleanup_localizations!
31
+ end
32
+ localizable_configuration.instance_eval(&block) if block_given?
33
+ end
34
+ end
35
+ end
36
+
37
+ ActiveRecord::Base.send(:extend, LocalizableModel::ActiveRecordExtension)
@@ -0,0 +1,49 @@
1
+ # encoding: utf-8
2
+
3
+ module LocalizableModel
4
+ # = LocalizableModel::ClassMethods
5
+ #
6
+ # Class methods for all Localizable models.
7
+ #
8
+ module ClassMethods
9
+ # Returns a scope where all records will be set to the given locale.
10
+ #
11
+ def in_locale(locale)
12
+ all
13
+ .extending(LocalizableModel::ScopeExtension)
14
+ .localize(locale)
15
+ .includes(:localizations)
16
+ end
17
+
18
+ # Returns a scope with only records matching the given locale.
19
+ #
20
+ # Page.localized('en').first.locale # => 'en'
21
+ #
22
+ def localized(locale)
23
+ in_locale(locale)
24
+ .where("localizations.locale = ?", locale)
25
+ .references(:localizations)
26
+ end
27
+
28
+ def localized_attributes
29
+ localizable_configuration.attributes.keys
30
+ end
31
+
32
+ # Accessor for the configuration.
33
+ def localizable_configuration
34
+ @localizable_configuration ||= inherited_localizable_configuration
35
+ end
36
+
37
+ private
38
+
39
+ def inherited_localizable_configuration
40
+ if superclass.respond_to?(:localizable_configuration)
41
+ LocalizableModel::Configuration.new(
42
+ superclass.localizable_configuration.attributes.dup
43
+ )
44
+ else
45
+ LocalizableModel::Configuration.new
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,48 @@
1
+ # encoding: utf-8
2
+
3
+ module LocalizableModel
4
+ class Configuration
5
+ def initialize(attributes = nil)
6
+ @attributes = attributes
7
+ end
8
+
9
+ def attribute(attribute_name, options = {})
10
+ attribute_table[attribute_name.to_sym] = options
11
+ end
12
+
13
+ def attributes
14
+ attribute_table.merge(dictionary_attributes)
15
+ end
16
+
17
+ def dictionary(dict)
18
+ dictionaries << dict
19
+ end
20
+
21
+ def attribute?(attribute)
22
+ attributes.keys.include?(attribute)
23
+ end
24
+
25
+ private
26
+
27
+ def dictionaries
28
+ @dictionaries ||= []
29
+ end
30
+
31
+ def dictionary_attributes
32
+ dictionaries.map(&:call).inject({}) do |attrs, list|
33
+ attrs.merge(hashify(list))
34
+ end
35
+ end
36
+
37
+ def hashify(list)
38
+ return list if list.is_a?(Hash)
39
+ list.each_with_object({}) do |e, a|
40
+ a[e] = {}
41
+ end
42
+ end
43
+
44
+ def attribute_table
45
+ @attributes ||= {}
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,6 @@
1
+ # encoding: utf-8
2
+
3
+ module LocalizableModel
4
+ class Engine < ::Rails::Engine
5
+ end
6
+ end
@@ -0,0 +1,122 @@
1
+ # encoding: utf-8
2
+
3
+ module LocalizableModel
4
+ # = LocalizableModel::InstanceMethods
5
+ #
6
+ # This is the public API for all localizable models.
7
+ # See Localizable for usage examples.
8
+ #
9
+ module InstanceMethods
10
+ # Returns all locales saved for this page.
11
+ #
12
+ delegate :locales, to: :localizer
13
+
14
+ # Getter for locale
15
+ #
16
+ # page.locale # => 'en'
17
+ #
18
+ delegate :locale, to: :localizer
19
+
20
+ # Setter for locale
21
+ #
22
+ # page.locale = 'no' # => 'no'
23
+ #
24
+ def locale=(locale)
25
+ @localizer = Localizer.new(self)
26
+ localizer.locale = locale
27
+ end
28
+
29
+ # Returns true if this page has a locale set
30
+ #
31
+ delegate :locale?, to: :localizer
32
+
33
+ # Returns a copy of the model with a different locale.
34
+ #
35
+ # localized = page.localize('en')
36
+ #
37
+ # localize also takes a block as an argument, which returns the
38
+ # result of the block.
39
+ #
40
+ # page.localize('nb') do |p|
41
+ # p.locale # => 'nb'
42
+ # end
43
+ # page.locale # => 'en'
44
+ #
45
+ def localize(locale)
46
+ clone = self.clone.localize!(locale)
47
+ block_given? ? (yield clone) : clone
48
+ end
49
+
50
+ # In-place variant of #localize.
51
+ #
52
+ # page.localize!('en')
53
+ #
54
+ # This is functionally equivalent to setting locale=,
55
+ # but returns the model instead of the locale and is chainable.
56
+ #
57
+ def localize!(locale)
58
+ @localizer = Localizer.new(self)
59
+ localizer.locale = locale
60
+ self
61
+ end
62
+
63
+ # assign_attributes from ActiveRecord is overridden to catch locale before
64
+ # any other attributes are written. This enables the following construct:
65
+ #
66
+ # Page.create(name: 'My Page', locale: 'en')
67
+ #
68
+ def assign_attributes(new_attributes)
69
+ if new_attributes.is_a?(Hash)
70
+ attributes = new_attributes.stringify_keys
71
+ self.locale = attributes["language"] if attributes.key?("language")
72
+ self.locale = attributes["locale"] if attributes.key?("locale")
73
+ end
74
+ super
75
+ end
76
+
77
+ # A localized model responds to :foo, :foo= and :foo?
78
+ #
79
+ def respond_to?(method_name, *args)
80
+ requested_attribute, = method_name.to_s.match(/(.*?)([\?=]?)$/)[1..2]
81
+ localizer.attribute?(requested_attribute.to_sym) ? true : super
82
+ end
83
+
84
+ alias translate localize
85
+ alias translate! localize!
86
+ alias working_language locale
87
+ alias working_language= locale=
88
+
89
+ protected
90
+
91
+ # Getter for the model's Localizer.
92
+ #
93
+ def localizer
94
+ @localizer ||= Localizer.new(self)
95
+ end
96
+
97
+ # Callback for cleaning up empty localizations.
98
+ # This is performed automatically when the model is saved.
99
+ #
100
+ def cleanup_localizations!
101
+ localizer.cleanup_localizations!
102
+ end
103
+
104
+ def method_missing(method_name, *args)
105
+ attr = method_to_attr(method_name)
106
+ super unless localizer.attribute?(attr)
107
+
108
+ case method_name.to_s
109
+ when /\?$/
110
+ localizer.value_for?(attr)
111
+ when /=$/
112
+ localizer.set(attr, args.first)
113
+ else
114
+ localizer.get(attr).to_s
115
+ end
116
+ end
117
+
118
+ def method_to_attr(method_name)
119
+ method_name.to_s.match(/(.*?)([\?=]?)$/)[1].to_sym
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,69 @@
1
+ # encoding: utf-8
2
+
3
+ module LocalizableModel
4
+ class Localizer
5
+ attr_accessor :locale
6
+ def initialize(model)
7
+ @model = model
8
+ @configuration = model.class.localizable_configuration
9
+ end
10
+
11
+ def attribute?(attribute)
12
+ @configuration.attribute?(attribute)
13
+ end
14
+
15
+ def locales
16
+ @model.localizations.map(&:locale).uniq
17
+ end
18
+
19
+ def locale?
20
+ locale ? true : false
21
+ end
22
+
23
+ def get(attribute, options = {})
24
+ get_options = { locale: locale }.merge(options)
25
+
26
+ find_localizations(
27
+ attribute.to_s,
28
+ get_options[:locale].to_s
29
+ ).try(&:first) ||
30
+ @model.localizations.new(
31
+ locale: get_options[:locale].to_s,
32
+ name: attribute.to_s
33
+ )
34
+ end
35
+
36
+ def set(attribute, value, options = {})
37
+ set_options = { locale: locale }.merge(options)
38
+ if value.is_a?(Hash)
39
+ value.each { |loc, val| set(attribute, val, locale: loc) }
40
+ else
41
+ require_locale!(attribute, set_options[:locale])
42
+ get(attribute, locale: set_options[:locale]).value = value
43
+ end
44
+ value
45
+ end
46
+
47
+ def value_for?(attribute)
48
+ get(attribute).value?
49
+ end
50
+
51
+ def cleanup_localizations!
52
+ @model.localizations = @model.localizations.select(&:value?)
53
+ end
54
+
55
+ private
56
+
57
+ def find_localizations(name, locale)
58
+ @model.localizations.select do |l|
59
+ l.name == name && l.locale == locale
60
+ end
61
+ end
62
+
63
+ def require_locale!(attribute, locale)
64
+ return if locale
65
+ raise(ArgumentError,
66
+ "Tried to set :#{attribute}, but no locale has been set")
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,20 @@
1
+ # encoding: utf-8
2
+
3
+ module LocalizableModel
4
+ # = LocalizableModel::ScopeExtension
5
+ #
6
+ # Injected into the Relation when Model.localized is called.
7
+ #
8
+ module ScopeExtension
9
+ attr_accessor :locale
10
+
11
+ def localize(locale)
12
+ @locale = locale
13
+ self
14
+ end
15
+
16
+ def to_a
17
+ super.map { |record| record.localize(@locale) }
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,5 @@
1
+ # encoding: utf-8
2
+
3
+ module LocalizableModel
4
+ VERSION = "0.0.1".freeze unless LocalizableModel.const_defined?("VERSION")
5
+ end
@@ -0,0 +1,19 @@
1
+ require "rails/generators/active_record/migration"
2
+
3
+ module LocalizableModel
4
+ module Generators
5
+ class MigrationGenerator < Rails::Generators::Base
6
+ include ActiveRecord::Generators::Migration
7
+
8
+ desc "Creates the LocalizableModel migration"
9
+ source_root File.expand_path("../templates", __FILE__)
10
+
11
+ def copy_files
12
+ migration_template(
13
+ "create_localizations.rb",
14
+ "db/migrate/create_localizations.rb"
15
+ )
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,11 @@
1
+ class CreateLocalizations < ActiveRecord::Migration
2
+ def change
3
+ create_table :localizations do |t|
4
+ t.references :localizable, polymorphic: true
5
+ t.string :name
6
+ t.string :locale
7
+ t.text :value, limit: 16_777_215
8
+ t.timestamps null: false
9
+ end
10
+ end
11
+ end
metadata ADDED
@@ -0,0 +1,141 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: localizable_model
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Inge Jørgensen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-05-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: mysql2
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.4.2
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.4.2
27
+ - !ruby/object:Gem::Dependency
28
+ name: pg
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.18.3
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.18.3
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec-rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 3.4.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 3.4.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: factory_girl
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 4.5.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 4.5.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: shoulda-matchers
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 3.1.0
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 3.1.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: rails
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 4.2.0
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 4.2.0
97
+ description: LocalizableModel provides localization support for ActiveRecord objects
98
+ email:
99
+ - inge@kord.no
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - README.md
105
+ - Rakefile
106
+ - app/models/localization.rb
107
+ - lib/localizable_model.rb
108
+ - lib/localizable_model/active_record_extension.rb
109
+ - lib/localizable_model/class_methods.rb
110
+ - lib/localizable_model/configuration.rb
111
+ - lib/localizable_model/engine.rb
112
+ - lib/localizable_model/instance_methods.rb
113
+ - lib/localizable_model/localizer.rb
114
+ - lib/localizable_model/scope_extension.rb
115
+ - lib/localizable_model/version.rb
116
+ - lib/rails/generators/localizable_model/migration/migration_generator.rb
117
+ - lib/rails/generators/localizable_model/migration/templates/create_localizations.rb
118
+ homepage: ''
119
+ licenses: []
120
+ metadata: {}
121
+ post_install_message:
122
+ rdoc_options: []
123
+ require_paths:
124
+ - lib
125
+ required_ruby_version: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: 1.9.2
130
+ required_rubygems_version: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ requirements: []
136
+ rubyforge_project: "."
137
+ rubygems_version: 2.4.5
138
+ signing_key:
139
+ specification_version: 4
140
+ summary: Localization support for ActiveRecord objects
141
+ test_files: []