localizable_model 0.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.
@@ -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: []