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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 23b9f13165bbb20bfdb7df78223d70aeac3a2786
4
- data.tar.gz: 9852997ff2602bb4a6fdc3df158849ac70dfcba1
3
+ metadata.gz: 23a9d0abe039872a11f1cc2e55c0d4d1e50898f1
4
+ data.tar.gz: 256aedb91e172f3bdcc51d5e03de89dd6348408f
5
5
  SHA512:
6
- metadata.gz: 75121a38895577938c5c73c80118aa150621435dd096a28d4b720de9a7ccf0a509ac17f5b48ce0ce8edf3f532b0799eabafe5a3976a5dccd6720f0dbb6bd5812
7
- data.tar.gz: 8e1847e0126bd7f30da95912610795719f64e3411b1b69c6f7295bc103ac27f2f5c401becbfe8da221720ead3eb02a09a6c91b631fb182995eed8da2f55c5c7d
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 ride the rails to babylonia
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
- validate :grass, languages: {present: [:en, :de, :it], length: 2..1000}
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
- I18n.locale #=> :en, so it will set en
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
- babylonian_field = BabylonianFields.new grass: 'Hello'
22
-
23
- babylonian_field.valid? #=> false
24
- babylonian_field.errors.full_messages #=> "Grass should also be translated in DE and IT"
25
- babylonian_field.grass = {de: 'DEUTSCH', it: 'I'}
26
- babylonian_field.valid? #=> false
27
- babylonian_field.errors.full_messages #=> "Grass should be between 2 an 1000 characters in IT"
28
- babylonian_field.grass = {it: 'IT'}
29
- babylonian_field.valid? #=> true
30
-
31
- == For Basic Usage
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
@@ -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.0.3"
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
@@ -1,4 +1,4 @@
1
1
  require 'babylonia'
2
2
  require "babylonia/rails/class_methods"
3
+ require "babylonia/rails/validators"
3
4
  require "babylonia/rails/integration"
4
- require "babylonia/rails/validator"
@@ -1,4 +1,5 @@
1
1
  require 'babylonia'
2
2
  require 'active_record'
3
3
 
4
- ActiveRecord::Base.send(:extend, Babylonia::ClassMethods)
4
+ ActiveRecord::Base.send(:extend, Babylonia::ClassMethods)
5
+ ActiveRecord::Base.send(:include, Babylonia::Rails::Validators)
@@ -0,0 +1,2 @@
1
+ require 'babylonia/rails/validators/locale_validator'
2
+ require 'babylonia/rails/validators/uniqueness_validator'
@@ -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
@@ -1,5 +1,5 @@
1
1
  module Babylonia
2
2
  module Rails
3
- VERSION = '0.0.2'
3
+ VERSION = '0.1.2'
4
4
  end
5
5
  end
@@ -2,13 +2,13 @@ require 'spec_helper'
2
2
 
3
3
  describe "Integration" do
4
4
 
5
- class BabylonianField < ActiveRecord::Base
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
- BabylonianField.should be_respond_to(:build_babylonian_tower_on)
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 LanguagesValidator do
3
+ describe Babylonia::Rails::Validators::LocalesValidator do
4
4
 
5
- class BabylonianField < ActiveRecord::Base
5
+ class BabylonianPersistentField < ActiveRecord::Base
6
+ self.table_name = 'babylonian_fields'
6
7
 
7
8
  build_babylonian_tower_on :marshes
8
- validates :marshes, languages: { present: [:de, :en, :it], length: 5..11 }
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 { BabylonianField.new(marshes: {en: 'Hello', de: 'Hello', it: 'Hello'})}
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
@@ -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.0.2
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-20 00:00:00.000000000 Z
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.0.3
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.0.3
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/validator.rb
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/validator_spec.rb
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/validator_spec.rb
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