has_localization_table 0.0.2
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/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +78 -0
- data/Rakefile +10 -0
- data/has_localization_table.gemspec +23 -0
- data/lib/has_localization_table/active_record.rb +185 -0
- data/lib/has_localization_table/config.rb +38 -0
- data/lib/has_localization_table/version.rb +3 -0
- data/lib/has_localization_table.rb +15 -0
- data/spec/active_record_spec.rb +198 -0
- data/spec/spec_helper.rb +19 -0
- metadata +106 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Daniel Vandersluis
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
# HasLocalizationTable
|
2
|
+
|
3
|
+
ActiveRecord plugin which adds setup and convenience methods for working with a relational localization table for user-driven data.
|
4
|
+
|
5
|
+
Adds accessors to retrieve localized attributes using the current locale, in order to avoid having to collect the correct object each time a value is needed. Localized attribute values are also cached for the current locale.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'has_localization_table'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install has_localization_table
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
The gem assumes that the localization table has already been migrated, and the model for it contains `belongs_to` associations for the locale table and the base table. You only need to call the `has_localization_table` method on the base model.
|
24
|
+
|
25
|
+
class Article < ActiveRecord::Base
|
26
|
+
# assuming ArticleLocalizations has name and body columns
|
27
|
+
has_localization_table :localizations, required: true, class_name: "ArticleLocalizations"
|
28
|
+
end
|
29
|
+
|
30
|
+
# Localized attributes can be retrieved by accessor...
|
31
|
+
a = Article.new(name: "Once Upon a Time...", body: "There once lived a princess locked away in a tower!")
|
32
|
+
a.name # "Once Upon a Time..."
|
33
|
+
a.body # "There once lived a princess locked away in a tower!"
|
34
|
+
|
35
|
+
# ... or set directly
|
36
|
+
a.name = "Sleeping Beauty"
|
37
|
+
|
38
|
+
# After changing to a different locale
|
39
|
+
a.name = "Belle au Bois Dormant"
|
40
|
+
a.body = "Il était une fois une princesse enfermée dans une tour!"
|
41
|
+
|
42
|
+
a.localizations
|
43
|
+
=> [<ArticleLocalization id: 1, article_id: 1, locale_id: 1, name: "Sleeping Beauty", body: "There once lived a princess locked away in a tower!">,
|
44
|
+
<ArticleLocalization id: 2, article_id: 1, locale_id: 2, name: "Belle au Bois Dormant", body: "Il était une fois une princesse enfermée dans une tour!">]
|
45
|
+
|
46
|
+
# Finder and order convenience methods are also provided:
|
47
|
+
Article.find_by_name("Snow White")
|
48
|
+
Article.find_by_body("...")
|
49
|
+
Article.find_by_name_and_body("...", "...")
|
50
|
+
Article.ordered_by_name # uses Arel, so it can be chained with other finder methods
|
51
|
+
|
52
|
+
### `has_localization_table` Arguments
|
53
|
+
If given, the first argument is the name used for the association, otherwise it defaults to `strings`.
|
54
|
+
|
55
|
+
* `class_name` (default: base class name + "Strings"; ie. `ArticleStrings`) - the name of the localization class.
|
56
|
+
* `required` (default: false) - if true, at least a localization object for the primary language (see Configuration section) must be present or validation will fail
|
57
|
+
* `optional` (default: []) - if `required` is true, can be used to specify that specific attributes are optional
|
58
|
+
|
59
|
+
Any options that can be passed into `has_many` can also be passed along and will be used when creating the association.
|
60
|
+
|
61
|
+
## Configuration
|
62
|
+
`HasLocalizationTable` can also be configured as follows:
|
63
|
+
|
64
|
+
HasLocalizationTable.configure do |config|
|
65
|
+
config.locale_class = "Locale"
|
66
|
+
config.locale_foreign_key = "locale_id"
|
67
|
+
config.primary_locale = Locale.primary_language
|
68
|
+
config.current_locale = Locale.current_language
|
69
|
+
config.all_locales = Locale.all
|
70
|
+
end
|
71
|
+
|
72
|
+
## Contributing
|
73
|
+
|
74
|
+
1. Fork it
|
75
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
76
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
77
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
78
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/has_localization_table/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Daniel Vandersluis"]
|
6
|
+
gem.email = ["dvandersluis@selfmgmt.com"]
|
7
|
+
gem.description = %q{Automatically sets up usage of a relational table to contain user-created multi-locale string attributes}
|
8
|
+
gem.summary = %q{Sets up associations and attribute methods for AR models that have a relational table to contain user-created data in multiple languages.}
|
9
|
+
gem.homepage = "https://github.com/dvandersluis/has_localization_table"
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "has_localization_table"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = HasLocalizationTable::VERSION
|
17
|
+
|
18
|
+
gem.add_dependency 'activesupport', ['>= 3.0.0']
|
19
|
+
gem.add_dependency 'activerecord', ['>= 3.0.0']
|
20
|
+
gem.add_development_dependency 'minitest'
|
21
|
+
gem.add_development_dependency 'sqlite3'
|
22
|
+
end
|
23
|
+
|
@@ -0,0 +1,185 @@
|
|
1
|
+
module HasLocalizationTable
|
2
|
+
module ActiveRecord
|
3
|
+
def has_localization_table(*args)
|
4
|
+
options = args.extract_options!
|
5
|
+
options[:association_name] = args.first
|
6
|
+
|
7
|
+
class_attribute :localization_table_options
|
8
|
+
self.localization_table_options = options
|
9
|
+
|
10
|
+
extend(ClassMethods)
|
11
|
+
include(InstanceMethods)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
def self.extended(klass)
|
17
|
+
options = { dependent: :delete_all }.merge(klass.localization_table_options)
|
18
|
+
|
19
|
+
association_name = options.delete(:association_name) || :strings
|
20
|
+
|
21
|
+
# If class_name isn't explicitly defined, try adding String onto the current class name
|
22
|
+
options[:class_name] = klass.name + "String" if options[:class_name].blank? and (Module.const_get(klass.name + "String") rescue false)
|
23
|
+
|
24
|
+
# Define the association
|
25
|
+
klass.has_many association_name, options.except(:required, :optional)
|
26
|
+
association = klass.reflect_on_association(association_name)
|
27
|
+
|
28
|
+
klass.class_eval do
|
29
|
+
# Initialize string records after main record initialization
|
30
|
+
after_initialize do
|
31
|
+
build_missing_strings
|
32
|
+
end
|
33
|
+
|
34
|
+
before_validation do
|
35
|
+
reject_empty_strings
|
36
|
+
build_missing_strings
|
37
|
+
end
|
38
|
+
|
39
|
+
# Reject any blank strings before saving the record
|
40
|
+
# Validation will have happened by this point, so if there is a required string that is needed, it won't be rejected
|
41
|
+
before_save do
|
42
|
+
reject_empty_strings
|
43
|
+
end
|
44
|
+
|
45
|
+
# Add validation to ensure a string for the primary language exists if the string is required
|
46
|
+
validate do
|
47
|
+
if self.class.localization_table_options[:required] || false
|
48
|
+
errors.add(association_name, :primary_lang_string_required) unless send(association_name).any? do |string|
|
49
|
+
string.send(HasLocalizationTable.config.locale_foreign_key) == HasLocalizationTable.primary_locale.id
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
define_method :build_missing_strings do
|
55
|
+
locale_ids = HasLocalizationTable.all_locales.map(&:id)
|
56
|
+
HasLocalizationTable.all_locales.each do |l|
|
57
|
+
send(association_name).build(HasLocalizationTable.config.locale_foreign_key => l.id) unless send(association_name).detect{ |str| str.send(HasLocalizationTable.config.locale_foreign_key) == l.id }
|
58
|
+
send(association_name).sort_by!{ |s| locale_ids.index(s.send(HasLocalizationTable.config.locale_foreign_key)) || 0 }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
private :build_missing_strings
|
62
|
+
|
63
|
+
define_method :reject_empty_strings do
|
64
|
+
send(association_name).reject! { |s| !s.persisted? and self.class.localized_attributes.all?{ |attr| s.send(attr).blank? } }
|
65
|
+
end
|
66
|
+
private :reject_empty_strings
|
67
|
+
|
68
|
+
# Find a record by multiple string values
|
69
|
+
define_singleton_method :find_by_localized_attributes do |attributes, language = HasLocalizationTable.current_locale|
|
70
|
+
string_record = association.klass.where({ HasLocalizationTable.config.locale_foreign_key => language.id }.merge(attributes)).first
|
71
|
+
string_record.send(klass.to_s.underscore.to_sym) rescue nil
|
72
|
+
end
|
73
|
+
private_class_method :find_by_localized_attributes
|
74
|
+
end
|
75
|
+
|
76
|
+
klass.localized_attributes.each do |attribute|
|
77
|
+
# Add validation to make all string fields required for the primary language
|
78
|
+
association.klass.class_eval do
|
79
|
+
validates attribute, presence: { message: :custom_this_field_is_required },
|
80
|
+
if: proc { |model| klass.name.constantize.localized_attribute_required?(attribute) && model.send(HasLocalizationTable.config.locale_foreign_key) == HasLocalizationTable.current_locale.id }
|
81
|
+
end
|
82
|
+
|
83
|
+
# Set up accessors and ordering named_scopes for each non-FK attribute on the base model
|
84
|
+
klass.class_eval do
|
85
|
+
define_method attribute do |language = HasLocalizationTable.current_locale|
|
86
|
+
# Try to load a string for the given language
|
87
|
+
# If that fails, try for the primary language
|
88
|
+
get_memoized_string(language, association_name, attribute) || get_memoized_string(HasLocalizationTable.primary_locale, association_name, attribute)
|
89
|
+
end
|
90
|
+
|
91
|
+
define_method "#{attribute}=" do |value|
|
92
|
+
set_memoized_string(HasLocalizationTable.current_locale, association_name, attribute, value)
|
93
|
+
end
|
94
|
+
|
95
|
+
define_singleton_method "ordered_by_#{attribute}" do |direction = :asc|
|
96
|
+
direction = direction == :asc ? "ASC" : "DESC"
|
97
|
+
|
98
|
+
joins(%{
|
99
|
+
LEFT OUTER JOIN #{association.table_name}
|
100
|
+
ON #{association.table_name}.#{association.foreign_key} = #{self.table_name}.#{self.primary_key}
|
101
|
+
AND #{association.table_name}.#{HasLocalizationTable.config.locale_foreign_key} = %d
|
102
|
+
} % HasLocalizationTable.current_locale.id
|
103
|
+
).
|
104
|
+
order( "#{association.table_name}.#{attribute} #{direction}")
|
105
|
+
#order{ Squeel::Nodes::Order.new(Squeel::Nodes::Stub.new(association.table_name).send(attribute), direction) }
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def localized_attributes
|
112
|
+
# Determine which attributes of the association model should be accessable through the base class
|
113
|
+
# ie. everything that's not a primary key, foreign key, or timestamp attribute
|
114
|
+
association_name = self.localization_table_options[:association_name] || :strings
|
115
|
+
association = reflect_on_association(association_name)
|
116
|
+
|
117
|
+
attribute_names = association.klass.attribute_names
|
118
|
+
timestamp_attrs = association.klass.new.send(:all_timestamp_attributes_in_model).map(&:to_s)
|
119
|
+
foreign_keys = association.klass.reflect_on_all_associations.map{ |a| a.association_foreign_key }
|
120
|
+
primary_keys = [association.klass.primary_key]
|
121
|
+
# protected_attrs = association.klass.protected_attributes.to_a
|
122
|
+
|
123
|
+
(attribute_names - timestamp_attrs - foreign_keys - primary_keys).map(&:to_sym)
|
124
|
+
end
|
125
|
+
|
126
|
+
def localized_attribute_required?(attribute)
|
127
|
+
return false unless localization_table_options[:required] || false
|
128
|
+
return true unless localization_table_options[:optional]
|
129
|
+
|
130
|
+
!localization_table_options[:optional].include?(attribute)
|
131
|
+
end
|
132
|
+
|
133
|
+
def method_missing(name, *args, &block)
|
134
|
+
if name.to_s =~ /\Afind_by_([a-z0-9_]+(_and_[a-z0-9_]+)*)\Z/
|
135
|
+
attributes = $1.split("_and_").map(&:to_sym)
|
136
|
+
if (attributes & localized_attributes).size == attributes.size and args.size == attributes.size
|
137
|
+
raise ArgumentError, "expected #{attributes.size} #{"argument".pluralize(attributes.size)}" unless args.size == attributes.size
|
138
|
+
args = attributes.zip(args).inject({}) { |memo, (key, val)| memo[key] = val; memo }
|
139
|
+
return find_by_localized_attributes(args)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
super
|
144
|
+
end
|
145
|
+
|
146
|
+
def respond_to?(*args)
|
147
|
+
if args.first.to_s =~ /\Afind_by_([a-z0-9_]+(_and_[a-z0-9_]+)*)\Z/
|
148
|
+
attributes = $1.split("_and_").map(&:to_sym)
|
149
|
+
return ((attributes & localized_attributes).size == attributes.size)
|
150
|
+
end
|
151
|
+
|
152
|
+
super
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
module InstanceMethods
|
157
|
+
private
|
158
|
+
# Both strings and the associations are memoized, so that if an association adds more than one attribute to the main model, the association doesn't need
|
159
|
+
# to be loaded each time a different attribute is accessed.
|
160
|
+
def get_memoized_string(language, association, attribute)
|
161
|
+
@_localized_attribute_cache ||= {}
|
162
|
+
@_localized_attribute_cache[attribute] ||= {}
|
163
|
+
|
164
|
+
@_localized_association_cache ||= {}
|
165
|
+
@_localized_association_cache[association] ||= {}
|
166
|
+
|
167
|
+
@_localized_attribute_cache[attribute][language.id] ||= begin
|
168
|
+
@_localized_association_cache[association][language.id] ||= (send(association).where{ HasLocalizationTable.config.locale_foreign_key == language.id }.first || send(association).detect{ |a| a.send(HasLocalizationTable.config.locale_foreign_key) == language.id })
|
169
|
+
s = @_localized_association_cache[association][language.id].send(attribute) rescue nil
|
170
|
+
s.blank? ? nil : s
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def set_memoized_string(language, association, attribute, value)
|
175
|
+
string = send(association).detect{ |a| a.send(HasLocalizationTable.config.locale_foreign_key) == language.id } || send(association).build(HasLocalizationTable.config.locale_foreign_key => language.id)
|
176
|
+
value = value.to_s
|
177
|
+
|
178
|
+
string.send(:"#{attribute}=", value)
|
179
|
+
|
180
|
+
@_localized_attribute_cache ||= {}
|
181
|
+
@_localized_attribute_cache[attribute] ||= {}
|
182
|
+
@_localized_attribute_cache[attribute][language.id] = value.blank? ? nil : value
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'active_support/configurable'
|
2
|
+
require 'active_support/inflector'
|
3
|
+
|
4
|
+
module HasLocalizationTable
|
5
|
+
# Configures global settings for HasLocalizationTable
|
6
|
+
# HasLocalizationTable.configure do |config|
|
7
|
+
# config.default_locale = Locale.find_by_code("en")
|
8
|
+
# end
|
9
|
+
def self.configure(&block)
|
10
|
+
yield @config ||= HasLocalizationTable::Configuration.new
|
11
|
+
end
|
12
|
+
|
13
|
+
# Global settings for ODF::Converter
|
14
|
+
def self.config
|
15
|
+
@config
|
16
|
+
end
|
17
|
+
|
18
|
+
# need a Class for 3.0
|
19
|
+
class Configuration #:nodoc:
|
20
|
+
include ActiveSupport::Configurable
|
21
|
+
include ActiveSupport::Inflector
|
22
|
+
|
23
|
+
config_accessor :locale_class
|
24
|
+
config_accessor :locale_foreign_key
|
25
|
+
config_accessor :primary_locale
|
26
|
+
config_accessor :current_locale
|
27
|
+
config_accessor :all_locales
|
28
|
+
end
|
29
|
+
|
30
|
+
# this is ugly. why can't we pass the default value to config_accessor...?
|
31
|
+
configure do |config|
|
32
|
+
config.locale_class = "Locale"
|
33
|
+
config.locale_foreign_key = "locale_id"
|
34
|
+
config.primary_locale = ->{ config.locale_class.constantize.first }
|
35
|
+
config.current_locale = ->{ config.locale_class.constantize.first }
|
36
|
+
config.all_locales = ->{ config.locale_class.constantize.all }
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require "has_localization_table/version"
|
2
|
+
require "has_localization_table/config"
|
3
|
+
require "has_localization_table/active_record"
|
4
|
+
|
5
|
+
ActiveRecord::Base.extend(HasLocalizationTable::ActiveRecord) if defined?(ActiveRecord::Base)
|
6
|
+
|
7
|
+
module HasLocalizationTable
|
8
|
+
[:primary_locale, :current_locale, :all_locales].each do |meth|
|
9
|
+
define_singleton_method meth do
|
10
|
+
l = config.send(meth)
|
11
|
+
return l.call if l.respond_to?(:call)
|
12
|
+
l
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# Setup in-memory database so AR can work
|
4
|
+
ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
|
5
|
+
ActiveRecord::Migration.tap do |m|
|
6
|
+
m.create_table :articles do |t|
|
7
|
+
t.timestamps
|
8
|
+
end
|
9
|
+
|
10
|
+
m.create_table :locales do |t|
|
11
|
+
t.string :name
|
12
|
+
end
|
13
|
+
|
14
|
+
m.create_table :article_strings do |t|
|
15
|
+
t.integer :article_id
|
16
|
+
t.integer :locale_id
|
17
|
+
t.string :name
|
18
|
+
t.string :description
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Set up locales
|
23
|
+
Locale = Class.new(ActiveRecord::Base)
|
24
|
+
Locale.create!(name: "English")
|
25
|
+
Locale.create!(name: "French")
|
26
|
+
|
27
|
+
ArticleString = Class.new(ActiveRecord::Base) do
|
28
|
+
belongs_to :article
|
29
|
+
belongs_to :locale
|
30
|
+
end
|
31
|
+
|
32
|
+
describe HasLocalizationTable do
|
33
|
+
before do
|
34
|
+
# Configure HLT
|
35
|
+
HasLocalizationTable.configure do |c|
|
36
|
+
c.primary_locale = Locale.first
|
37
|
+
c.current_locale = Locale.first
|
38
|
+
c.all_locales = Locale.all
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "#has_localization_table" do
|
43
|
+
before do
|
44
|
+
Object.send(:remove_const, :Article) rescue nil
|
45
|
+
Article = Class.new(ActiveRecord::Base)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should track any given options" do
|
49
|
+
Article.has_localization_table :localizations, required: true, optional: [:description]
|
50
|
+
Article.localization_table_options.must_equal({ association_name: :localizations, required: true, optional: [:description] })
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should define has_many association on the base class with a default name of :strings" do
|
54
|
+
Article.has_localization_table
|
55
|
+
assoc = Article.reflect_on_association(:strings)
|
56
|
+
assoc.wont_be_nil
|
57
|
+
assoc.macro.must_equal :has_many
|
58
|
+
assoc.klass.must_equal ArticleString
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should use the given association name" do
|
62
|
+
Article.has_localization_table :localizations
|
63
|
+
assoc = Article.reflect_on_association(:localizations)
|
64
|
+
assoc.wont_be_nil
|
65
|
+
assoc.macro.must_equal :has_many
|
66
|
+
assoc.klass.must_equal ArticleString
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should use the given class" do
|
70
|
+
ArticleText = Class.new(ArticleString)
|
71
|
+
Article.has_localization_table class_name: ArticleText
|
72
|
+
assoc = Article.reflect_on_association(:strings)
|
73
|
+
assoc.wont_be_nil
|
74
|
+
assoc.macro.must_equal :has_many
|
75
|
+
assoc.klass.name.must_equal "ArticleText"
|
76
|
+
|
77
|
+
Object.send(:remove_const, :ArticleText)
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should add validations if given required: true" do
|
81
|
+
Article.has_localization_table required: true
|
82
|
+
a = Article.new
|
83
|
+
refute a.valid?
|
84
|
+
a.errors[:strings].wont_be_empty
|
85
|
+
|
86
|
+
a = Article.new(description: "Wishing the world hello!")
|
87
|
+
s = a.strings.first
|
88
|
+
refute s.valid?
|
89
|
+
s.errors[:name].wont_be_empty
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should not add validations if given required: false" do
|
93
|
+
Article.has_localization_table required: false
|
94
|
+
a = Article.new
|
95
|
+
a.valid? or raise a.strings.map(&:errors).inspect
|
96
|
+
a.errors[:strings].must_be_empty
|
97
|
+
|
98
|
+
a = Article.new(description: "Wishing the world hello!")
|
99
|
+
s = a.strings.first
|
100
|
+
assert s.valid?
|
101
|
+
s.errors[:name].must_be_empty
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should not add validations if required is not given" do
|
105
|
+
Article.has_localization_table
|
106
|
+
a = Article.new
|
107
|
+
assert a.valid?
|
108
|
+
a.errors[:strings].must_be_empty
|
109
|
+
|
110
|
+
a = Article.new(description: "Wishing the world hello!")
|
111
|
+
s = a.strings.first
|
112
|
+
assert s.valid?
|
113
|
+
s.errors[:name].must_be_empty
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should not add validations for optional fields" do
|
117
|
+
Article.has_localization_table required: true, optional: [:description]
|
118
|
+
a = Article.new(name: "Test")
|
119
|
+
assert a.valid?
|
120
|
+
a.errors[:strings].must_be_empty
|
121
|
+
assert a.strings.all?{ |s| s.errors[:description].empty? }
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
describe "other methods" do
|
126
|
+
before do
|
127
|
+
Object.send(:remove_const, :Article) rescue nil
|
128
|
+
Article = Class.new(ActiveRecord::Base)
|
129
|
+
Article.has_localization_table
|
130
|
+
end
|
131
|
+
|
132
|
+
let(:a) { Article.new(name: "Test", description: "Description") }
|
133
|
+
|
134
|
+
it "should set localized attributes" do
|
135
|
+
a.strings.first.name.must_equal "Test"
|
136
|
+
a.strings.first.description.must_equal "Description"
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should create accessor methods" do
|
140
|
+
a.name.must_equal "Test"
|
141
|
+
a.description.must_equal "Description"
|
142
|
+
end
|
143
|
+
|
144
|
+
it "should save localized attributes" do
|
145
|
+
a.save!
|
146
|
+
a.reload
|
147
|
+
a.name.must_equal "Test"
|
148
|
+
a.description.must_equal "Description"
|
149
|
+
end
|
150
|
+
|
151
|
+
it "should create mutator methods" do
|
152
|
+
a.name = "Changed"
|
153
|
+
a.description = "Changed Description"
|
154
|
+
a.name.must_equal "Changed"
|
155
|
+
a.description.must_equal "Changed Description"
|
156
|
+
a.strings.first.name.must_equal "Changed"
|
157
|
+
a.strings.first.description.must_equal "Changed Description"
|
158
|
+
end
|
159
|
+
|
160
|
+
it "should use the current locale when setting" do
|
161
|
+
a
|
162
|
+
|
163
|
+
HasLocalizationTable.configure do |c|
|
164
|
+
c.current_locale = Locale.last
|
165
|
+
end
|
166
|
+
|
167
|
+
a.name = "French Name"
|
168
|
+
a.description = "French Description"
|
169
|
+
|
170
|
+
eng = a.strings.detect{ |s| s.locale_id == Locale.first.id }
|
171
|
+
fre = a.strings.detect{ |s| s.locale_id == Locale.last.id }
|
172
|
+
|
173
|
+
eng.name.must_equal "Test"
|
174
|
+
eng.description.must_equal "Description"
|
175
|
+
fre.name.must_equal "French Name"
|
176
|
+
fre.description.must_equal "French Description"
|
177
|
+
end
|
178
|
+
|
179
|
+
it "should create finder methods" do
|
180
|
+
a.save!
|
181
|
+
Article.find_by_name("Test").must_equal a
|
182
|
+
Article.find_by_description("Description").must_equal a
|
183
|
+
Article.find_by_name_and_description("Test", "Description").must_equal a
|
184
|
+
Article.find_by_description_and_name("Description", "Test").must_equal a
|
185
|
+
|
186
|
+
Article.find_by_name("Wrong").must_be_nil
|
187
|
+
Article.find_by_description("Wrong").must_be_nil
|
188
|
+
end
|
189
|
+
|
190
|
+
it "should create ordered_by methods" do
|
191
|
+
a.save!
|
192
|
+
b = Article.create!(name: "Name", description: "Another Description")
|
193
|
+
c = Article.create!(name: "Once Upon a Time...", description: "Fairytale")
|
194
|
+
Article.ordered_by_name.must_equal [b, c, a]
|
195
|
+
Article.ordered_by_description.must_equal [b, a, c]
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'active_record'
|
3
|
+
require 'has_localization_table'
|
4
|
+
|
5
|
+
class MiniTest::Spec
|
6
|
+
def run(*args, &block)
|
7
|
+
value = nil
|
8
|
+
|
9
|
+
begin
|
10
|
+
ActiveRecord::Base.connection.transaction do
|
11
|
+
value = super
|
12
|
+
raise ActiveRecord::Rollback
|
13
|
+
end
|
14
|
+
rescue ActiveRecord::Rollback
|
15
|
+
end
|
16
|
+
|
17
|
+
return value # The result of run must be always returned for the pretty dots to show up
|
18
|
+
end
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: has_localization_table
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Daniel Vandersluis
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-08-15 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activesupport
|
16
|
+
requirement: &21307760 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 3.0.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *21307760
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: activerecord
|
27
|
+
requirement: &21323460 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 3.0.0
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *21323460
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: minitest
|
38
|
+
requirement: &21322860 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *21322860
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: sqlite3
|
49
|
+
requirement: &21322440 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *21322440
|
58
|
+
description: Automatically sets up usage of a relational table to contain user-created
|
59
|
+
multi-locale string attributes
|
60
|
+
email:
|
61
|
+
- dvandersluis@selfmgmt.com
|
62
|
+
executables: []
|
63
|
+
extensions: []
|
64
|
+
extra_rdoc_files: []
|
65
|
+
files:
|
66
|
+
- .gitignore
|
67
|
+
- Gemfile
|
68
|
+
- LICENSE
|
69
|
+
- README.md
|
70
|
+
- Rakefile
|
71
|
+
- has_localization_table.gemspec
|
72
|
+
- lib/has_localization_table.rb
|
73
|
+
- lib/has_localization_table/active_record.rb
|
74
|
+
- lib/has_localization_table/config.rb
|
75
|
+
- lib/has_localization_table/version.rb
|
76
|
+
- spec/active_record_spec.rb
|
77
|
+
- spec/spec_helper.rb
|
78
|
+
homepage: https://github.com/dvandersluis/has_localization_table
|
79
|
+
licenses: []
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options: []
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ! '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
|
+
none: false
|
92
|
+
requirements:
|
93
|
+
- - ! '>='
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
requirements: []
|
97
|
+
rubyforge_project:
|
98
|
+
rubygems_version: 1.8.12
|
99
|
+
signing_key:
|
100
|
+
specification_version: 3
|
101
|
+
summary: Sets up associations and attribute methods for AR models that have a relational
|
102
|
+
table to contain user-created data in multiple languages.
|
103
|
+
test_files:
|
104
|
+
- spec/active_record_spec.rb
|
105
|
+
- spec/spec_helper.rb
|
106
|
+
has_rdoc:
|