mongoid-rspec 1.5.0 → 1.11.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.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +14 -2
  6. data/README.md +188 -108
  7. data/lib/matchers/accept_nested_attributes.rb +65 -0
  8. data/lib/matchers/associations.rb +124 -22
  9. data/lib/matchers/document.rb +43 -4
  10. data/lib/matchers/indexes.rb +22 -10
  11. data/lib/matchers/validations/acceptance_of.rb +2 -2
  12. data/lib/matchers/validations/associated.rb +5 -5
  13. data/lib/matchers/validations/confirmation_of.rb +2 -2
  14. data/lib/matchers/validations/custom_validation_of.rb +2 -19
  15. data/lib/matchers/validations/exclusion_of.rb +8 -1
  16. data/lib/matchers/validations/format_of.rb +21 -21
  17. data/lib/matchers/validations/inclusion_of.rb +20 -13
  18. data/lib/matchers/validations/length_of.rb +66 -19
  19. data/lib/matchers/validations/numericality_of.rb +10 -10
  20. data/lib/matchers/validations/presence_of.rb +2 -2
  21. data/lib/matchers/validations/uniqueness_of.rb +13 -30
  22. data/lib/matchers/validations/with_message.rb +27 -0
  23. data/lib/matchers/validations.rb +45 -26
  24. data/lib/mongoid-rspec/version.rb +1 -1
  25. data/lib/mongoid-rspec.rb +5 -1
  26. data/mongoid-rspec.gemspec +2 -2
  27. data/spec/models/article.rb +18 -9
  28. data/spec/models/comment.rb +2 -2
  29. data/spec/models/movie_article.rb +6 -5
  30. data/spec/models/permalink.rb +1 -1
  31. data/spec/models/person.rb +0 -3
  32. data/spec/models/profile.rb +9 -7
  33. data/spec/models/record.rb +1 -1
  34. data/spec/models/site.rb +2 -3
  35. data/spec/models/user.rb +20 -12
  36. data/spec/unit/accept_nested_attributes_spec.rb +12 -0
  37. data/spec/unit/allow_mass_assignment_spec.rb +1 -1
  38. data/spec/unit/associations_spec.rb +6 -6
  39. data/spec/unit/document_spec.rb +8 -5
  40. data/spec/unit/indexes_spec.rb +3 -2
  41. data/spec/unit/validations_spec.rb +8 -0
  42. data/spec/validators/ssn_validator.rb +3 -1
  43. metadata +40 -28
  44. data/.rvmrc +0 -1
@@ -1,40 +1,36 @@
1
1
  module Mongoid
2
2
  module Matchers
3
- module Validations
3
+ module Validations
4
4
  class ValidateUniquenessOfMatcher < HaveValidationMatcher
5
+ include WithMessage
5
6
  def initialize(field)
6
7
  super(field, :uniqueness)
7
8
  end
8
-
9
+
9
10
  def scoped_to(*scope)
10
11
  @scope = [scope].flatten.map(&:to_sym)
11
12
  self
12
- end
13
+ end
13
14
  alias_method :scoped_on, :scoped_to
14
-
15
+
15
16
  def case_insensitive
16
17
  @case_insensitive = true
17
18
  self
18
19
  end
19
-
20
+
20
21
  def allow_blank?(allow_blank)
21
22
  @allow_blank = allow_blank
22
23
  self
23
24
  end
24
25
 
25
- def with_message(message)
26
- @expected_message = message
27
- self
28
- end
29
-
30
26
  def matches?(actual)
31
27
  return false unless @result = super(actual)
32
-
28
+
33
29
  check_scope if @scope
34
30
  check_allow_blank if @allow_blank
35
31
  check_case_sensitivity if @case_insensitive
36
32
  check_expected_message if @expected_message
37
-
33
+
38
34
  @result
39
35
  end
40
36
 
@@ -46,7 +42,7 @@ module Mongoid
46
42
  options_desc << " with message '#{@expected_message}'" if @expected_message
47
43
  super << options_desc.to_sentence
48
44
  end
49
-
45
+
50
46
  private
51
47
 
52
48
  def check_allow_blank
@@ -67,7 +63,7 @@ module Mongoid
67
63
  @result = false
68
64
  end
69
65
  end
70
-
66
+
71
67
  def check_case_sensitivity
72
68
  if @validator.options[:case_sensitive] == false
73
69
  @positive_result_message << " with case insensitive values"
@@ -76,24 +72,11 @@ module Mongoid
76
72
  @result = false
77
73
  end
