babylonia-rails 0.0.2 → 0.1.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.
- checksums.yaml +4 -4
- data/README.rdoc +32 -16
- data/babylonia-rails.gemspec +1 -1
- data/lib/babylonia/rails.rb +1 -1
- data/lib/babylonia/rails/integration.rb +2 -1
- data/lib/babylonia/rails/validators.rb +2 -0
- data/lib/babylonia/rails/validators/locale_validator.rb +62 -0
- data/lib/babylonia/rails/validators/uniqueness_validator.rb +74 -0
- data/lib/babylonia/rails/version.rb +1 -1
- data/spec/babylonia-rails/integration_spec.rb +3 -3
- data/spec/babylonia-rails/persistence_spec.rb +5 -4
- data/spec/babylonia-rails/validators/locale_validator_spec.rb +144 -0
- data/spec/babylonia-rails/validators/uniqueness_validator_spec.rb +168 -0
- data/spec/spec_helper.rb +2 -1
- data/spec/support/tables.rb +2 -1
- metadata +11 -7
- data/lib/babylonia/rails/validator.rb +0 -42
- data/spec/babylonia-rails/validator_spec.rb +0 -76
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 23a9d0abe039872a11f1cc2e55c0d4d1e50898f1
|
4
|
+
data.tar.gz: 256aedb91e172f3bdcc51d5e03de89dd6348408f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 23c004c9cc8086efce000c3bfc5c37d33f660ce32aa818a30730c603076a81875b33178dafdeae4c191108c0e455fb91e33bba507145ffcd5003d1782b8cc443
|
7
|
+
data.tar.gz: 392d0bc2877b8ffd537acbec02d019a77d72d061025e982e98f7f92025c4c6969efc9e5629532de68608164fcb6b1a6c462d17111077f2f534d6950dd9e149da
|
data/README.rdoc
CHANGED
@@ -7,28 +7,44 @@ In your gemfile
|
|
7
7
|
Or via the gem command
|
8
8
|
gem install babylonia-rails
|
9
9
|
|
10
|
-
== How to
|
11
|
-
Feels like babylonia, validates like rails
|
10
|
+
== How to Ride the Rails to Babylonia
|
11
|
+
Feels like babylonia, validates like rails: You can use all rails validators except confirmation. Just add the validation options under :locales in the validation as you would normally do.
|
12
12
|
|
13
13
|
class BabylonianFields < ActiveRecord::Base
|
14
|
-
build_babylonian_tower_on :grass
|
14
|
+
build_babylonian_tower_on :grass, :sand, :sea
|
15
15
|
|
16
|
-
|
16
|
+
validates :grass, locales: { presence: [:en, :de, :it], length: { in: 6..20 }}
|
17
|
+
validates :sand, locales: { absence: [:en, :it], format: { with: /some nifty regexp/ }}
|
17
18
|
end
|
18
19
|
|
19
|
-
|
20
|
+
You can add a :locales option to each validation to determine which locales get validated with the said validator
|
21
|
+
|
22
|
+
class BabylonianFields < ActiveRecord::Base
|
23
|
+
build_babylonian_tower_on :grass, :sand, :sea
|
24
|
+
|
25
|
+
validates :grass, locales: { presence: [:en, :de, :it], length: { in: 6..20, locales: [:it] }}
|
26
|
+
validates :sand, locales: { absence: [:en, :it], uniqueness: { scope: :some_scope, locales: [:de, :en] }}
|
27
|
+
end
|
20
28
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
babylonian_field
|
30
|
-
|
31
|
-
|
29
|
+
Yes, there's also uniqueness validation. It works with a LIKE / arel.matches query.
|
30
|
+
|
31
|
+
For all the details on rails validation visit {here}[http://edgeguides.rubyonrails.org/active_record_validations.html#validation-helpers].
|
32
|
+
|
33
|
+
Please be aware that validations will only validate available locales!
|
34
|
+
|
35
|
+
== What About Forms?
|
36
|
+
|
37
|
+
form_for(@babylonian_field, url: {action: 'create'}) do |f|
|
38
|
+
f.text_field :grass_en
|
39
|
+
f.text_field :grass_de
|
40
|
+
f.text_field :grass_it
|
41
|
+
f.submit "Create"
|
42
|
+
end
|
43
|
+
|
44
|
+
== What about dynamic Finders?
|
45
|
+
Uhhm, no. Arel itself is pretty nifty, so if you want to find something by SQL, you will.
|
46
|
+
|
47
|
+
== For Further Usage
|
32
48
|
Visit {babylonia}[http://github.com/beatrichartz/babylonia]
|
33
49
|
|
34
50
|
== Contributing to babylonia-rails
|
data/babylonia-rails.gemspec
CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |s|
|
|
18
18
|
|
19
19
|
active_record = ENV["ACTIVE_RECORD_VERSION"] || ">= 3.2.0"
|
20
20
|
|
21
|
-
s.add_dependency "babylonia", ">= 0.
|
21
|
+
s.add_dependency "babylonia", ">= 0.1.1"
|
22
22
|
s.add_dependency "activerecord", active_record
|
23
23
|
s.add_development_dependency "bundler", ">= 1.0.0"
|
24
24
|
end
|
data/lib/babylonia/rails.rb
CHANGED
@@ -0,0 +1,62 @@
|
|
1
|
+
# Rails each validator for locales
|
2
|
+
# @params [Hash] options
|
3
|
+
# @option [Hash] locales The validations to do on the locales of an attribute
|
4
|
+
require 'active_model'
|
5
|
+
|
6
|
+
module Babylonia
|
7
|
+
module Rails
|
8
|
+
module Validators
|
9
|
+
class LocalesValidator < ::ActiveModel::EachValidator
|
10
|
+
attr_reader :validators
|
11
|
+
|
12
|
+
def validate_each(record, attribute, value)
|
13
|
+
@validators = {}
|
14
|
+
record.available_locales.each do |lang|
|
15
|
+
validations = ActiveRecord::VERSION::MAJOR < 4 ? [:acceptance, :exclusion, :format, :inclusion, :length, :numericality, :presence, :uniqueness] : [:absence, :acceptance, :exclusion, :format, :inclusion, :length, :numericality, :presence, :uniqueness]
|
16
|
+
validations.each do |validation|
|
17
|
+
add_validator validation, attribute, lang if should_validate?(validation, lang)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
validators.each do |validator, attributes|
|
22
|
+
if validator == :uniqueness
|
23
|
+
Babylonia::Rails::Validators::LocaleUniquenessValidator.new(validator_attributes(validator, attributes)).validate(record)
|
24
|
+
else
|
25
|
+
"ActiveModel::Validations::#{validator.to_s.classify}Validator".constantize.new(validator_attributes(validator, attributes)).validate(record)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def add_validator validator, attribute, lang
|
33
|
+
@validators ||= {}
|
34
|
+
if validator == :uniqueness
|
35
|
+
@validators[validator] ||= { attributes: {} }
|
36
|
+
@validators[validator][:attributes].merge! lang => attribute
|
37
|
+
else
|
38
|
+
@validators[validator] ||= { attributes: [] }
|
39
|
+
@validators[validator][:attributes] << :"#{attribute}_#{lang}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def should_validate?(option, lang)
|
44
|
+
if [:presence, :absence, :uniqueness, :numericality].include?(option)
|
45
|
+
options[option] == true || (options[option].is_a?(Array) && options[option].include?(lang)) || (options[option].is_a?(Hash) && locale_included_in_validation_options?(option, lang))
|
46
|
+
else
|
47
|
+
options[option].is_a?(Hash) && locale_included_in_validation_options?(option, lang)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def locale_included_in_validation_options?(validation_name, lang)
|
52
|
+
options[validation_name][:locales].blank? || options[validation_name][:locales].include?(lang)
|
53
|
+
end
|
54
|
+
|
55
|
+
def validator_attributes(validator, attributes)
|
56
|
+
options[validator].is_a?(Hash) ? options[validator].delete_if{|k,v| k == :locales}.merge(attributes) : attributes
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
module Babylonia
|
4
|
+
module Rails
|
5
|
+
module Validators
|
6
|
+
class LocaleUniquenessValidator < ::ActiveRecord::Validations::UniquenessValidator
|
7
|
+
|
8
|
+
def validate(record)
|
9
|
+
(ActiveRecord::VERSION::MAJOR > 3 ? attributes : attributes.first).each do |locale, attribute|
|
10
|
+
value = record.read_attribute_for_validation(:"#{attribute}_#{locale}")
|
11
|
+
next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
|
12
|
+
validate_each(record, attribute, locale, value)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def validate_each(record, attribute, locale, value)
|
17
|
+
finder_class = find_finder_class_for(record)
|
18
|
+
table = finder_class.arel_table
|
19
|
+
|
20
|
+
relation = build_relation(finder_class, table, attribute, locale, value)
|
21
|
+
relation = relation.and(table[finder_class.primary_key.to_sym].not_eq(record.id)) if record.persisted?
|
22
|
+
if ActiveRecord::VERSION::MAJOR > 3
|
23
|
+
relation = scope_relation(record, table, relation)
|
24
|
+
else
|
25
|
+
Array.wrap(options[:scope]).each do |scope_item|
|
26
|
+
scope_value = record.read_attribute(scope_item)
|
27
|
+
relation = relation.and(table[scope_item].eq(scope_value))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
relation = finder_class.unscoped.where(relation)
|
31
|
+
relation = relation.merge(options[:conditions]) if options[:conditions]
|
32
|
+
|
33
|
+
if relation.exists?
|
34
|
+
error_options = options.except(:case_sensitive, :scope, :conditions)
|
35
|
+
error_options[:value] = value
|
36
|
+
|
37
|
+
record.errors.add(:"#{attribute}_#{locale}", :taken, error_options)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
# Copy from rails uniqueness validator
|
44
|
+
# The check for an existing value should be run from a class that
|
45
|
+
# isn't abstract. This means working down from the current class
|
46
|
+
# (self), to the first non-abstract class. Since classes don't know
|
47
|
+
# their subclasses, we have to build the hierarchy between self and
|
48
|
+
# the record's class.
|
49
|
+
def find_finder_class_for(record) #:nodoc:
|
50
|
+
class_hierarchy = [record.class]
|
51
|
+
|
52
|
+
while class_hierarchy.last != @klass && class_hierarchy.last.superclass
|
53
|
+
class_hierarchy.push(class_hierarchy.last.superclass)
|
54
|
+
end
|
55
|
+
|
56
|
+
class_hierarchy.detect { |klass| !klass.abstract_class? }
|
57
|
+
end
|
58
|
+
|
59
|
+
def build_relation(klass, table, attribute, locale, value) #:nodoc:
|
60
|
+
column = klass.columns_hash[attribute.to_s]
|
61
|
+
value = klass.connection.type_cast(value, column)
|
62
|
+
value = YAML.dump({locale => value}).gsub(/\A[^\n]+/, '')
|
63
|
+
value = value.to_s[0, column.limit] if value && column.limit && column.text?
|
64
|
+
if !options[:case_sensitive] && value && column.text?
|
65
|
+
table[attribute].lower.matches("%#{value.respond_to?(:expr) ? value.expr : value.downcase}%")
|
66
|
+
else
|
67
|
+
value = klass.connection.case_sensitive_modifier(value) unless value.nil?
|
68
|
+
table[attribute].matches("%#{value.expr}%")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -2,13 +2,13 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe "Integration" do
|
4
4
|
|
5
|
-
class
|
6
|
-
|
5
|
+
class BabylonianIntegratedField < ActiveRecord::Base
|
6
|
+
self.table_name = 'babylonian_fields'
|
7
7
|
end
|
8
8
|
|
9
9
|
describe "Babylonia" do
|
10
10
|
it "should already have extended active record" do
|
11
|
-
|
11
|
+
BabylonianIntegratedField.should be_respond_to(:build_babylonian_tower_on)
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
@@ -1,11 +1,12 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe
|
3
|
+
describe Babylonia::Rails::Validators::LocalesValidator do
|
4
4
|
|
5
|
-
class
|
5
|
+
class BabylonianPersistentField < ActiveRecord::Base
|
6
|
+
self.table_name = 'babylonian_fields'
|
6
7
|
|
7
8
|
build_babylonian_tower_on :marshes
|
8
|
-
validates :marshes,
|
9
|
+
validates :marshes, locales: { presence: [:de, :en, :it], length: {in: 5..11} }
|
9
10
|
|
10
11
|
end
|
11
12
|
|
@@ -13,7 +14,7 @@ describe LanguagesValidator do
|
|
13
14
|
before(:each) do
|
14
15
|
I18n.stub available_locales: [:de, :en, :it]
|
15
16
|
end
|
16
|
-
subject {
|
17
|
+
subject { BabylonianPersistentField.new(marshes: {en: 'Hello', de: 'Hello', it: 'Hello'})}
|
17
18
|
it "should be possible to store the string value" do
|
18
19
|
subject.save!
|
19
20
|
subject.reload
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Babylonia::Rails::Validators::LocalesValidator do
|
4
|
+
let(:format_validation_options) {
|
5
|
+
{
|
6
|
+
with: /\A\z/,
|
7
|
+
locales: [:de, :en, :it]
|
8
|
+
}
|
9
|
+
}
|
10
|
+
let(:numericality_validation_options) {
|
11
|
+
{
|
12
|
+
only_integer: true,
|
13
|
+
locales: [:de, :en, :it]
|
14
|
+
}
|
15
|
+
}
|
16
|
+
let(:inclusion_validation_options) {
|
17
|
+
{
|
18
|
+
in: %w(small medium large),
|
19
|
+
locales: [:de, :en, :it]
|
20
|
+
}
|
21
|
+
}
|
22
|
+
let(:exclusion_validation_options) {
|
23
|
+
{
|
24
|
+
in: %w(big huge),
|
25
|
+
locales: [:de, :en, :it]
|
26
|
+
}
|
27
|
+
}
|
28
|
+
let(:length_validation_options) {
|
29
|
+
{
|
30
|
+
in: 5..11,
|
31
|
+
locales: [:de, :en, :it]
|
32
|
+
}
|
33
|
+
}
|
34
|
+
let(:validation_options) {
|
35
|
+
{
|
36
|
+
presence: [:de, :en, :it],
|
37
|
+
absence: [:pi, :gb, :er],
|
38
|
+
format: format_validation_options,
|
39
|
+
numericality: numericality_validation_options,
|
40
|
+
inclusion: inclusion_validation_options,
|
41
|
+
exclusion: exclusion_validation_options,
|
42
|
+
length: length_validation_options
|
43
|
+
}
|
44
|
+
}
|
45
|
+
|
46
|
+
class BabylonianField < ActiveRecord::Base
|
47
|
+
|
48
|
+
build_babylonian_tower_on :marshes
|
49
|
+
|
50
|
+
class << self
|
51
|
+
attr_accessor :installed_validations
|
52
|
+
def install_validations(options)
|
53
|
+
self.installed_validations = true
|
54
|
+
validates :marshes, locales: options
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
class BabylonianSecondField < ActiveRecord::Base
|
61
|
+
self.table_name = 'babylonian_fields'
|
62
|
+
|
63
|
+
build_babylonian_tower_on :marshes
|
64
|
+
|
65
|
+
class << self
|
66
|
+
attr_accessor :installed_validations
|
67
|
+
def install_validations(options)
|
68
|
+
self.installed_validations = true
|
69
|
+
validates :marshes, locales: options
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class BabylonianThirdField < ActiveRecord::Base
|
75
|
+
self.table_name = 'babylonian_fields'
|
76
|
+
|
77
|
+
build_babylonian_tower_on :marshes
|
78
|
+
|
79
|
+
class << self
|
80
|
+
attr_accessor :installed_validations
|
81
|
+
def install_validations(options)
|
82
|
+
self.installed_validations = true
|
83
|
+
validates :marshes, allow_nil: true, locales: options
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
before(:each) do
|
89
|
+
I18n.stub available_locales: [:de, :en, :it, :pi, :gb, :er]
|
90
|
+
# it is not allowed to access structures created by let in before(:all), but installing the validators on each run is also not
|
91
|
+
# the way to go.
|
92
|
+
BabylonianField.install_validations(validation_options) unless BabylonianField.installed_validations
|
93
|
+
BabylonianSecondField.install_validations(validation_options) unless BabylonianSecondField.installed_validations
|
94
|
+
BabylonianThirdField.install_validations(validation_options) unless BabylonianThirdField.installed_validations
|
95
|
+
end
|
96
|
+
|
97
|
+
describe "rails validations except confirmation, acceptance and uniqueness" do
|
98
|
+
subject { BabylonianField.new }
|
99
|
+
let(:format_validator) { double('format_validator') }
|
100
|
+
let(:inclusion_validator) { double('inclusion_validator') }
|
101
|
+
let(:exclusion_validator) { double('exlusion_validator') }
|
102
|
+
let(:length_validator) { double('length_validator') }
|
103
|
+
let(:presence_validator) { double('presence_validator') }
|
104
|
+
let(:absence_validator) { double('absence_validator') }
|
105
|
+
let(:numericality_validator) { double('absence_validator') }
|
106
|
+
|
107
|
+
it "should pass them on to the rails validators" do
|
108
|
+
[:format, :inclusion, :exclusion, :length, :numericality].each do |validator|
|
109
|
+
options = send(:"#{validator}_validation_options")
|
110
|
+
attributes = options[:locales].map{|l| :"marshes_#{l}"}
|
111
|
+
"ActiveModel::Validations::#{validator.to_s.classify}Validator".constantize.should_receive(:new).with(options.dup.delete_if{|k,v| k == :locales}.merge(attributes: attributes)).and_return(send(:"#{validator}_validator"))
|
112
|
+
send(:"#{validator}_validator").should_receive(:validate).with(subject)
|
113
|
+
end
|
114
|
+
ActiveModel::Validations::PresenceValidator.should_receive(:new).with(attributes: [:marshes_de,:marshes_en,:marshes_it]).and_return(presence_validator)
|
115
|
+
presence_validator.should_receive(:validate).with(subject)
|
116
|
+
unless ActiveRecord::VERSION::MAJOR < 4
|
117
|
+
ActiveModel::Validations::AbsenceValidator.should_receive(:new).with(attributes: [:marshes_pi,:marshes_gb,:marshes_er]).and_return(absence_validator)
|
118
|
+
absence_validator.should_receive(:validate).with(subject)
|
119
|
+
end
|
120
|
+
subject.valid? #=> this will be true since all calls are mocked
|
121
|
+
end
|
122
|
+
context "integration" do
|
123
|
+
context "with defaults" do
|
124
|
+
subject { BabylonianSecondField.new }
|
125
|
+
it "should work for all kinds of errors" do
|
126
|
+
subject.should_not be_valid
|
127
|
+
[:de, :en, :it].each do |lang|
|
128
|
+
subject.errors[:"marshes_#{lang}"].should == ["is not included in the list", "is too short (minimum is 5 characters)", "is not a number", "can't be blank"]
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
context "with allow_nil set to true" do
|
133
|
+
subject { BabylonianThirdField.new }
|
134
|
+
it "should allow blank" do
|
135
|
+
subject.should be_valid
|
136
|
+
subject.errors.should be_blank
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Babylonia::Rails::Validators::LocaleUniquenessValidator do
|
4
|
+
let(:case_sensitive_validation_options) {
|
5
|
+
{
|
6
|
+
uniqueness: {
|
7
|
+
locales: [:de, :en, :fr]
|
8
|
+
}
|
9
|
+
}
|
10
|
+
}
|
11
|
+
let(:case_insensitive_validation_options) {
|
12
|
+
{
|
13
|
+
uniqueness: {
|
14
|
+
case_sensitive: false,
|
15
|
+
locales: [:de, :en, :fr]
|
16
|
+
}
|
17
|
+
}
|
18
|
+
}
|
19
|
+
let(:scoped_validation_options) {
|
20
|
+
{
|
21
|
+
uniqueness: {
|
22
|
+
scope: :some_value,
|
23
|
+
case_sensitive: false,
|
24
|
+
locales: [:de, :en, :fr]
|
25
|
+
}
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
class BabylonianUniqueField < ActiveRecord::Base
|
30
|
+
self.table_name = 'babylonian_fields'
|
31
|
+
|
32
|
+
build_babylonian_tower_on :marshes
|
33
|
+
|
34
|
+
class << self
|
35
|
+
attr_accessor :installed_validations
|
36
|
+
def install_validations(options)
|
37
|
+
self.installed_validations = true
|
38
|
+
validates :marshes, locales: options
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
class BabylonianUniqueSecondField < ActiveRecord::Base
|
45
|
+
self.table_name = 'babylonian_fields'
|
46
|
+
|
47
|
+
build_babylonian_tower_on :marshes
|
48
|
+
|
49
|
+
class << self
|
50
|
+
attr_accessor :installed_validations
|
51
|
+
def install_validations(options)
|
52
|
+
self.installed_validations = true
|
53
|
+
validates :marshes, locales: options
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class BabylonianUniqueThirdField < ActiveRecord::Base
|
59
|
+
self.table_name = 'babylonian_fields'
|
60
|
+
|
61
|
+
build_babylonian_tower_on :marshes
|
62
|
+
|
63
|
+
class << self
|
64
|
+
attr_accessor :installed_validations
|
65
|
+
def install_validations(options)
|
66
|
+
self.installed_validations = true
|
67
|
+
validates :marshes, allow_nil: true, locales: options
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
before(:each) do
|
73
|
+
I18n.stub available_locales: [:de, :en, :it, :pi, :fr, :er]
|
74
|
+
# it is not allowed to access structures created by let in before(:all), but installing the validators on each run is also not
|
75
|
+
# the way to go.
|
76
|
+
BabylonianUniqueField.install_validations(case_insensitive_validation_options) unless BabylonianUniqueField.installed_validations
|
77
|
+
BabylonianUniqueSecondField.install_validations(case_sensitive_validation_options) unless BabylonianUniqueSecondField.installed_validations
|
78
|
+
BabylonianUniqueThirdField.install_validations(scoped_validation_options) unless BabylonianUniqueThirdField.installed_validations
|
79
|
+
end
|
80
|
+
|
81
|
+
describe "case insensitive uniqueness validation" do
|
82
|
+
let(:attributes) { { marshes: {en: "Hello, I'm a string!", de: "Hallo,\nIch bin ein Faden!", fr: "Bonjour: Je suis un fil" } } }
|
83
|
+
let(:duplicate_attributes) { { marshes: {en: "hello, I'm a string!", de: "Hallo,\nich bin ein faden!", fr: "Bonjour: je suis un fil" } } }
|
84
|
+
context "with an existing record" do
|
85
|
+
let!(:existing) { BabylonianUniqueField.create(attributes) }
|
86
|
+
subject { BabylonianUniqueField.new(duplicate_attributes) }
|
87
|
+
after(:each) do
|
88
|
+
existing.destroy
|
89
|
+
end
|
90
|
+
it "should validate the records uniqueness" do
|
91
|
+
subject.should_not be_valid
|
92
|
+
subject.errors.full_messages.should == ["Marshes de has already been taken", "Marshes en has already been taken", "Marshes fr has already been taken"]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
context "with no existing record" do
|
96
|
+
subject { BabylonianUniqueField.new(duplicate_attributes) }
|
97
|
+
it "should validate the records uniqueness" do
|
98
|
+
subject.should be_valid
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe "case sensitive uniqueness validation" do
|
104
|
+
let(:attributes) { { marshes: {en: "Hello, I'm a string!", de: "Hallo,\nIch bin ein Faden!", fr: "Bonjour: Je suis un fil" } } }
|
105
|
+
let(:other_case_attributes) { { marshes: {en: "hello, I'm a string!", de: "Hallo,\nich bin ein faden!", fr: "Bonjour: je suis un fil" } } }
|
106
|
+
context "with an existing record" do
|
107
|
+
let!(:existing) { BabylonianUniqueSecondField.create(attributes) }
|
108
|
+
subject { BabylonianUniqueSecondField.new(attributes) }
|
109
|
+
after(:each) do
|
110
|
+
existing.destroy
|
111
|
+
end
|
112
|
+
it "should validate the records uniqueness" do
|
113
|
+
subject.should_not be_valid
|
114
|
+
subject.errors.full_messages.should == ["Marshes de has already been taken", "Marshes en has already been taken", "Marshes fr has already been taken"]
|
115
|
+
end
|
116
|
+
end
|
117
|
+
context "with an existing record with differently cased attributes" do
|
118
|
+
let!(:existing) { BabylonianUniqueSecondField.create(attributes) }
|
119
|
+
subject { BabylonianUniqueSecondField.new(other_case_attributes) }
|
120
|
+
after(:each) do
|
121
|
+
existing.destroy
|
122
|
+
end
|
123
|
+
it "should validate the records uniqueness" do
|
124
|
+
subject.should be_valid
|
125
|
+
end
|
126
|
+
end
|
127
|
+
context "with no existing record" do
|
128
|
+
subject { BabylonianUniqueSecondField.new(attributes) }
|
129
|
+
it "should validate the records uniqueness" do
|
130
|
+
subject.should be_valid
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe "scoped uniqueness validation" do
|
136
|
+
let(:attributes) { { marshes: {en: "Hello, I'm a string!", de: "Hallo,\nIch bin ein Faden!", fr: "Bonjour: Je suis un fil" }, some_value: true } }
|
137
|
+
let(:same_scope_attributes) { { marshes: {en: "hello, I'm a string!", de: "Hallo,\nich bin ein faden!", fr: "Bonjour: je suis un fil" }, some_value: true } }
|
138
|
+
let(:other_scope_attributes) { { marshes: {en: "hello, I'm a string!", de: "Hallo,\nich bin ein faden!", fr: "Bonjour: je suis un fil" }, some_value: false } }
|
139
|
+
context "with an existing record" do
|
140
|
+
let!(:existing) { BabylonianUniqueThirdField.create(attributes) }
|
141
|
+
subject { BabylonianUniqueThirdField.new(same_scope_attributes) }
|
142
|
+
after(:each) do
|
143
|
+
existing.destroy
|
144
|
+
end
|
145
|
+
it "should validate the records uniqueness" do
|
146
|
+
subject.should_not be_valid
|
147
|
+
subject.errors.full_messages.should == ["Marshes de has already been taken", "Marshes en has already been taken", "Marshes fr has already been taken"]
|
148
|
+
end
|
149
|
+
end
|
150
|
+
context "with an existing record in another scope" do
|
151
|
+
let!(:existing) { BabylonianUniqueThirdField.create(attributes) }
|
152
|
+
subject { BabylonianUniqueThirdField.new(other_scope_attributes) }
|
153
|
+
after(:each) do
|
154
|
+
existing.destroy
|
155
|
+
end
|
156
|
+
it "should validate the records uniqueness" do
|
157
|
+
subject.should be_valid
|
158
|
+
end
|
159
|
+
end
|
160
|
+
context "with no existing record" do
|
161
|
+
subject { BabylonianUniqueThirdField.new(attributes) }
|
162
|
+
it "should validate the records uniqueness" do
|
163
|
+
subject.should be_valid
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -18,12 +18,13 @@ module HelperMethods
|
|
18
18
|
|
19
19
|
end
|
20
20
|
|
21
|
+
require File.expand_path('./support/tables.rb', File.dirname(__FILE__))
|
22
|
+
|
21
23
|
RSpec.configure do |configuration|
|
22
24
|
include HelperMethods
|
23
25
|
configuration.before(:suite) do
|
24
26
|
ActiveRecord::Base.configurations = YAML.load_file(File.expand_path('./support/database.yml', File.dirname(__FILE__)))
|
25
27
|
ActiveRecord::Base.establish_connection(:test)
|
26
|
-
require File.expand_path('./support/tables.rb', File.dirname(__FILE__))
|
27
28
|
|
28
29
|
CreateTestTables.new.up
|
29
30
|
end
|
data/spec/support/tables.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
class CreateTestTables < ActiveRecord::Migration
|
2
2
|
def up
|
3
|
-
create_table :babylonian_fields do |t|
|
3
|
+
create_table :babylonian_fields, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin' do |t|
|
4
4
|
t.text :marshes
|
5
|
+
t.boolean :some_value
|
5
6
|
t.timestamps
|
6
7
|
end
|
7
8
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: babylonia-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Beat Richartz
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-11-
|
11
|
+
date: 2013-11-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: babylonia
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - '>='
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.
|
19
|
+
version: 0.1.1
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - '>='
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.
|
26
|
+
version: 0.1.1
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: activerecord
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -71,11 +71,14 @@ files:
|
|
71
71
|
- lib/babylonia/rails.rb
|
72
72
|
- lib/babylonia/rails/class_methods.rb
|
73
73
|
- lib/babylonia/rails/integration.rb
|
74
|
-
- lib/babylonia/rails/
|
74
|
+
- lib/babylonia/rails/validators.rb
|
75
|
+
- lib/babylonia/rails/validators/locale_validator.rb
|
76
|
+
- lib/babylonia/rails/validators/uniqueness_validator.rb
|
75
77
|
- lib/babylonia/rails/version.rb
|
76
78
|
- spec/babylonia-rails/integration_spec.rb
|
77
79
|
- spec/babylonia-rails/persistence_spec.rb
|
78
|
-
- spec/babylonia-rails/
|
80
|
+
- spec/babylonia-rails/validators/locale_validator_spec.rb
|
81
|
+
- spec/babylonia-rails/validators/uniqueness_validator_spec.rb
|
79
82
|
- spec/spec_helper.rb
|
80
83
|
- spec/support/database.yml
|
81
84
|
- spec/support/tables.rb
|
@@ -106,7 +109,8 @@ summary: Let there be languages for your rails users!
|
|
106
109
|
test_files:
|
107
110
|
- spec/babylonia-rails/integration_spec.rb
|
108
111
|
- spec/babylonia-rails/persistence_spec.rb
|
109
|
-
- spec/babylonia-rails/
|
112
|
+
- spec/babylonia-rails/validators/locale_validator_spec.rb
|
113
|
+
- spec/babylonia-rails/validators/uniqueness_validator_spec.rb
|
110
114
|
- spec/spec_helper.rb
|
111
115
|
- spec/support/database.yml
|
112
116
|
- spec/support/tables.rb
|
@@ -1,42 +0,0 @@
|
|
1
|
-
class LanguagesValidator < ActiveModel::EachValidator
|
2
|
-
def validate_each(record, attribute, value)
|
3
|
-
validate_languages_present(record, attribute, options[:present].sort) if options[:present]
|
4
|
-
validate_length_in_range(record, attribute, options[:length]) if options[:length].is_a?(Range)
|
5
|
-
end
|
6
|
-
|
7
|
-
private
|
8
|
-
|
9
|
-
def validate_languages_present(record, attribute, languages)
|
10
|
-
is = record_attribute_languages(record, attribute)
|
11
|
-
unless is == languages
|
12
|
-
record.errors[attribute] << (options[:message] || "should be filled in #{(languages - is).map(&:upcase).to_sentence}")
|
13
|
-
(languages - is).each do |lang|
|
14
|
-
record.errors[:"#{attribute}_#{lang}"] << (options[:message] || "should be filled")
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
def validate_length_in_range(record, attribute, range)
|
20
|
-
all = record_attribute_languages(record, attribute)
|
21
|
-
in_range = keys_not_matching(record, attribute){|k,v| !range.include?(v.size) }
|
22
|
-
unless all.empty? || all == in_range
|
23
|
-
record.errors[attribute] << (options[:message] || "should be between #{range.first} and #{range.last} characters in #{(all - in_range).map(&:upcase).to_sentence}")
|
24
|
-
(all - in_range).each do |lang|
|
25
|
-
record.errors[:"#{attribute}_#{lang}"] << (options[:message] || "should be between #{range.first} and #{range.last} characters")
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def keys_not_matching record, attribute, &block
|
31
|
-
not_matching = record_attribute_hash(record, attribute).dup.delete_if &block
|
32
|
-
not_matching.keys.sort
|
33
|
-
end
|
34
|
-
|
35
|
-
def record_attribute_hash record, attribute
|
36
|
-
record.send(:"#{attribute}_hash")
|
37
|
-
end
|
38
|
-
|
39
|
-
def record_attribute_languages record, attribute
|
40
|
-
record.send(:"#{attribute}_languages").sort
|
41
|
-
end
|
42
|
-
end
|
@@ -1,76 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe LanguagesValidator do
|
4
|
-
|
5
|
-
class BabylonianField < ActiveRecord::Base
|
6
|
-
|
7
|
-
build_babylonian_tower_on :marshes
|
8
|
-
validates :marshes, languages: { present: [:de, :en, :it], length: 5..11 }
|
9
|
-
|
10
|
-
end
|
11
|
-
|
12
|
-
describe "validations" do
|
13
|
-
subject { BabylonianField.new(marshes: "Hello") }
|
14
|
-
before(:each) do
|
15
|
-
I18n.stub locale: :en, available_locales: [:de, :en, :it]
|
16
|
-
end
|
17
|
-
context "presence validations" do
|
18
|
-
context "when invalid" do
|
19
|
-
it "should indicate which languages are not translated" do
|
20
|
-
subject.should_not be_valid
|
21
|
-
subject.errors[:marshes].first.should == "should be filled in DE and IT"
|
22
|
-
subject.errors[:marshes_en].should be_empty
|
23
|
-
subject.errors[:marshes_de].first.should == "should be filled"
|
24
|
-
subject.errors[:marshes_it].first.should == "should be filled"
|
25
|
-
end
|
26
|
-
it "should indicate remaining incomplete languages if some are updated" do
|
27
|
-
subject.marshes = {it: 'SOMETHING'}
|
28
|
-
subject.should_not be_valid
|
29
|
-
subject.errors[:marshes].first.should == "should be filled in DE"
|
30
|
-
subject.errors[:marshes_en].should be_empty
|
31
|
-
subject.errors[:marshes_it].should be_empty
|
32
|
-
subject.errors[:marshes_de].first.should == "should be filled"
|
33
|
-
end
|
34
|
-
it "should work when attributes are deleted" do
|
35
|
-
subject.marshes = ''
|
36
|
-
subject.should_not be_valid
|
37
|
-
subject.errors[:marshes].first.should == "should be filled in DE, EN, and IT"
|
38
|
-
subject.errors[:marshes_en].first.should == "should be filled"
|
39
|
-
subject.errors[:marshes_de].first.should == "should be filled"
|
40
|
-
subject.errors[:marshes_it].first.should == "should be filled"
|
41
|
-
end
|
42
|
-
end
|
43
|
-
context "when valid" do
|
44
|
-
before(:each) do
|
45
|
-
subject.marshes = {en: 'Hello', de: 'Hello', it: 'Hello'}
|
46
|
-
end
|
47
|
-
it "should be valid" do
|
48
|
-
subject.should be_valid
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
context "length validations" do
|
53
|
-
before(:each) do
|
54
|
-
subject.marshes = {en: 'H', de: 'Hello', it: 'Hlo'}
|
55
|
-
end
|
56
|
-
context "when invalid" do
|
57
|
-
it "should indicate that the languages that are not in length" do
|
58
|
-
subject.should_not be_valid
|
59
|
-
subject.errors[:marshes].first.should == "should be between 5 and 11 characters in EN and IT"
|
60
|
-
subject.errors[:marshes_en].first.should == "should be between 5 and 11 characters"
|
61
|
-
subject.errors[:marshes_it].first.should == "should be between 5 and 11 characters"
|
62
|
-
end
|
63
|
-
end
|
64
|
-
context "when valid" do
|
65
|
-
before(:each) do
|
66
|
-
subject.marshes = {en: 'HelloHelloH', de: 'Hello', it: 'Hello'}
|
67
|
-
end
|
68
|
-
it "should be valid" do
|
69
|
-
subject.should be_valid
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
|
76
|
-
end
|