glebtv-mongoid-rspec 1.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.gitignore +6 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +14 -0
  7. data/Gemfile +4 -0
  8. data/LICENSE +20 -0
  9. data/README.md +203 -0
  10. data/Rakefile +18 -0
  11. data/gemfiles/mongoid-3.1.gemfile +5 -0
  12. data/gemfiles/mongoid-4.0.gemfile +5 -0
  13. data/glebtv-mongoid-rspec.gemspec +25 -0
  14. data/lib/glebtv-mongoid-rspec.rb +1 -0
  15. data/lib/matchers/accept_nested_attributes.rb +65 -0
  16. data/lib/matchers/allow_mass_assignment.rb +101 -0
  17. data/lib/matchers/associations.rb +313 -0
  18. data/lib/matchers/collections.rb +9 -0
  19. data/lib/matchers/document.rb +160 -0
  20. data/lib/matchers/indexes.rb +81 -0
  21. data/lib/matchers/validations.rb +78 -0
  22. data/lib/matchers/validations/acceptance_of.rb +9 -0
  23. data/lib/matchers/validations/associated.rb +19 -0
  24. data/lib/matchers/validations/confirmation_of.rb +9 -0
  25. data/lib/matchers/validations/custom_validation_of.rb +47 -0
  26. data/lib/matchers/validations/exclusion_of.rb +49 -0
  27. data/lib/matchers/validations/format_of.rb +71 -0
  28. data/lib/matchers/validations/inclusion_of.rb +49 -0
  29. data/lib/matchers/validations/length_of.rb +147 -0
  30. data/lib/matchers/validations/numericality_of.rb +74 -0
  31. data/lib/matchers/validations/presence_of.rb +9 -0
  32. data/lib/matchers/validations/uniqueness_of.rb +82 -0
  33. data/lib/matchers/validations/with_message.rb +27 -0
  34. data/lib/mongoid-rspec.rb +33 -0
  35. data/lib/mongoid-rspec/version.rb +5 -0
  36. data/spec/models/article.rb +29 -0
  37. data/spec/models/comment.rb +6 -0
  38. data/spec/models/log.rb +4 -0
  39. data/spec/models/movie_article.rb +8 -0
  40. data/spec/models/permalink.rb +5 -0
  41. data/spec/models/person.rb +10 -0
  42. data/spec/models/profile.rb +16 -0
  43. data/spec/models/record.rb +5 -0
  44. data/spec/models/site.rb +9 -0
  45. data/spec/models/user.rb +36 -0
  46. data/spec/spec_helper.rb +34 -0
  47. data/spec/unit/accept_nested_attributes_spec.rb +12 -0
  48. data/spec/unit/associations_spec.rb +42 -0
  49. data/spec/unit/collections_spec.rb +7 -0
  50. data/spec/unit/document_spec.rb +27 -0
  51. data/spec/unit/indexes_spec.rb +13 -0
  52. data/spec/unit/validations_spec.rb +52 -0
  53. data/spec/validators/ssn_validator.rb +16 -0
  54. 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,9 @@
1
+ module Mongoid
2
+ module Matchers
3
+ module Validations
4
+ def validate_presence_of(field)
5
+ HaveValidationMatcher.new(field, :presence)
6
+ end
7
+ end
8
+ end
9
+ 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,5 @@
1
+ module Mongoid
2
+ module Rspec
3
+ VERSION = "1.12.0"
4
+ end
5
+ 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
@@ -0,0 +1,6 @@
1
+ class Comment
2
+ include Mongoid::Document
3
+
4
+ embedded_in :article, inverse_of: :comments, polymorphic: true
5
+ belongs_to :user, inverse_of: :comments
6
+ end
@@ -0,0 +1,4 @@
1
+ class Log
2
+ include Mongoid::Document
3
+ store_in collection: "logs"
4
+ end
@@ -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,5 @@
1
+ class Permalink
2
+ include Mongoid::Document
3
+
4
+ embedded_in :linkable, inverse_of: :link
5
+ end
@@ -0,0 +1,10 @@
1
+ require 'ssn_validator.rb'
2
+
3
+ class Person
4
+ include Mongoid::Document
5
+
6
+ field :name
7
+ field :ssn
8
+
9
+ validates :ssn, ssn: true
10
+ 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
@@ -0,0 +1,5 @@
1
+ class Record
2
+ include Mongoid::Document
3
+
4
+ belongs_to :user, inverse_of: :record
5
+ end
@@ -0,0 +1,9 @@
1
+ class Site
2
+ include Mongoid::Document
3
+
4
+ field :name
5
+
6
+ has_many :users, inverse_of: :site, order: :email.desc
7
+
8
+ validates :name, presence: true, uniqueness: true
9
+ end
@@ -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
@@ -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