78
74
  end
79
-
80
- def check_expected_message
81
- actual_message = @validator.options[:message]
82
- if actual_message.nil?
83
- @negative_result_message << " with no custom message"
84
- @result = false
85
- elsif actual_message == @expected_message
86
- @positive_result_message << " with custom message '#{@expected_message}'"
87
- else
88
- @negative_result_message << " got message '#{actual_message}'"
89
- @result = false
90
- end
91
- end
92
75
  end
93
-
76
+
94
77
  def validate_uniqueness_of(field)
95
78
  ValidateUniquenessOfMatcher.new(field)
96
- end
79
+ end
97
80
  end
98
81
  end
99
- 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
@@ -1,20 +1,22 @@
1
1
  module Mongoid
2
2
  module Matchers
3
- module Validations
4
-
3
+ module Validations
4
+
5
5
  class HaveValidationMatcher
6
-
6
+
7
7
  def initialize(field, validation_type)
8
8
  @field = field.to_s
9
9
  @type = validation_type.to_s
10
10
  @options = {}
11
11
  end
12
-
12
+
13
13
  def matches?(actual)
14
14
  @klass = actual.is_a?(Class) ? actual : actual.class
15
-
16
- @validator = @klass.validators_on(@field).detect{|v| v.kind.to_s == @type}
17
-
15
+
16
+ @validator = @klass.validators_on(@field).detect{ |v|
17
+ v.kind.to_s == @type and (!v.options[:on] or on_options_matches?(v))
18
+ }
19
+
18
20
  if @validator
19
21
  @negative_result_message = "#{@type.inspect} validator on #{@field.inspect}"
20
22
  @positive_result_message = "#{@type.inspect} validator on #{@field.inspect}"
@@ -23,37 +25,54 @@ module Mongoid
23
25
  return false
24
26
  end
25
27
  @result = true
26
- check_on if @options[:on]
28
+ check_on if @options[:on]
27
29
  @result
28
30
  end
29
-
30
- def failure_message_for_should
31
- "Expected #{@klass.inspect} to #{description}; instead got #{@negative_result_message}"
31
+
32
+ def failure_message_for_should
33
+ "Expected #{@klass.inspect} to #{description}; instead got #{@negative_result_message}"
32
34
  end
33
-
34
- def failure_message_for_should_not
35
- "Expected #{@klass.inspect} to not #{description}; instead got #{@positive_result_message}"
35
+
36
+ def failure_message_for_should_not
37
+ "Expected #{@klass.inspect} to not #{description}; instead got #{@positive_result_message}"
36
38
  end
37
-
39
+
38
40
  def description
39
- "validate #{@type} of #{@field.inspect}"
41
+ desc = "have #{@type.inspect} validator on #{@field.inspect}"
42
+ desc << " on #{@options[:on]}" if @options[:on]
43
+ desc
40
44
  end
41
-
45
+
42
46
  def on(*on_method)
43
47
  @options[:on] = on_method.flatten
44
48
  self
45
- end
46
-
49
+ end
50
+
47
51
  def check_on
48
- message = " on methods: #{@validator.options[:on]}"
49
- if [@validator.options[:on]].flatten == @options[:on]
50
- @positive_result_message << message
51
- else
52
- @negative_result_message << message
53
- @result = false
52
+ validator_on_methods = [@validator.options[:on]].flatten
53
+
54
+ if validator_on_methods.any?
55
+ message = " on methods: #{validator_on_methods}"
56
+
57
+ if on_options_covered_by?( @validator )
58
+ @positive_result_message << message
59
+ else
60
+ @negative_result_message << message
61
+ @result = false
62
+ end
54
63
  end
55
64
  end
56
- end
65
+
66
+ private
67
+
68
+ def on_options_matches?(validator)
69
+ @options[:on] and validator.options[:on] and on_options_covered_by?(validator)
70
+ end
71
+
72
+ def on_options_covered_by?(validator)
73
+ ([@options[:on]].flatten - [validator.options[:on]].flatten).empty?
74
+ end
75
+ end
57
76
  end
58
77
  end
59
78
  end
@@ -1,5 +1,5 @@
1
1
  module Mongoid
2
2
  module Rspec
3
- VERSION = "1.5.0"
3
+ VERSION = "1.11.0"
4
4
  end
5
5
  end
data/lib/mongoid-rspec.rb CHANGED
@@ -1,14 +1,18 @@
1
1
  $LOAD_PATH.unshift(File.dirname(__FILE__))
2
2
 
3
3
  require 'mongoid'
