glebtv-mongoid-rspec 1.12.0
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 +7 -0
- data/.document +5 -0
- data/.gitignore +6 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +14 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +203 -0
- data/Rakefile +18 -0
- data/gemfiles/mongoid-3.1.gemfile +5 -0
- data/gemfiles/mongoid-4.0.gemfile +5 -0
- data/glebtv-mongoid-rspec.gemspec +25 -0
- data/lib/glebtv-mongoid-rspec.rb +1 -0
- data/lib/matchers/accept_nested_attributes.rb +65 -0
- data/lib/matchers/allow_mass_assignment.rb +101 -0
- data/lib/matchers/associations.rb +313 -0
- data/lib/matchers/collections.rb +9 -0
- data/lib/matchers/document.rb +160 -0
- data/lib/matchers/indexes.rb +81 -0
- data/lib/matchers/validations.rb +78 -0
- data/lib/matchers/validations/acceptance_of.rb +9 -0
- data/lib/matchers/validations/associated.rb +19 -0
- data/lib/matchers/validations/confirmation_of.rb +9 -0
- data/lib/matchers/validations/custom_validation_of.rb +47 -0
- data/lib/matchers/validations/exclusion_of.rb +49 -0
- data/lib/matchers/validations/format_of.rb +71 -0
- data/lib/matchers/validations/inclusion_of.rb +49 -0
- data/lib/matchers/validations/length_of.rb +147 -0
- data/lib/matchers/validations/numericality_of.rb +74 -0
- data/lib/matchers/validations/presence_of.rb +9 -0
- data/lib/matchers/validations/uniqueness_of.rb +82 -0
- data/lib/matchers/validations/with_message.rb +27 -0
- data/lib/mongoid-rspec.rb +33 -0
- data/lib/mongoid-rspec/version.rb +5 -0
- data/spec/models/article.rb +29 -0
- data/spec/models/comment.rb +6 -0
- data/spec/models/log.rb +4 -0
- data/spec/models/movie_article.rb +8 -0
- data/spec/models/permalink.rb +5 -0
- data/spec/models/person.rb +10 -0
- data/spec/models/profile.rb +16 -0
- data/spec/models/record.rb +5 -0
- data/spec/models/site.rb +9 -0
- data/spec/models/user.rb +36 -0
- data/spec/spec_helper.rb +34 -0
- data/spec/unit/accept_nested_attributes_spec.rb +12 -0
- data/spec/unit/associations_spec.rb +42 -0
- data/spec/unit/collections_spec.rb +7 -0
- data/spec/unit/document_spec.rb +27 -0
- data/spec/unit/indexes_spec.rb +13 -0
- data/spec/unit/validations_spec.rb +52 -0
- data/spec/validators/ssn_validator.rb +16 -0
- metadata +163 -0
@@ -0,0 +1,74 @@
|
|
1
|
+
module Mongoid
|
2
|
+
module Matchers
|
3
|
+
module Validations
|
4
|
+
class ValidateNumericalityOfMatcher < HaveValidationMatcher
|
5
|
+
@@allowed_options = [:equal_to, :greater_than, :greater_than_or_equal_to, :less_than, :less_than_or_equal_to,
|
6
|
+
:even, :odd, :only_integer, :allow_nil, :nil]
|
7
|
+
|
8
|
+
def initialize(field)
|
9
|
+
super(field, :numericality)
|
10
|
+
@options = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_allow(options)
|
14
|
+
options[:equal_to] = options if options.is_a?(Numeric)
|
15
|
+
options[:allow_nil] = options.delete(:nil) if options.has_key?(:nil)
|
16
|
+
raise ArgumentError, "validate_numericality_of#to_allow requires a Hash parameter containing any of the following keys: " <<
|
17
|
+
@@allowed_options.map(&:inspect).join(", ") if !options.is_a?(Hash) or options.empty? or (options.keys - @@allowed_options).any?
|
18
|
+
@options.merge!(options)
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def matches?(actual)
|
23
|
+
return false unless result = super(actual)
|
24
|
+
|
25
|
+
@@allowed_options.each do |comparator|
|
26
|
+
if @options.has_key?(comparator) and !([:even, :odd, :only_integer].include?(comparator) and !@validator.options.include?(comparator))
|
27
|
+
result &= (@validator.options[comparator] == @options[comparator])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
@positive_result_message <<= options_message(@validator.options)
|
31
|
+
@negative_result_message <<= options_message(@validator.options)
|
32
|
+
result
|
33
|
+
end
|
34
|
+
|
35
|
+
def description
|
36
|
+
super << options_message(@options)
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
def options_message(options)
|
42
|
+
type_msg = []
|
43
|
+
comp_msg = []
|
44
|
+
options.each_pair do |key, value|
|
45
|
+
case key
|
46
|
+
when :allow_nil
|
47
|
+
when :only_integer
|
48
|
+
type_msg << "integer" if value
|
49
|
+
when :odd, :even
|
50
|
+
type_msg << "#{key.to_s}-numbered" if value
|
51
|
+
else
|
52
|
+
comp_msg << "#{key.to_s.gsub("_", " ")} #{value.inspect}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
allow_nil = (options[:allow_nil] ? "nil" : "non-nil") if options.has_key?(:allow_nil)
|
56
|
+
["", "allowing", allow_nil, type_msg.any? ? type_msg.to_sentence : nil, "values", comp_msg.any? ? comp_msg.to_sentence : nil].compact.join(" ")
|
57
|
+
end
|
58
|
+
|
59
|
+
def method_missing(m, *args, &block)
|
60
|
+
if @@allowed_options.include?(m.to_sym)
|
61
|
+
raise ArgumentError, "wrong number of arguments (#{args.length} for 1)" if args.length > 1
|
62
|
+
send :to_allow, m.to_sym => args.first
|
63
|
+
else
|
64
|
+
super
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def validate_numericality_of(field)
|
70
|
+
ValidateNumericalityOfMatcher.new(field)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Mongoid
|
2
|
+
module Matchers
|
3
|
+
module Validations
|
4
|
+
class ValidateUniquenessOfMatcher < HaveValidationMatcher
|
5
|
+
include WithMessage
|
6
|
+
def initialize(field)
|
7
|
+
super(field, :uniqueness)
|
8
|
+
end
|
9
|
+
|
10
|
+
def scoped_to(*scope)
|
11
|
+
@scope = [scope].flatten.map(&:to_sym)
|
12
|
+
self
|
13
|
+
end
|
14
|
+
alias_method :scoped_on, :scoped_to
|
15
|
+
|
16
|
+
def case_insensitive
|
17
|
+
@case_insensitive = true
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def allow_blank?(allow_blank)
|
22
|
+
@allow_blank = allow_blank
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
def matches?(actual)
|
27
|
+
return false unless @result = super(actual)
|
28
|
+
|
29
|
+
check_scope if @scope
|
30
|
+
check_allow_blank if @allow_blank
|
31
|
+
check_case_sensitivity if @case_insensitive
|
32
|
+
check_expected_message if @expected_message
|
33
|
+
|
34
|
+
@result
|
35
|
+
end
|
36
|
+
|
37
|
+
def description
|
38
|
+
options_desc = []
|
39
|
+
options_desc << " scoped to #{@scope.inspect}" if @scope
|
40
|
+
options_desc << " allowing blank values" if @allow_blank
|
41
|
+
options_desc << " allowing case insensitive values" if @case_insensitive
|
42
|
+
options_desc << " with message '#{@expected_message}'" if @expected_message
|
43
|
+
super << options_desc.to_sentence
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def check_allow_blank
|
49
|
+
if @validator.options[:allow_blank] == @allow_blank
|
50
|
+
@positive_result_message << " with blank values allowed"
|
51
|
+
else
|
52
|
+
@negative_result_message << " with no blank values allowed"
|
53
|
+
@result = false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def check_scope
|
58
|
+
message = " scope to #{@validator.options[:scope]}"
|
59
|
+
if [@validator.options[:scope]].flatten.map(&:to_sym) == @scope
|
60
|
+
@positive_result_message << message
|
61
|
+
else
|
62
|
+
@negative_result_message << message
|
63
|
+
@result = false
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def check_case_sensitivity
|
68
|
+
if @validator.options[:case_sensitive] == false
|
69
|
+
@positive_result_message << " with case insensitive values"
|
70
|
+
else
|
71
|
+
@negative_result_message << " without case insensitive values"
|
72
|
+
@result = false
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def validate_uniqueness_of(field)
|
78
|
+
ValidateUniquenessOfMatcher.new(field)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Mongoid
|
2
|
+
module Matchers
|
3
|
+
module Validations
|
4
|
+
module WithMessage
|
5
|
+
def with_message(message)
|
6
|
+
@expected_message = message
|
7
|
+
self
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def check_expected_message
|
13
|
+
actual_message = @validator.options[:message]
|
14
|
+
if actual_message.nil?
|
15
|
+
@negative_result_message << " with no custom message"
|
16
|
+
@result = false
|
17
|
+
elsif actual_message == @expected_message
|
18
|
+
@positive_result_message << " with custom message '#{@expected_message}'"
|
19
|
+
else
|
20
|
+
@negative_result_message << " got message '#{actual_message}'"
|
21
|
+
@result = false
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
require 'mongoid'
|
4
|
+
require 'rspec/core'
|
5
|
+
require 'rspec/expectations'
|
6
|
+
require 'rspec/mocks'
|
7
|
+
require "active_model"
|
8
|
+
require 'matchers/document'
|
9
|
+
require 'matchers/associations'
|
10
|
+
require 'matchers/collections'
|
11
|
+
require 'matchers/indexes'
|
12
|
+
require 'matchers/allow_mass_assignment'
|
13
|
+
require 'matchers/accept_nested_attributes'
|
14
|
+
require 'matchers/validations'
|
15
|
+
require 'matchers/validations/with_message'
|
16
|
+
require 'matchers/validations/associated'
|
17
|
+
require 'matchers/validations/confirmation_of'
|
18
|
+
require 'matchers/validations/exclusion_of'
|
19
|
+
require 'matchers/validations/format_of'
|
20
|
+
require 'matchers/validations/inclusion_of'
|
21
|
+
require 'matchers/validations/length_of'
|
22
|
+
require 'matchers/validations/numericality_of'
|
23
|
+
require 'matchers/validations/presence_of'
|
24
|
+
require 'matchers/validations/uniqueness_of'
|
25
|
+
require 'matchers/validations/acceptance_of'
|
26
|
+
require 'matchers/validations/custom_validation_of'
|
27
|
+
|
28
|
+
module Mongoid
|
29
|
+
module Matchers
|
30
|
+
include Mongoid::Matchers::Associations
|
31
|
+
include Mongoid::Matchers::Validations
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class Article
|
2
|
+
include Mongoid::Document
|
3
|
+
include Mongoid::Timestamps
|
4
|
+
|
5
|
+
field :title, localize: true
|
6
|
+
field :content
|
7
|
+
field :published, type: Boolean, default: false
|
8
|
+
field :allow_comments, type: Boolean, default: true
|
9
|
+
field :number_of_comments, type: Integer
|
10
|
+
field :status, type: Symbol
|
11
|
+
|
12
|
+
embeds_many :comments, cascade_callbacks: true
|
13
|
+
embeds_one :permalink
|
14
|
+
belongs_to :author, class_name: 'User', inverse_of: :articles, index: true
|
15
|
+
|
16
|
+
validates :title, presence: true
|
17
|
+
|
18
|
+
validates_inclusion_of :status, in: [:pending], on: :create
|
19
|
+
validates_inclusion_of :status, in: [:approved, :rejected ], on: :update
|
20
|
+
|
21
|
+
validates_length_of :title, within: 8..16
|
22
|
+
validates_length_of :content, minimum: 200
|
23
|
+
|
24
|
+
index({ title: 1 }, { unique: true, background: true, drop_dups: true })
|
25
|
+
index({ published: 1 })
|
26
|
+
index({ 'permalink._id' => 1 })
|
27
|
+
|
28
|
+
accepts_nested_attributes_for :permalink
|
29
|
+
end
|
data/spec/models/log.rb
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
class MovieArticle < Article
|
2
|
+
|
3
|
+
field :rating, type: Float
|
4
|
+
field :classification, type: Integer
|
5
|
+
|
6
|
+
validates :rating, numericality: { greater_than: 0, less_than_or_equal_to: 5 }
|
7
|
+
validates :classification, numericality: { even: true, only_integer: true, allow_nil: false }
|
8
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class Profile
|
2
|
+
include Mongoid::Document
|
3
|
+
|
4
|
+
field :first_name
|
5
|
+
field :last_name
|
6
|
+
field :age
|
7
|
+
field :hobbies, type: Array, default: []
|
8
|
+
|
9
|
+
embedded_in :user, inverse_of: :profile
|
10
|
+
|
11
|
+
validates :age, numericality: { greater_than: 0 }
|
12
|
+
validates :terms_of_service, acceptance: true
|
13
|
+
validates :hobbies, length: { minimum: 1, message: "requires at least one hobby" }
|
14
|
+
|
15
|
+
index({ first_name: 1, last_name: 1 })
|
16
|
+
end
|
data/spec/models/site.rb
ADDED
data/spec/models/user.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
class User
|
2
|
+
include Mongoid::Document
|
3
|
+
include Mongoid::Timestamps::Created
|
4
|
+
|
5
|
+
field :login
|
6
|
+
field :email
|
7
|
+
field :role
|
8
|
+
field :age, type: Integer
|
9
|
+
field :password, type: String
|
10
|
+
field :provider_uid
|
11
|
+
field :locale
|
12
|
+
|
13
|
+
belongs_to :site, inverse_of: :users
|
14
|
+
has_many :articles, foreign_key: :author_id, order: :title
|
15
|
+
has_many :comments, dependent: :destroy, autosave: true
|
16
|
+
has_and_belongs_to_many :children, class_name: "User"
|
17
|
+
has_one :record, autobuild: true
|
18
|
+
|
19
|
+
embeds_one :profile
|
20
|
+
|
21
|
+
validates :login, presence: true, uniqueness: { scope: :site }, format: { with: /\A[\w\-]+\z/ }, exclusion: { in: ["super", "index", "edit"] }
|
22
|
+
validates :email, uniqueness: { case_sensitive: false, scope: :site, message: "is already taken" }, confirmation: true
|
23
|
+
validates :role, presence: true, inclusion: { in: ["admin", "moderator", "member"] }
|
24
|
+
validates :profile, presence: true, associated: true
|
25
|
+
validates :age, presence: true, numericality: true, inclusion: { in: 23..42 }, on: [:create, :update]
|
26
|
+
validates :password, presence: true, on: [:create, :update]
|
27
|
+
validates :password, exclusion: { in: ->(user) { ['password'] } }
|
28
|
+
validates :provider_uid, presence: true
|
29
|
+
validates :locale, inclusion: { in: ->(user) { [:en, :ru] } }
|
30
|
+
|
31
|
+
accepts_nested_attributes_for :articles, :comments
|
32
|
+
|
33
|
+
def admin?
|
34
|
+
false
|
35
|
+
end
|
36
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "lib"))
|
3
|
+
MODELS = File.join(File.dirname(__FILE__), "models")
|
4
|
+
$LOAD_PATH.unshift(MODELS)
|
5
|
+
VALIDATORS = File.join(File.dirname(__FILE__), "validators")
|
6
|
+
$LOAD_PATH.unshift(VALIDATORS)
|
7
|
+
|
8
|
+
require "rubygems"
|
9
|
+
require "bundler"
|
10
|
+
Bundler.setup
|
11
|
+
|
12
|
+
require 'mongoid'
|
13
|
+
require 'rspec'
|
14
|
+
require 'rspec/core'
|
15
|
+
require 'rspec/expectations'
|
16
|
+
|
17
|
+
I18n.enforce_available_locales = true
|
18
|
+
|
19
|
+
Mongoid.configure do |config|
|
20
|
+
config.connect_to("mongoid-rspec-test")
|
21
|
+
end
|
22
|
+
|
23
|
+
Dir[ File.join(MODELS, "*.rb") ].sort.each { |file| require File.basename(file) }
|
24
|
+
|
25
|
+
require 'mongoid-rspec'
|
26
|
+
|
27
|
+
RSpec.configure do |config|
|
28
|
+
config.include RSpec::Matchers
|
29
|
+
config.include Mongoid::Matchers
|
30
|
+
config.mock_with :rspec
|
31
|
+
config.after :all do
|
32
|
+
Mongoid::Config.purge!
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "AcceptsNestedAttributes" do
|
4
|
+
describe User do
|
5
|
+
it { should accept_nested_attributes_for(:articles) }
|
6
|
+
it { should accept_nested_attributes_for(:comments) }
|
7
|
+
end
|
8
|
+
|
9
|
+
describe Article do
|
10
|
+
it { should accept_nested_attributes_for(:permalink) }
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Associations" do
|
4
|
+
describe User do
|
5
|
+
it { should have_many(:articles).with_foreign_key(:author_id).ordered_by(:title) }
|
6
|
+
|
7
|
+
it { should have_one(:record).with_autobuild }
|
8
|
+
|
9
|
+
it { should have_many(:comments).with_dependent(:destroy).with_autosave }
|
10
|
+
|
11
|
+
it { should embed_one(:profile) }
|
12
|
+
|
13
|
+
it { should have_and_belong_to_many(:children).of_type(User) }
|
14
|
+
end
|
15
|
+
|
16
|
+
describe Profile do
|
17
|
+
it { should be_embedded_in(:user).as_inverse_of(:profile) }
|
18
|
+
end
|
19
|
+
|
20
|
+
describe Article do
|
21
|
+
it { should belong_to(:author).of_type(User).as_inverse_of(:articles).with_index }
|
22
|
+
it { should embed_many(:comments).with_cascading_callbacks }
|
23
|
+
it { should embed_one(:permalink) }
|
24
|
+
end
|
25
|
+
|
26
|
+
describe Comment do
|
27
|
+
it { should be_embedded_in(:article).as_inverse_of(:comments).with_polymorphism }
|
28
|
+
it { should belong_to(:user).as_inverse_of(:comments) }
|
29
|
+
end
|
30
|
+
|
31
|
+
describe Record do
|
32
|
+
it { should belong_to(:user).as_inverse_of(:record) }
|
33
|
+
end
|
34
|
+
|
35
|
+
describe Permalink do
|
36
|
+
it { should be_embedded_in(:linkable).as_inverse_of(:link) }
|
37
|
+
end
|
38
|
+
|
39
|
+
describe Site do
|
40
|
+
it { should have_many(:users).as_inverse_of(:site).ordered_by(:email.desc) }
|
41
|
+
end
|
42
|
+
end
|