4
- require 'rspec'
4
+ require 'rspec/core'
5
+ require 'rspec/expectations'
6
+ require 'rspec/mocks'
5
7
  require "active_model"
6
8
  require 'matchers/document'
7
9
  require 'matchers/associations'
8
10
  require 'matchers/collections'
9
11
  require 'matchers/indexes'
10
12
  require 'matchers/allow_mass_assignment'
13
+ require 'matchers/accept_nested_attributes'
11
14
  require 'matchers/validations'
15
+ require 'matchers/validations/with_message'
12
16
  require 'matchers/validations/associated'
13
17
  require 'matchers/validations/confirmation_of'
14
18
  require 'matchers/validations/exclusion_of'
@@ -20,6 +20,6 @@ Gem::Specification.new do |s|
20
20
  s.require_paths = ["lib"]
21
21
 
22
22
  s.add_dependency 'rake'
23
- s.add_dependency 'mongoid', '>= 3.0.1'
24
- s.add_dependency 'rspec', '>= 2.9'
23
+ s.add_dependency 'mongoid', '~> 3.1.6'
24
+ s.add_dependency 'rspec', '>= 2.14'
25
25
  end
@@ -5,19 +5,28 @@ class Article
5
5
  include Mongoid::Versioning
6
6
  include Mongoid::MultiParameterAttributes
7
7
 
8
- field :title
8
+ field :title, localize: true
9
9
  field :content
10
- field :published, :type => Boolean, :default => false
11
- field :allow_comments, :type => Boolean, :default => true
10
+ field :published, type: Boolean, default: false
11
+ field :allow_comments, type: Boolean, default: true
12
+ field :number_of_comments, type: Integer
13
+ field :status, type: Symbol
12
14
 
13
- embeds_many :comments
15
+ embeds_many :comments, cascade_callbacks: true
14
16
  embeds_one :permalink
15
- belongs_to :author, :class_name => 'User', :inverse_of => :articles, :index => true
17
+ belongs_to :author, class_name: 'User', inverse_of: :articles, index: true
16
18
 
17
- validates :title, :presence => true
19
+ validates :title, presence: true
18
20
 
19
- validates_length_of :title, :minimum => 8, :maximum => 16
21
+ validates_inclusion_of :status, in: [:pending], on: :create
22
+ validates_inclusion_of :status, in: [:approved, :rejected ], on: :update
20
23
 
21
- index({ title: 1 }, { unique: true, background: true })
24
+ validates_length_of :title, within: 8..16
25
+ validates_length_of :content, minimum: 200
26
+
27
+ index({ title: 1 }, { unique: true, background: true, drop_dups: true })
22
28
  index({ published: 1 })
23
- end
29
+ index({ 'permalink._id' => 1 })
30
+
31
+ accepts_nested_attributes_for :permalink
32
+ end
@@ -1,6 +1,6 @@
1
1
  class Comment
2
2
  include Mongoid::Document
3
3
 
4
- embedded_in :article, :inverse_of => :comments
5
- belongs_to :user, :inverse_of => :comments
4
+ embedded_in :article, inverse_of: :comments, polymorphic: true
5
+ belongs_to :user, inverse_of: :comments
6
6
  end
@@ -1,7 +1,8 @@
1
1
  class MovieArticle < Article
2
- field :rating, :type => Float
3
- field :classification, :type => Integer
4
-
5
- validates_numericality_of :rating, :greater_than => 0, :less_than_or_equal_to => 5
6
- validates_numericality_of :classification, :even => true, :only_integer => true, :allow_nil => false
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 }
7
8
  end
@@ -1,5 +1,5 @@
1
1
  class Permalink
2
2
  include Mongoid::Document
3
3
 
4
- embedded_in :linkable, :inverse_of => :link
4
+ embedded_in :linkable, inverse_of: :link
5
5
  end
@@ -1,13 +1,10 @@
1
1
  require 'ssn_validator.rb'
2
2
 
3
3
  class Person
4
-
5
4
  include Mongoid::Document
6
5
 
7
6
  field :name
8
7
  field :ssn
9
8
 
10
9
  validates :ssn, ssn: true
11
-
12
-
13
10
  end
@@ -1,14 +1,16 @@
1
1
  class Profile
2
2
  include Mongoid::Document
3
-
3
+
4
4
  field :first_name
5
5
  field :last_name
6
6
  field :age
7
-
8
- embedded_in :user, :inverse_of => :profile
9
-
10
- validates_numericality_of :age, :greater_than => 0
11
- validates_acceptance_of :terms_of_service
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" }
12
14
 
13
- index({first_name: 1, last_name: 1 })
15
+ index({ first_name: 1, last_name: 1 })
14
16
  end
@@ -1,5 +1,5 @@
1
1
  class Record
2
2
  include Mongoid::Document
3
3
 
4
- belongs_to :user, :inverse_of => :record
4
+ belongs_to :user, inverse_of: :record
5
5
  end
data/spec/models/site.rb CHANGED
@@ -3,8 +3,7 @@ class Site
3
3
 
4
4
  field :name
5
5
 
6
- has_many :users, :inverse_of => :site
7
-
8
- validates :name, :presence => true, :uniqueness => true
6
+ has_many :users, inverse_of: :site, order: :email.desc
9
7
 
8
+ validates :name, presence: true, uniqueness: true
10
9
  end
data/spec/models/user.rb CHANGED
@@ -1,29 +1,37 @@
1
1
  class User
2
2
  include Mongoid::Document
3
+ include Mongoid::Timestamps::Created
3
4
 
4
5
  field :login
5
6
  field :email
6
7
  field :role
7
8
  field :age, type: Integer
8
9
  field :password, type: String
10
+ field :provider_uid
11
+ field :locale
9
12
 
10
- belongs_to :site, :inverse_of => :users
11
- has_many :articles, :foreign_key => :author_id
12
- has_many :comments, :dependent => :destroy, :autosave => true
13
- has_and_belongs_to_many :children, :class_name => "User"
14
- has_one :record
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
15
18
 
16
19
  embeds_one :profile
17
20
 
18
- validates :login, :presence => true, :uniqueness => { :scope => :site }, :format => { :with => /^[\w\-]+$/ }, :exclusion => { :in => ["super", "index", "edit"]}
19
- validates :email, :uniqueness => { :case_sensitive => false, :scope => :site, :message => "is already taken" }, :confirmation => true
20
- validates :role, :presence => true, :inclusion => { :in => ["admin", "moderator", "member"]}
21
- validates :profile, :presence => true, :associated => true
22
- validates :age, :presence => true, :numericality => true, :inclusion => { :in => 23..42 }, :on => [:create, :update]
23
- validates :password, :presence => true, :on => :create
21
+ validates :login, presence: true, uniqueness: { scope: :site }, format: { with: /^[\w\-]+$/ }, 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] } }
24
30
 
25
31
  attr_accessible :login, :email, :age, :password
26
- attr_accessible :role, :as => :admin
32
+ attr_accessible :role, as: :admin
33
+
34
+ accepts_nested_attributes_for :articles, :comments
27
35
 
28
36
  def admin?
29
37
  false
@@ -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
@@ -8,7 +8,7 @@ describe "AllowMassAssignment" do
8
8
  it { should allow_mass_assignment_of(:password) }
9
9
  it { should allow_mass_assignment_of(:password) }
10
10
  it { should allow_mass_assignment_of(:role).as(:admin) }
11
-
11
+
12
12
  it { should_not allow_mass_assignment_of(:role) }
13
13
  end
14
14
  end
@@ -2,13 +2,13 @@ require 'spec_helper'
2
2
 
3
3
  describe "Associations" do
4
4
  describe User do
5
- it { should have_many(:articles).with_foreign_key(:author_id) }
5
+ it { should have_many(:articles).with_foreign_key(:author_id).ordered_by(:title) }
6
6
 
7
- it { should have_one(:record) }
7
+ it { should have_one(:record).with_autobuild }
8
8
 
9
9
  it { should have_many(:comments).with_dependent(:destroy).with_autosave }
10
10
 
11
- it { should embed_one :profile }
11
+ it { should embed_one(:profile) }
12
12
 
13
13
  it { should have_and_belong_to_many(:children).of_type(User) }
14
14
  end
@@ -19,12 +19,12 @@ describe "Associations" do
19
19
 
20
20
  describe Article do
21
21
  it { should belong_to(:author).of_type(User).as_inverse_of(:articles).with_index }
22
- it { should embed_many(:comments) }
22
+ it { should embed_many(:comments).with_cascading_callbacks }
23
23
  it { should embed_one(:permalink) }
24
24
  end
25
25
 
26
26
  describe Comment do
27
- it { should be_embedded_in(:article).as_inverse_of(:comments) }
27
+ it { should be_embedded_in(:article).as_inverse_of(:comments).with_polymorphism }
28
28
  it { should belong_to(:user).as_inverse_of(:comments) }
29
29
  end
30
30
 
@@ -37,6 +37,6 @@ describe "Associations" do
37
37
  end
38
38
 
39
39
  describe Site do
40
- it { should have_many(:users).as_inverse_of(:site) }
40
+ it { should have_many(:users).as_inverse_of(:site).ordered_by(:email.desc) }
41
41
  end
42
42
  end
@@ -3,15 +3,18 @@ require 'spec_helper'
3
3
  describe "Document" do
4
4
  describe User do
5
5
  it { should have_fields(:email, :login) }
6
+ it { should be_timestamped_document }
7
+ it { should be_timestamped_document.with(:created) }
8
+ it { should_not be_timestamped_document.with(:updated) }
6
9
  end
7
-
10
+
8
11
  describe Article do
9
12
  it { should have_field(:published).of_type(Boolean).with_default_value_of(false) }
10
- it { should have_field(:allow_comments).of_type(Boolean).with_default_value_of(true) }
13
+ it { should have_field(:allow_comments).of_type(Boolean).with_default_value_of(true) }
14
+ it { should belong_to(:author) }
15
+ it { should have_field(:title).localized }
11
16
  it { should_not have_field(:allow_comments).of_type(Boolean).with_default_value_of(false) }
12
- end
13
-
14
- describe Article do
17
+ it { should_not have_field(:number_of_comments).of_type(Integer).with_default_value_of(1) }
15
18
  it { should be_mongoid_document }
16
19
  it { should be_versioned_document }
17
20
  it { should be_timestamped_document }
@@ -3,9 +3,10 @@ require 'spec_helper'
3
3
  describe "Indexes" do
4
4
  describe Article do
5
5
  it { should have_index_for(published: 1) }
6
- it { should have_index_for(title: 1).with_options(unique: true, background: true) }
6
+ it { should have_index_for(title: 1).with_options(unique: true, background: true, drop_dups: true) }
7
+ it { should have_index_for('permalink._id' => 1) }
7
8
  end
8
-
9
+
9
10
  describe Profile do
10
11
  it { should have_index_for(first_name: 1, last_name: 1) }
11
12
  end
@@ -13,21 +13,29 @@ describe "Validations" do
13
13
  it { should validate_format_of(:login).to_allow("valid_login").not_to_allow("invalid login") }
14
14
  it { should validate_associated(:profile) }
15
15
  it { should validate_exclusion_of(:login).to_not_allow("super", "index", "edit") }
16
+ it { should validate_exclusion_of(:password).to_not_allow("password") }
16
17
  it { should validate_inclusion_of(:role).to_allow("admin", "member") }
17
18
  it { should validate_confirmation_of(:email) }
18
19
  it { should validate_presence_of(:age).on(:create, :update) }
19
20
  it { should validate_numericality_of(:age).on(:create, :update) }
20
21
  it { should validate_inclusion_of(:age).to_allow(23..42).on([:create, :update]) }
21
22
  it { should validate_presence_of(:password).on(:create) }
23
+ it { should validate_presence_of(:provider_uid).on(:create) }
24
+ it { should validate_inclusion_of(:locale).to_allow([:en, :ru]) }
22
25
  end
23
26
 
24
27
  describe Profile do
25
28
  it { should validate_numericality_of(:age).greater_than(0) }
26
29
  it { should validate_acceptance_of(:terms_of_service) }
30
+ it { should validate_length_of(:hobbies).with_minimum(1).with_message("requires at least one hobby") }
27
31
  end
28
32
 
29
33
  describe Article do
30
34
  it { should validate_length_of(:title).within(8..16) }
35
+ it { should_not validate_length_of(:content).greater_than(200).less_than(16) }
36
+ it { should validate_length_of(:content).greater_than(200) }
37
+ it { should validate_inclusion_of(:status).to_allow([:pending]).on( :create ) }
38
+ it { should validate_inclusion_of(:status).to_allow([:approved, :rejected]).on( :update ) }
31
39
  end
32
40
 
33
41
  describe MovieArticle do
@@ -1,7 +1,9 @@
1
1
  class SsnValidator < ActiveModel::EachValidator
2
2
 
3
3
  def validate_each(record, attribute, value)
4
- record.errors[attribute] << "#{value} is not a valid Social Security Number" unless valid_ssn?(record, attribute, value)
4
+ unless valid_ssn?(record, attribute, value)
5
+ record.errors[attribute] << "#{value} is not a valid Social Security Number"
6
+ end
5
7
  end
6
8
 
7
9
  def self.kind() :custom end