├── .rspec ├── spec ├── models │ ├── employer.rb │ ├── mixed_drink.rb │ ├── answer.rb │ ├── video.rb │ ├── pet_owner.rb │ ├── location.rb │ ├── login.rb │ ├── translation.rb │ ├── vet_visit.rb │ ├── account.rb │ ├── user_accout.rb │ ├── agent.rb │ ├── favorite.rb │ ├── phone.rb │ ├── country_code.rb │ ├── pet.rb │ ├── survey.rb │ ├── user.rb │ ├── comment.rb │ ├── preference.rb │ ├── game.rb │ ├── question.rb │ ├── namespacing.rb │ ├── category.rb │ ├── animal.rb │ ├── name.rb │ ├── patient.rb │ ├── paranoid_post.rb │ ├── post.rb │ ├── parents.rb │ ├── address.rb │ ├── callbacks.rb │ └── inheritance.rb ├── config │ ├── mongoid_with_utc.yml │ ├── mongoid_with_url.yml │ └── mongoid.yml ├── unit │ ├── mongoid │ │ ├── extensions │ │ │ ├── hash │ │ │ │ ├── scoping_spec.rb │ │ │ │ ├── criteria_helpers_spec.rb │ │ │ │ ├── conversions_spec.rb │ │ │ │ └── assimilation_spec.rb │ │ │ ├── datetime │ │ │ │ └── conversions_spec.rb │ │ │ ├── set │ │ │ │ └── conversions_spec.rb │ │ │ ├── binary │ │ │ │ └── conversions_spec.rb │ │ │ ├── array │ │ │ │ ├── parentization_spec.rb │ │ │ │ ├── assimilation_spec.rb │ │ │ │ ├── conversions_spec.rb │ │ │ │ └── accessors_spec.rb │ │ │ ├── objectid │ │ │ │ └── conversions_spec.rb │ │ │ ├── string │ │ │ │ └── conversions_spec.rb │ │ │ ├── nil │ │ │ │ └── assimilation_spec.rb │ │ │ ├── true_class │ │ │ │ └── equality_spec.rb │ │ │ ├── false_class │ │ │ │ └── equality_spec.rb │ │ │ ├── big_decimal │ │ │ │ └── conversions_spec.rb │ │ │ ├── proc │ │ │ │ └── scoping_spec.rb │ │ │ ├── boolean │ │ │ │ └── conversions_spec.rb │ │ │ ├── object │ │ │ │ └── conversions_spec.rb │ │ │ ├── float │ │ │ │ └── conversions_spec.rb │ │ │ ├── integer │ │ │ │ └── conversions_spec.rb │ │ │ └── symbol │ │ │ │ └── inflections_spec.rb │ │ ├── criterion │ │ │ └── complex_spec.rb │ │ ├── deprecation_spec.rb │ │ ├── matchers │ │ │ ├── default_spec.rb │ │ │ ├── ne_spec.rb │ │ │ ├── all_spec.rb │ │ │ ├── size_spec.rb │ │ │ ├── in_spec.rb │ │ │ ├── nin_spec.rb │ │ │ ├── lt_spec.rb │ │ │ ├── gt_spec.rb │ │ │ ├── gte_spec.rb │ │ │ ├── lte_spec.rb │ │ │ └── exists_spec.rb │ │ ├── factory_spec.rb │ │ ├── contexts_spec.rb │ │ ├── timestamps_spec.rb │ │ ├── collections │ │ │ ├── master_spec.rb │ │ │ ├── cyclic_iterator_spec.rb │ │ │ └── slaves_spec.rb │ │ ├── versioning_spec.rb │ │ ├── logger_spec.rb │ │ ├── serialization_spec.rb │ │ ├── javascript_spec.rb │ │ ├── validations_spec.rb │ │ ├── hierarchy_spec.rb │ │ ├── memoization_spec.rb │ │ ├── state_spec.rb │ │ ├── cursor_spec.rb │ │ ├── extras_spec.rb │ │ ├── persistence │ │ │ ├── remove_all_spec.rb │ │ │ └── remove_spec.rb │ │ ├── callbacks_spec.rb │ │ ├── associations │ │ │ ├── foreign_key_spec.rb │ │ │ └── meta_data_spec.rb │ │ ├── indexes_spec.rb │ │ └── collections_spec.rb │ └── mongoid_spec.rb ├── integration │ └── mongoid │ │ ├── extensions_spec.rb │ │ ├── callback_spec.rb │ │ ├── contexts │ │ └── enumerable_spec.rb │ │ ├── paranoia_spec.rb │ │ ├── persistence │ │ └── update_spec.rb │ │ ├── named_scope_spec.rb │ │ ├── attributes_spec.rb │ │ └── dirty_spec.rb └── spec_helper.rb ├── lib ├── mongoid │ ├── version.rb │ ├── validations │ │ ├── locale │ │ │ └── en.yml │ │ ├── associated.rb │ │ └── uniqueness.rb │ ├── extensions │ │ ├── hash │ │ │ ├── scoping.rb │ │ │ ├── criteria_helpers.rb │ │ │ ├── conversions.rb │ │ │ ├── assimilation.rb │ │ │ └── accessors.rb │ │ ├── proc │ │ │ └── scoping.rb │ │ ├── datetime │ │ │ └── conversions.rb │ │ ├── false_class │ │ │ └── equality.rb │ │ ├── true_class │ │ │ └── equality.rb │ │ ├── objectid │ │ │ └── conversions.rb │ │ ├── string │ │ │ └── conversions.rb │ │ ├── array │ │ │ ├── parentization.rb │ │ │ ├── accessors.rb │ │ │ ├── conversions.rb │ │ │ └── assimilation.rb │ │ ├── binary │ │ │ └── conversions.rb │ │ ├── nil │ │ │ └── assimilation.rb │ │ ├── float │ │ │ └── conversions.rb │ │ ├── integer │ │ │ └── conversions.rb │ │ ├── set │ │ │ └── conversions.rb │ │ ├── big_decimal │ │ │ └── conversions.rb │ │ ├── object │ │ │ └── conversions.rb │ │ ├── date │ │ │ └── conversions.rb │ │ ├── boolean │ │ │ └── conversions.rb │ │ ├── time_conversions.rb │ │ └── symbol │ │ │ └── inflections.rb │ ├── matchers │ │ ├── lt.rb │ │ ├── gt.rb │ │ ├── in.rb │ │ ├── lte.rb │ │ ├── gte.rb │ │ ├── ne.rb │ │ ├── nin.rb │ │ ├── all.rb │ │ ├── size.rb │ │ ├── exists.rb │ │ └── default.rb │ ├── logger.rb │ ├── callbacks.rb │ ├── deprecation.rb │ ├── factory.rb │ ├── criterion │ │ ├── complex.rb │ │ └── exclusion.rb │ ├── javascript.rb │ ├── contexts │ │ ├── ids.rb │ │ └── paging.rb │ ├── javascript │ │ └── functions.yml │ ├── contexts.rb │ ├── collections │ │ ├── master.rb │ │ ├── cyclic_iterator.rb │ │ ├── operations.rb │ │ └── slaves.rb │ ├── indexes.rb │ ├── timestamps.rb │ ├── state.rb │ ├── versioning.rb │ ├── persistence │ │ ├── remove.rb │ │ ├── remove_all.rb │ │ ├── command.rb │ │ ├── insert.rb │ │ ├── insert_embedded.rb │ │ ├── remove_embedded.rb │ │ └── update.rb │ ├── memoization.rb │ ├── associations │ │ ├── proxy.rb │ │ ├── foreign_key.rb │ │ ├── meta_data.rb │ │ ├── options.rb │ │ ├── referenced_in.rb │ │ └── embedded_in.rb │ ├── matchers.rb │ ├── components.rb │ ├── named_scope.rb │ ├── identity.rb │ ├── validations.rb │ ├── atomicity.rb │ ├── railties │ │ └── database.rake │ ├── extras.rb │ ├── paths.rb │ ├── fields.rb │ ├── cursor.rb │ ├── collections.rb │ ├── railtie.rb │ ├── field.rb │ ├── scope.rb │ └── hierarchy.rb └── rails │ └── generators │ ├── mongoid │ ├── model │ │ ├── templates │ │ │ └── model.rb │ │ └── model_generator.rb │ └── config │ │ ├── templates │ │ └── mongoid.yml │ │ └── config_generator.rb │ └── mongoid_generator.rb ├── .rvmrc ├── .gitignore ├── rake_rubies.sh ├── rake_spec_several_time_zones.sh ├── Gemfile ├── .watchr ├── MIT_LICENSE ├── Rakefile ├── mongoid.gemspec └── README.rdoc /.rspec: -------------------------------------------------------------------------------- 1 | --colour 2 | --format nested 3 | --backtrace 4 | -------------------------------------------------------------------------------- /spec/models/employer.rb: -------------------------------------------------------------------------------- 1 | class Employer 2 | def id 3 | "1" 4 | end 5 | end -------------------------------------------------------------------------------- /spec/config/mongoid_with_utc.yml: -------------------------------------------------------------------------------- 1 | test: 2 | use_utc: true 3 | database: mongoid_config_test -------------------------------------------------------------------------------- /spec/models/mixed_drink.rb: -------------------------------------------------------------------------------- 1 | class MixedDrink 2 | include Mongoid::Document 3 | field :name 4 | end -------------------------------------------------------------------------------- /lib/mongoid/version.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc 3 | VERSION = "2.0.0.beta9" 4 | end 5 | -------------------------------------------------------------------------------- /.rvmrc: -------------------------------------------------------------------------------- 1 | rvm_gemset_create_on_use_flag=1 2 | # rvm use 1.8.7@mongoid 3 | # rvm use 1.9.1@mongoid 4 | rvm use 1.9.2-head@mongoid 5 | -------------------------------------------------------------------------------- /spec/models/answer.rb: -------------------------------------------------------------------------------- 1 | class Answer 2 | include Mongoid::Document 3 | embedded_in :question, :inverse_of => :answers 4 | end 5 | -------------------------------------------------------------------------------- /lib/mongoid/validations/locale/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | activemodel: 3 | errors: 4 | messages: 5 | taken: "is already taken" 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_STORE 2 | coverage/* 3 | pkg/* 4 | scratch_directory/* 5 | tmp/* 6 | *.gem 7 | RAILS 8 | .bundle 9 | Gemfile.lock 10 | -------------------------------------------------------------------------------- /spec/models/video.rb: -------------------------------------------------------------------------------- 1 | class Video 2 | include Mongoid::Document 3 | field :title 4 | embedded_in :person, :inverse_of => :videos 5 | end 6 | -------------------------------------------------------------------------------- /spec/models/pet_owner.rb: -------------------------------------------------------------------------------- 1 | class PetOwner 2 | include Mongoid::Document 3 | field :title 4 | embeds_one :pet 5 | embeds_one :address 6 | end 7 | -------------------------------------------------------------------------------- /spec/models/location.rb: -------------------------------------------------------------------------------- 1 | class Location 2 | include Mongoid::Document 3 | field :name 4 | embedded_in :address, :inverse_of => :locations 5 | end 6 | -------------------------------------------------------------------------------- /spec/models/login.rb: -------------------------------------------------------------------------------- 1 | class Login 2 | include Mongoid::Document 3 | field :username 4 | key :username 5 | validates_uniqueness_of :username 6 | end 7 | -------------------------------------------------------------------------------- /spec/models/translation.rb: -------------------------------------------------------------------------------- 1 | class Translation 2 | include Mongoid::Document 3 | field :language 4 | embedded_in :name, :inverse_of => :translations 5 | end 6 | -------------------------------------------------------------------------------- /spec/models/vet_visit.rb: -------------------------------------------------------------------------------- 1 | class VetVisit 2 | include Mongoid::Document 3 | field :date, :type => Date 4 | embedded_in :pet, :inverse_of => :vet_visits 5 | end 6 | -------------------------------------------------------------------------------- /rake_rubies.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | rvm use ruby-1.8.7 3 | bundle exec rake spec 4 | rvm use ruby-1.9.1 5 | bundle exec rake spec 6 | rvm use ruby-1.9.2-head 7 | bundle exec rake spec 8 | -------------------------------------------------------------------------------- /spec/models/account.rb: -------------------------------------------------------------------------------- 1 | class Account 2 | include Mongoid::Document 3 | referenced_in :creator, :class_name => "User", :foreign_key => :creator_id 4 | field :number 5 | end 6 | -------------------------------------------------------------------------------- /spec/models/user_accout.rb: -------------------------------------------------------------------------------- 1 | class UserAccount 2 | include Mongoid::Document 3 | field :username 4 | validates_uniqueness_of :username, :message => "is not unique" 5 | end 6 | -------------------------------------------------------------------------------- /spec/models/agent.rb: -------------------------------------------------------------------------------- 1 | class Agent 2 | include Mongoid::Document 3 | field :title 4 | field :number 5 | embeds_many :names 6 | references_many :posts, :foreign_key => :poster_id 7 | end 8 | -------------------------------------------------------------------------------- /spec/models/favorite.rb: -------------------------------------------------------------------------------- 1 | class Favorite 2 | include Mongoid::Document 3 | 4 | field :title 5 | validates_uniqueness_of :title 6 | 7 | embedded_in :person, :inverse_of => :favorites 8 | end 9 | -------------------------------------------------------------------------------- /spec/models/phone.rb: -------------------------------------------------------------------------------- 1 | class Phone 2 | include Mongoid::Document 3 | field :number 4 | key :number 5 | embeds_one :country_code 6 | embedded_in :person, :inverse_of => :phone_numbers 7 | end 8 | -------------------------------------------------------------------------------- /spec/models/country_code.rb: -------------------------------------------------------------------------------- 1 | class CountryCode 2 | include Mongoid::Document 3 | field :code, :type => Integer 4 | key :code 5 | embedded_in :phone_number, :inverse_of => :country_codes 6 | end 7 | -------------------------------------------------------------------------------- /spec/config/mongoid_with_url.yml: -------------------------------------------------------------------------------- 1 | defaults: &defaults 2 | uri: "mongodb://localhost/sushi" 3 | 4 | development: 5 | <<: *defaults 6 | 7 | test: 8 | <<: *defaults 9 | database: sushi_test 10 | -------------------------------------------------------------------------------- /spec/models/pet.rb: -------------------------------------------------------------------------------- 1 | class Pet 2 | include Mongoid::Document 3 | field :name 4 | field :weight, :type => Float, :default => 0.0 5 | embeds_many :vet_visits 6 | embedded_in :pet_owner, :inverse_of => :pet 7 | end 8 | -------------------------------------------------------------------------------- /spec/models/survey.rb: -------------------------------------------------------------------------------- 1 | class Survey 2 | include Mongoid::Document 3 | embeds_many :questions 4 | 5 | accepts_nested_attributes_for :questions, :reject_if => lambda{ |a| a[:content].blank? }, :allow_destroy => true 6 | end -------------------------------------------------------------------------------- /spec/models/user.rb: -------------------------------------------------------------------------------- 1 | class User 2 | include Mongoid::Document 3 | references_one :account, :foreign_key => :creator_id 4 | references_many :posts, :foreign_key => :author_id, :inverse_of => :author 5 | field :name 6 | end 7 | -------------------------------------------------------------------------------- /rake_spec_several_time_zones.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | rake spec 3 | export TZ=America/Santiago 4 | rake spec 5 | export TZ=US/Central 6 | rake spec 7 | export TZ=Europe/Stockholm 8 | rake spec 9 | export TZ=US/Pacific 10 | rake spec 11 | 12 | -------------------------------------------------------------------------------- /spec/models/comment.rb: -------------------------------------------------------------------------------- 1 | class Comment 2 | include Mongoid::Document 3 | include Mongoid::Versioning 4 | include Mongoid::Timestamps 5 | field :title 6 | 7 | field :text 8 | key :text 9 | validates_presence_of :text 10 | end 11 | -------------------------------------------------------------------------------- /spec/models/preference.rb: -------------------------------------------------------------------------------- 1 | class Preference 2 | include Mongoid::Document 3 | field :name 4 | field :value 5 | references_many :people, :stored_as => :array, :inverse_of => :preferences 6 | validates_length_of :name, :minimum => 2, :allow_nil => true 7 | end 8 | -------------------------------------------------------------------------------- /spec/models/game.rb: -------------------------------------------------------------------------------- 1 | class Game 2 | include Mongoid::Document 3 | field :high_score, :type => Integer, :default => 500 4 | field :score, :type => Integer, :default => 0 5 | referenced_in :person 6 | enslave and cache 7 | 8 | attr_protected :_id 9 | end 10 | -------------------------------------------------------------------------------- /spec/models/question.rb: -------------------------------------------------------------------------------- 1 | class Question 2 | include Mongoid::Document 3 | field :content 4 | embedded_in :survey, :inverse_of => :questions 5 | embeds_many :answers 6 | 7 | accepts_nested_attributes_for :answers, :reject_if => lambda { |a| a[:content].blank? }, :allow_destroy => true 8 | end -------------------------------------------------------------------------------- /lib/mongoid/extensions/hash/scoping.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Extensions #:nodoc: 4 | module Hash #:nodoc: 5 | module Scoping #:nodoc: 6 | def scoped(*args) 7 | self 8 | end 9 | end 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/models/namespacing.rb: -------------------------------------------------------------------------------- 1 | module Medical 2 | class Patient 3 | include Mongoid::Document 4 | field :name 5 | embeds_many :prescriptions, :class_name => "Medical::Prescription" 6 | end 7 | class Prescription 8 | include Mongoid::Document 9 | field :name 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/mongoid/matchers/lt.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Matchers #:nodoc: 4 | class Lt < Default 5 | # Return true if the attribute is less than the value. 6 | def matches?(value) 7 | determine(value, :<) 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/mongoid/extensions/proc/scoping.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Extensions #:nodoc: 4 | module Proc #:nodoc: 5 | module Scoping #:nodoc: 6 | def scoped(*args) 7 | call(*args).scoped 8 | end 9 | end 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/mongoid/matchers/gt.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Matchers #:nodoc: 4 | class Gt < Default 5 | # Return true if the attribute is greater than the value. 6 | def matches?(value) 7 | determine(value, :>) 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/mongoid/matchers/in.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Matchers #:nodoc: 4 | class In < Default 5 | # Return true if the attribute is in the values. 6 | def matches?(value) 7 | value.values.first.include?(@attribute) 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/mongoid/matchers/lte.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Matchers #:nodoc: 4 | class Lte < Default 5 | # Return true if the attribute is less than or equal to the value. 6 | def matches?(value) 7 | determine(value, :<=) 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/mongoid/extensions/datetime/conversions.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Extensions #:nodoc: 4 | module DateTime #:nodoc: 5 | module Conversions #:nodoc: 6 | def get(value) 7 | super.try(:to_datetime) 8 | end 9 | end 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/mongoid/matchers/gte.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Matchers #:nodoc: 4 | class Gte < Default 5 | # Return true if the attribute is greater than or equal to the value. 6 | def matches?(value) 7 | determine(value, :>=) 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/mongoid/matchers/ne.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Matchers #:nodoc: 4 | class Ne < Default 5 | # Return true if the attribute and first value are not equal. 6 | def matches?(value) 7 | @attribute != value.values.first 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/mongoid/matchers/nin.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Matchers #:nodoc: 4 | class Nin < Default 5 | # Return true if the attribute is not in the value list. 6 | def matches?(value) 7 | !value.values.first.include?(@attribute) 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/mongoid/matchers/all.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Matchers #:nodoc: 4 | class All < Default 5 | # Return true if the attribute and first value in the hash are equal. 6 | def matches?(value) 7 | @attribute == value.values.first 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/mongoid/matchers/size.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Matchers #:nodoc: 4 | class Size < Default 5 | # Return true if the attribute size is equal to the first value. 6 | def matches?(value) 7 | @attribute.size == value.values.first 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/models/category.rb: -------------------------------------------------------------------------------- 1 | class RootCategory 2 | include Mongoid::Document 3 | embeds_many :categories 4 | end 5 | 6 | class Category 7 | include Mongoid::Document 8 | embedded_in :root_category, :inverse_of => :categories 9 | embedded_in :category, :inverse_of => :categories 10 | embeds_many :categories 11 | 12 | field :name 13 | end -------------------------------------------------------------------------------- /spec/unit/mongoid/extensions/hash/scoping_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Extensions::Hash::Scoping do 4 | 5 | describe "#scoped" do 6 | 7 | it "returns self" do 8 | { :where => { :active => true } }.scoped.should == 9 | { :where => { :active => true } } 10 | end 11 | 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /spec/models/animal.rb: -------------------------------------------------------------------------------- 1 | class Animal 2 | include Mongoid::Document 3 | field :name 4 | field :tags, :type => Array 5 | key :name 6 | embedded_in :person, :inverse_of => :pet 7 | 8 | def tag_list 9 | tags.join(", ") 10 | end 11 | 12 | def tag_list=(_tag_list) 13 | self.tags = _tag_list.split(",").map(&:strip) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/mongoid/extensions/false_class/equality.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Extensions #:nodoc: 4 | module FalseClass #:nodoc: 5 | module Equality #:nodoc: 6 | def is_a?(other) 7 | return true if other.name == "Boolean" 8 | super(other) 9 | end 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/mongoid/extensions/true_class/equality.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Extensions #:nodoc: 4 | module TrueClass #:nodoc: 5 | module Equality #:nodoc: 6 | def is_a?(other) 7 | return true if other.name == "Boolean" 8 | super(other) 9 | end 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/mongoid/extensions/objectid/conversions.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Extensions #:nodoc: 4 | module ObjectID #:nodoc: 5 | module Conversions #:nodoc: 6 | def set(value) 7 | value 8 | end 9 | def get(value) 10 | value 11 | end 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/models/name.rb: -------------------------------------------------------------------------------- 1 | class Name 2 | include Mongoid::Document 3 | field :first_name 4 | field :last_name 5 | field :parent_title 6 | key :first_name, :last_name 7 | embeds_many :translations 8 | embedded_in :namable, :inverse_of => [:name, :names] 9 | 10 | def set_parent=(set = false) 11 | self.parent_title = namable.title if set 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/mongoid/extensions/string/conversions.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Extensions #:nodoc: 4 | module String #:nodoc: 5 | module Conversions #:nodoc: 6 | def set(value) 7 | value.to_s unless value.nil? 8 | end 9 | def get(value) 10 | value 11 | end 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/models/patient.rb: -------------------------------------------------------------------------------- 1 | class Email 2 | include Mongoid::Document 3 | field :address 4 | validates_uniqueness_of :address 5 | embedded_in :patient, :inverse_of => :email 6 | end 7 | 8 | class Patient 9 | include Mongoid::Document 10 | field :title 11 | store_in :inpatient 12 | embeds_many :addresses 13 | embeds_one :email 14 | validates_presence_of :title, :on => :create 15 | end 16 | -------------------------------------------------------------------------------- /lib/mongoid/matchers/exists.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Matchers #:nodoc: 4 | class Exists < Default 5 | # Return true if the attribute exists and checking for existence or 6 | # return true if the attribute does not exist and checking for 7 | # non-existence. 8 | def matches?(value) 9 | @attribute.nil? != value.values.first 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/unit/mongoid/extensions/datetime/conversions_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Extensions::DateTime::Conversions do 4 | describe ".get" do 5 | before do 6 | @time = Time.now.utc 7 | ::Mongoid::Extensions::TimeConversions.stubs(:get).returns(@time) 8 | end 9 | 10 | it "converts to a datetime" do 11 | DateTime.get(@time).should be_kind_of(DateTime) 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/mongoid/extensions/array/parentization.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Extensions #:nodoc: 4 | module Array #:nodoc: 5 | module Parentization #:nodoc: 6 | # Adds the parent document to each element in the array. 7 | def parentize(parent, association_name) 8 | each { |obj| obj.parentize(parent, association_name) } 9 | end 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/unit/mongoid/criterion/complex_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Criterion::Complex do 4 | 5 | let(:complex) { Mongoid::Criterion::Complex.new(:key => :field, :operator => "$gt") } 6 | 7 | describe "#initialize" do 8 | 9 | it "sets the key" do 10 | complex.key.should == :field 11 | end 12 | 13 | it "sets the operator" do 14 | complex.operator.should == "$gt" 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/unit/mongoid/extensions/set/conversions_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Extensions::Set::Conversions do 4 | 5 | describe "#get" do 6 | 7 | it "returns the set if Array" do 8 | Set.get(["test"]).should == Set.new(["test"]) 9 | end 10 | 11 | end 12 | 13 | describe "#set" do 14 | 15 | it "returns an array" do 16 | Set.set(["test"]).should == ["test"] 17 | end 18 | 19 | end 20 | 21 | end 22 | -------------------------------------------------------------------------------- /lib/mongoid/extensions/binary/conversions.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Extensions #:nodoc: 4 | module Binary #:nodoc: 5 | module Conversions #:nodoc: 6 | # Get the value from the db hash. 7 | def get(value) 8 | value 9 | end 10 | # Set the value in the db hash. 11 | def set(value) 12 | value 13 | end 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/unit/mongoid/extensions/binary/conversions_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Extensions::Binary::Conversions do 4 | 5 | let(:bin) do 6 | Binary.new 7 | end 8 | 9 | describe "#get" do 10 | 11 | it "returns self" do 12 | Binary.get(bin).should == bin 13 | end 14 | end 15 | 16 | describe "#set" do 17 | 18 | it "returns self" do 19 | Binary.set(bin).should == bin 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/mongoid/logger.rb: -------------------------------------------------------------------------------- 1 | module Mongoid 2 | class Logger 3 | 4 | delegate :info, :debug, :error, :fatal, :unknown, :to => :logger, :allow_nil => true 5 | 6 | def warn(message) 7 | logger.warn(message) if logger && logger.respond_to?(:warn) 8 | end 9 | 10 | def logger 11 | Mongoid.logger 12 | end 13 | 14 | def inspect 15 | "#" 16 | end 17 | 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/unit/mongoid/extensions/hash/criteria_helpers_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Extensions::Hash::CriteriaHelpers do 4 | 5 | describe "#expand_complex_criteria" do 6 | 7 | before do 8 | @hash = {:age.gt => 40, :title => "Title"} 9 | end 10 | 11 | it "expands complex criteria to form a valid `where` hash" do 12 | @hash.expand_complex_criteria.should == {:age => {"$gt" => 40}, :title => "Title"} 13 | end 14 | 15 | end 16 | 17 | end -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # Use `bundle install` in order to install these gems 2 | # Use `bundle exec rake` in order to run the specs using the bundle 3 | source "http://gemcutter.org" 4 | 5 | gem "bundler", "~>1.0.0.beta" 6 | 7 | gem "activemodel", "~>3.0.0.beta" 8 | gem "tzinfo", "~>0.3.22" 9 | gem "will_paginate", "~>3.0.pre" 10 | gem "mongo", "~> 1.0.5" 11 | gem "bson", "~> 1.0.4" 12 | gem "bson_ext", "~> 1.0.4" 13 | 14 | gem "rspec", "2.0.0.beta.16" 15 | gem "mocha", "0.9.8" 16 | gem "watchr", "0.6" 17 | -------------------------------------------------------------------------------- /spec/unit/mongoid/extensions/array/parentization_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Extensions::Array::Parentization do 4 | 5 | describe "#parentize" do 6 | 7 | before do 8 | @parent = stub 9 | @child = mock 10 | @array = [@child] 11 | end 12 | 13 | it "sets the parent on each element" do 14 | @child.expects(:parentize).with(@parent, :child) 15 | @array.parentize(@parent, :child) 16 | end 17 | 18 | end 19 | 20 | end 21 | -------------------------------------------------------------------------------- /lib/mongoid/extensions/nil/assimilation.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Extensions #:nodoc: 4 | module Nil #:nodoc: 5 | module Assimilation #:nodoc: 6 | # Will remove the child object from the parent. 7 | def assimilate(parent, options, type = nil) 8 | parent.remove_attribute(options.name); self 9 | end 10 | 11 | def collectionize 12 | to_s.collectionize 13 | end 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/models/paranoid_post.rb: -------------------------------------------------------------------------------- 1 | class ParanoidPost 2 | include Mongoid::Document 3 | include Mongoid::Versioning 4 | include Mongoid::Timestamps 5 | include Mongoid::Paranoia 6 | field :title 7 | referenced_in :person 8 | 9 | references_many :tags, :stored_as => :array 10 | 11 | named_scope :recent, where(:created_at => { "$lt" => Time.now, "$gt" => 30.days.ago }) 12 | 13 | class << self 14 | def old 15 | where(:created_at => { "$lt" => 30.days.ago }) 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/mongoid/extensions/float/conversions.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Extensions #:nodoc: 4 | module Float #:nodoc: 5 | module Conversions #:nodoc: 6 | def set(value) 7 | return nil if value.blank? 8 | begin 9 | Float(value) 10 | rescue ArgumentError => e 11 | value 12 | end 13 | end 14 | def get(value) 15 | value 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/unit/mongoid/extensions/objectid/conversions_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Extensions::ObjectID::Conversions do 4 | 5 | let(:object_id) do 6 | BSON::ObjectID.new 7 | end 8 | 9 | describe "#get" do 10 | 11 | it "returns self" do 12 | BSON::ObjectID.get(object_id).should == object_id 13 | end 14 | end 15 | 16 | describe "#set" do 17 | 18 | it "returns self" do 19 | BSON::ObjectID.set(object_id).should == object_id 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /.watchr: -------------------------------------------------------------------------------- 1 | # vim:set filetype=ruby: 2 | def run(cmd) 3 | puts cmd 4 | system cmd 5 | end 6 | 7 | def spec(file) 8 | if File.exists?(file) 9 | run("rspec #{file}") 10 | else 11 | puts("Spec: #{file} does not exist.") 12 | end 13 | end 14 | 15 | watch("spec/.*/*_spec\.rb") do |match| 16 | puts(match[0]) 17 | spec(match[0]) 18 | end 19 | 20 | watch("lib/(.*/.*)\.rb") do |match| 21 | puts(match[1]) 22 | spec("spec/unit/#{match[1]}_spec.rb") 23 | spec("spec/integration/#{match[1]}_spec.rb") 24 | end 25 | -------------------------------------------------------------------------------- /lib/mongoid/extensions/integer/conversions.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Extensions #:nodoc: 4 | module Integer #:nodoc: 5 | module Conversions #:nodoc: 6 | def set(value) 7 | return nil if value.blank? 8 | begin 9 | Integer(value) 10 | rescue ArgumentError => e 11 | value 12 | end 13 | end 14 | def get(value) 15 | value 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/config/mongoid.yml: -------------------------------------------------------------------------------- 1 | defaults: &defaults 2 | host: localhost 3 | slaves: 4 | # - host: localhost 5 | # port: 27018 6 | # - host: localhost 7 | # port: 27019 8 | allow_dynamic_fields: false 9 | parameterize_keys: false 10 | persist_in_safe_mode: false 11 | raise_not_found_error: false 12 | reconnect_time: 5 13 | use_object_ids: true 14 | persist_types: false 15 | option_no_exist: false 16 | skip_version_check: false 17 | 18 | test: 19 | <<: *defaults 20 | database: mongoid_config_test 21 | -------------------------------------------------------------------------------- /lib/mongoid/callbacks.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Callbacks 4 | extend ActiveSupport::Concern 5 | included do 6 | extend ActiveModel::Callbacks 7 | 8 | # Define all the callbacks that are accepted by the document. 9 | define_model_callbacks \ 10 | :create, 11 | :destroy, 12 | :save, 13 | :update, 14 | :validation 15 | end 16 | 17 | def valid?(*) #nodoc 18 | _run_validation_callbacks { super } 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/mongoid/deprecation.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | class Deprecation #:nodoc 4 | include Singleton 5 | 6 | # Alert of a deprecation. This will delegate to the logger and call warn on 7 | # it. 8 | # 9 | # Example: 10 | # 11 | # deprecation.alert("Method no longer used") 12 | def alert(message) 13 | @logger.warn("Deprecation: #{message}") 14 | end 15 | 16 | protected 17 | def initialize 18 | @logger = Mongoid::Logger.new 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/mongoid/extensions/set/conversions.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Extensions #:nodoc: 4 | module Set #:nodoc: 5 | # This module converts set into mongoid related objects. 6 | module Conversions #:nodoc: 7 | extend ActiveSupport::Concern 8 | 9 | module ClassMethods #:nodoc: 10 | def get(value) 11 | ::Set.new(value) 12 | end 13 | def set(value) 14 | value 15 | end 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/mongoid/extensions/big_decimal/conversions.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require "bigdecimal" 3 | 4 | module Mongoid #:nodoc: 5 | module Extensions #:nodoc: 6 | module BigDecimal #:nodoc: 7 | module Conversions #:nodoc: 8 | # Get the string as a +BigDecimal+ 9 | def get(value) 10 | value ? ::BigDecimal.new(value) : value 11 | end 12 | # Set the value in the hash as a string. 13 | def set(value) 14 | value ? value.to_s : value 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/integration/mongoid/extensions_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Extensions do 4 | 5 | context "setting floating point numbers" do 6 | 7 | context "when value is an empty string" do 8 | 9 | let(:person) { Person.new(:ssn => "555555555555555") } 10 | 11 | before do 12 | Person.validates_numericality_of :blood_alcohol_content, :allow_blank => true 13 | end 14 | 15 | it "does not set the value" do 16 | person.save.should be_true 17 | end 18 | 19 | end 20 | end 21 | 22 | end 23 | -------------------------------------------------------------------------------- /lib/mongoid/extensions/array/accessors.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Extensions #:nodoc: 4 | module Array #:nodoc: 5 | module Accessors #:nodoc: 6 | # If the attributes already exists in the array then they will be 7 | # updated, otherwise they will be appended. 8 | def update(attributes) 9 | delete_if { |e| attributes["_id"] && (e["_id"] == attributes["_id"]) } 10 | self.<< attributes 11 | end 12 | 13 | alias :merge! :update 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/unit/mongoid/deprecation_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Deprecation do 4 | 5 | let(:logger) do 6 | stub.quacks_like(Logger.allocate) 7 | end 8 | 9 | before do 10 | Mongoid::Logger.expects(:new).returns(logger) 11 | end 12 | 13 | describe "#alert" do 14 | 15 | let(:deprecation) do 16 | Mongoid::Deprecation.instance 17 | end 18 | 19 | it "calls warn on the memoized logger" do 20 | logger.expects(:warn).with("Deprecation: testing") 21 | deprecation.alert("testing") 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/mongoid/extensions/hash/criteria_helpers.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Extensions #:nodoc: 4 | module Hash #:nodoc: 5 | module CriteriaHelpers #:nodoc: 6 | def expand_complex_criteria 7 | hsh = {} 8 | self.each_pair do |k,v| 9 | if k.class == Mongoid::Criterion::Complex 10 | hsh[k.key] = {"$#{k.operator}" => v} 11 | else 12 | hsh[k] = v 13 | end 14 | end 15 | hsh 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/unit/mongoid/matchers/default_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Matchers::Default do 4 | 5 | let(:matcher) { Mongoid::Matchers::Default.new("Testing") } 6 | 7 | describe "#matches?" do 8 | 9 | context "when the values are equal" do 10 | 11 | it "returns true" do 12 | matcher.matches?("Testing").should be_true 13 | end 14 | 15 | end 16 | 17 | context "when the values are not equal" do 18 | 19 | it "returns false" do 20 | matcher.matches?("Other").should be_false 21 | end 22 | 23 | end 24 | 25 | end 26 | 27 | end 28 | -------------------------------------------------------------------------------- /spec/unit/mongoid/matchers/ne_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Matchers::Ne do 4 | 5 | let(:matcher) { Mongoid::Matchers::Ne.new("first") } 6 | 7 | describe "#matches?" do 8 | 9 | context "when the values are not equal" do 10 | 11 | it "returns true" do 12 | matcher.matches?("$ne" => "second").should be_true 13 | end 14 | 15 | end 16 | 17 | context "when the values are equal" do 18 | 19 | it "returns false" do 20 | matcher.matches?("$ne" => "first").should be_false 21 | end 22 | 23 | end 24 | 25 | end 26 | 27 | end 28 | -------------------------------------------------------------------------------- /lib/mongoid/factory.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | class Factory #:nodoc: 4 | # Builds a new +Document+ from the supplied attributes. 5 | # 6 | # Example: 7 | # 8 | # Mongoid::Factory.build(Person, {}) 9 | # 10 | # Options: 11 | # 12 | # klass: The class to instantiate from if _type is not present. 13 | # attributes: The +Document+ attributes. 14 | def self.build(klass, attributes) 15 | attrs = {}.merge(attributes) 16 | type = attrs["_type"] 17 | type ? type.constantize.instantiate(attrs) : klass.instantiate(attrs) 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/unit/mongoid/matchers/all_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Matchers::All do 4 | 5 | let(:matcher) { Mongoid::Matchers::All.new(["first", "second"]) } 6 | 7 | describe "#matches?" do 8 | 9 | context "when the values are equal" do 10 | 11 | it "returns true" do 12 | matcher.matches?("$all" => ["first", "second"]).should be_true 13 | end 14 | 15 | end 16 | 17 | context "when the values are not equal" do 18 | 19 | it "returns false" do 20 | matcher.matches?("$all" => ["first"]).should be_false 21 | end 22 | 23 | end 24 | 25 | end 26 | 27 | end 28 | -------------------------------------------------------------------------------- /spec/unit/mongoid/matchers/size_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Matchers::Size do 4 | 5 | let(:matcher) { Mongoid::Matchers::Size.new(["first", "second"]) } 6 | 7 | describe "#matches?" do 8 | 9 | context "when the attribute is the same size" do 10 | 11 | it "returns true" do 12 | matcher.matches?("$size" => 2).should be_true 13 | end 14 | 15 | end 16 | 17 | context "when the attribute is not the same size" do 18 | 19 | it "returns false" do 20 | matcher.matches?("$size" => 5).should be_false 21 | end 22 | 23 | end 24 | 25 | end 26 | 27 | end 28 | -------------------------------------------------------------------------------- /lib/mongoid/extensions/object/conversions.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Extensions #:nodoc: 4 | module Object #:nodoc: 5 | # This module converts objects into mongoid related objects. 6 | module Conversions #:nodoc: 7 | extend ActiveSupport::Concern 8 | 9 | module ClassMethods 10 | def set(value) 11 | value.respond_to?(:raw_attributes) ? value.raw_attributes : value 12 | end 13 | 14 | def get(value) 15 | value ? self.instantiate(value) : value 16 | end 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/unit/mongoid/matchers/in_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Matchers::In do 4 | 5 | let(:matcher) { Mongoid::Matchers::In.new("first") } 6 | 7 | describe "#matches?" do 8 | 9 | context "when the values includes the attribute" do 10 | 11 | it "returns true" do 12 | matcher.matches?("$in" => ["first", "second"]).should be_true 13 | end 14 | 15 | end 16 | 17 | context "when the values dont include the atribute" do 18 | 19 | it "returns false" do 20 | matcher.matches?("$in" => ["third"]).should be_false 21 | end 22 | 23 | end 24 | 25 | end 26 | 27 | end 28 | -------------------------------------------------------------------------------- /lib/mongoid/criterion/complex.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Criterion #:nodoc: 4 | # Complex criterion are used when performing operations on symbols to get 5 | # get a shorthand syntax for where clauses. 6 | # 7 | # Example: 8 | # 9 | # { :field => { "$lt" => "value" } } 10 | # becomes: 11 | # { :field.lt => "value } 12 | class Complex 13 | attr_accessor :key, :operator 14 | 15 | # Create the new complex criterion. 16 | def initialize(opts = {}) 17 | @key, @operator = opts[:key], opts[:operator] 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/unit/mongoid/extensions/string/conversions_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Extensions::String::Conversions do 4 | 5 | describe "#set" do 6 | 7 | context "when the value is not nil" do 8 | 9 | it "returns the object to_s" do 10 | String.set(1).should == "1" 11 | end 12 | end 13 | 14 | context "when the value is nil" do 15 | 16 | it "returns nil" do 17 | String.set(nil).should be_nil 18 | end 19 | end 20 | end 21 | 22 | describe "#get" do 23 | it "returns the string" do 24 | String.get("test").should == "test" 25 | end 26 | end 27 | 28 | end 29 | -------------------------------------------------------------------------------- /spec/unit/mongoid/matchers/nin_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Matchers::Nin do 4 | 5 | let(:matcher) { Mongoid::Matchers::Nin.new("first") } 6 | 7 | describe "#matches?" do 8 | 9 | context "when the values do not contain the attribute" do 10 | 11 | it "returns true" do 12 | matcher.matches?("$nin" => ["second", "third"]).should be_true 13 | end 14 | 15 | end 16 | 17 | context "when the values contain the attribute" do 18 | 19 | it "returns false" do 20 | matcher.matches?("$nin" => ["first"]).should be_false 21 | end 22 | 23 | end 24 | 25 | end 26 | 27 | end 28 | -------------------------------------------------------------------------------- /lib/mongoid/javascript.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | class Javascript 4 | # Constant for the file that defines all the js functions. 5 | FUNCTIONS = File.join(File.dirname(__FILE__), "javascript", "functions.yml") 6 | 7 | # Load the javascript functions and define a class method for each one, 8 | # that memoizes the value. 9 | # 10 | # Example: 11 | # 12 | # Mongoid::Javascript.aggregate 13 | YAML.load(File.read(FUNCTIONS)).each_pair do |key, function| 14 | (class << self; self; end).class_eval <<-EOT 15 | def #{key} 16 | @#{key} ||= "#{function}" 17 | end 18 | EOT 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/rails/generators/mongoid/model/templates/model.rb: -------------------------------------------------------------------------------- 1 | class <%= class_name %><%= " < #{options[:parent].classify}" if options[:parent] %> 2 | <% unless options[:parent] -%> 3 | include Mongoid::Document 4 | <% end -%> 5 | <% if options[:timestamps] -%> 6 | include Mongoid::Timestamps 7 | <% end -%> 8 | <%= 'include Mongoid::Versioning' if options[:versioning] -%> 9 | <% attributes.reject{|attr| attr.reference?}.each do |attribute| -%> 10 | field :<%= attribute.name %>, :type => <%= attribute.type_class %> 11 | <% end -%> 12 | <% attributes.select{|attr| attr.reference? }.each do |attribute| -%> 13 | embedded_in :<%= attribute.name%>, :inverse_of => :<%= class_name.tableize %> 14 | <% end -%> 15 | end 16 | -------------------------------------------------------------------------------- /lib/mongoid/extensions/date/conversions.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Extensions #:nodoc: 4 | module Date #:nodoc: 5 | module Conversions #:nodoc: 6 | def get(value) 7 | return nil if value.blank? 8 | if Mongoid::Config.instance.use_utc? 9 | value.to_date 10 | else 11 | ::Date.new(value.year, value.month, value.day) 12 | end 13 | end 14 | 15 | protected 16 | 17 | def convert_to_time(value) 18 | value = ::Date.parse(value) if value.is_a?(::String) 19 | ::Time.utc(value.year, value.month, value.day) 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/mongoid/extensions/array/conversions.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Extensions #:nodoc: 4 | module Array #:nodoc: 5 | # This module converts arrays into mongoid related objects. 6 | module Conversions #:nodoc: 7 | extend ActiveSupport::Concern 8 | 9 | module ClassMethods #:nodoc: 10 | def raise_or_return(value) 11 | unless value.nil? || value.is_a?(::Array) 12 | raise Mongoid::Errors::InvalidType.new(::Array, value) 13 | end 14 | value 15 | end 16 | 17 | alias :get :raise_or_return 18 | alias :set :raise_or_return 19 | end 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/rails/generators/mongoid/config/templates/mongoid.yml: -------------------------------------------------------------------------------- 1 | defaults: &defaults 2 | host: localhost 3 | # slaves: 4 | # - host: slave1.local 5 | # port: 27018 6 | # - host: slave2.local 7 | # port: 27019 8 | 9 | development: 10 | <<: *defaults 11 | database: <%= database_name || app_name %>_development 12 | 13 | test: 14 | <<: *defaults 15 | database: <%= database_name || app_name %>_test 16 | 17 | # set these environment variables on your prod server 18 | production: 19 | <<: *defaults 20 | host: <%%= ENV['MONGOID_HOST'] %> 21 | port: <%%= ENV['MONGOID_PORT'] %> 22 | username: <%%= ENV['MONGOID_USERNAME'] %> 23 | password: <%%= ENV['MONGOID_PASSWORD'] %> 24 | database: <%%= ENV['MONGOID_DATABASE'] %> -------------------------------------------------------------------------------- /spec/models/post.rb: -------------------------------------------------------------------------------- 1 | class Tag 2 | include Mongoid::Document 3 | field :text 4 | referenced_in :post, :stored_as => :array 5 | end 6 | 7 | class Post 8 | include Mongoid::Document 9 | include Mongoid::Versioning 10 | include Mongoid::Timestamps 11 | field :title 12 | referenced_in :person 13 | referenced_in :author, :foreign_key => :author_id, :class_name => "User" 14 | referenced_in :poster, :foreign_key => :poster_id, :class_name => "Agent" 15 | 16 | references_many :tags, :stored_as => :array 17 | 18 | named_scope :recent, where(:created_at => { "$lt" => Time.now, "$gt" => 30.days.ago }) 19 | 20 | class << self 21 | def old 22 | where(:created_at => { "$lt" => 30.days.ago }) 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/mongoid/contexts/ids.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Contexts #:nodoc: 4 | module Ids 5 | # Return documents based on an id search. Will handle if a single id has 6 | # been passed or mulitple ids. 7 | # 8 | # Example: 9 | # 10 | # context.id_criteria([1, 2, 3]) 11 | # 12 | # Returns: 13 | # 14 | # The single or multiple documents. 15 | def id_criteria(params) 16 | criteria.id(params) 17 | result = params.is_a?(Array) ? criteria.entries : one 18 | if Mongoid.raise_not_found_error 19 | raise Errors::DocumentNotFound.new(klass, params) if result.blank? 20 | end 21 | return result 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/mongoid/matchers/default.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Matchers #:nodoc: 4 | class Default 5 | # Creating a new matcher only requires the value. 6 | def initialize(attribute) 7 | @attribute = attribute 8 | end 9 | # Return true if the attribute and value are equal. 10 | def matches?(value) 11 | @attribute == value 12 | end 13 | 14 | protected 15 | # Return the first value in the hash. 16 | def first(value) 17 | value.values.first 18 | end 19 | 20 | # If object exists then compare, else return false 21 | def determine(value, operator) 22 | @attribute ? @attribute.send(operator, first(value)) : false 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/rails/generators/mongoid/config/config_generator.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require 'rails/generators/mongoid_generator' 3 | 4 | module Mongoid 5 | module Generators 6 | class ConfigGenerator < Rails::Generators::Base 7 | desc "Creates a Mongoid configuration file at config/mongoid.yml" 8 | 9 | argument :database_name, :type => :string, :optional => true 10 | 11 | def self.source_root 12 | @_mongoid_source_root ||= File.expand_path("../templates", __FILE__) 13 | end 14 | 15 | def app_name 16 | Rails::Application.subclasses.first.parent.to_s.underscore 17 | end 18 | 19 | def create_config_file 20 | template 'mongoid.yml', File.join('config', "mongoid.yml") 21 | end 22 | 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/mongoid/extensions/boolean/conversions.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Extensions #:nodoc: 4 | module Boolean #:nodoc: 5 | module Conversions #:nodoc: 6 | extend ActiveSupport::Concern 7 | 8 | BOOLEAN_MAP = { 9 | true => true, "true" => true, "TRUE" => true, "1" => true, 1 => true, 1.0 => true, 10 | false => false, "false" => false, "FALSE" => false, "0" => false, 0 => false, 0.0 => false 11 | } 12 | 13 | module ClassMethods #:nodoc 14 | 15 | def set(value) 16 | value = BOOLEAN_MAP[value] 17 | value.nil? ? nil : value 18 | end 19 | 20 | def get(value) 21 | value 22 | end 23 | end 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/mongoid/javascript/functions.yml: -------------------------------------------------------------------------------- 1 | aggregate: 2 | "function(obj, prev) { 3 | prev.count++; 4 | }" 5 | 6 | group: 7 | "function(obj, prev) { 8 | prev.group.push(obj); 9 | }" 10 | 11 | max: 12 | "function(obj, prev) { 13 | if (prev.max == 'start') { 14 | prev.max = obj.[field]; 15 | } 16 | if (prev.max < obj.[field]) { 17 | prev.max = obj.[field]; 18 | } 19 | }" 20 | 21 | min: 22 | "function(obj, prev) { 23 | if (prev.min == 'start') { 24 | prev.min = obj.[field]; 25 | } 26 | if (prev.min > obj.[field]) { 27 | prev.min = obj.[field]; 28 | } 29 | }" 30 | 31 | sum: 32 | "function(obj, prev) { 33 | if (prev.sum == 'start') { 34 | prev.sum = 0; 35 | } 36 | prev.sum += obj.[field]; 37 | }" 38 | -------------------------------------------------------------------------------- /lib/mongoid/contexts.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require "mongoid/contexts/ids" 3 | require "mongoid/contexts/paging" 4 | require "mongoid/contexts/enumerable" 5 | require "mongoid/contexts/mongo" 6 | 7 | module Mongoid 8 | module Contexts 9 | # Determines the context to be used for this criteria. If the class is an 10 | # embedded document, then the context will be the array in the has_many 11 | # association it is in. If the class is a root, then the database itself 12 | # will be the context. 13 | # 14 | # Example: 15 | # 16 | # Contexts.context_for(criteria) 17 | def self.context_for(criteria) 18 | if criteria.klass.embedded? 19 | return Contexts::Enumerable.new(criteria) 20 | end 21 | Contexts::Mongo.new(criteria) 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/unit/mongoid/extensions/array/assimilation_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Extensions::Array::Assimilation do 4 | 5 | describe "#assimilate" do 6 | 7 | before do 8 | @address_one = { :street => "Circular Quay" } 9 | @address_two = Address.new(:street => "King St.") 10 | @parent = Person.new(:title => "Mr.") 11 | @options = Mongoid::Associations::Options.new(:name => :addresses) 12 | @child = [@address_one, @address_two] 13 | end 14 | 15 | it "incorporates the hash into the object graph" do 16 | @child.assimilate(@parent, @options) 17 | @parent.addresses.size.should == 2 18 | @parent.addresses.first.street.should == "Circular Quay" 19 | @parent.addresses.last.street.should == "King St." 20 | end 21 | 22 | end 23 | 24 | end 25 | -------------------------------------------------------------------------------- /spec/unit/mongoid/extensions/nil/assimilation_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Extensions::Nil::Assimilation do 4 | 5 | describe "#assimilate" do 6 | 7 | before do 8 | @name = Name.new(:first_name => "Durran") 9 | @parent = Person.new(:title => "Mr.", :name => @name) 10 | @options = Mongoid::Associations::Options.new(:name => :name) 11 | end 12 | 13 | it "removes the child attribute from the parent" do 14 | nil.assimilate(@parent, @options) 15 | @parent.attributes[:name].should be_nil 16 | end 17 | 18 | it "returns nil" do 19 | nil.assimilate(@parent, @options).should be_nil 20 | end 21 | end 22 | 23 | describe "#collectionize" do 24 | 25 | it "returns ''" do 26 | nil.collectionize.should == "" 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/unit/mongoid/extensions/true_class/equality_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe TrueClass do 4 | 5 | describe "#is_a?" do 6 | 7 | context "when provided a Boolean" do 8 | 9 | it "returns true" do 10 | true.is_a?(Boolean).should be_true 11 | end 12 | end 13 | 14 | context "when provided a FalseClass" do 15 | 16 | it "returns false" do 17 | true.is_a?(FalseClass).should be_false 18 | end 19 | end 20 | 21 | context "when provided a TrueClass" do 22 | 23 | it "returns true" do 24 | true.is_a?(TrueClass).should be_true 25 | end 26 | end 27 | 28 | context "when provided an invalid class" do 29 | 30 | it "returns false" do 31 | false.is_a?(String).should be_false 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/unit/mongoid/factory_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Factory do 4 | 5 | describe ".build" do 6 | 7 | context "when the _type attribute is present" do 8 | 9 | before do 10 | @attributes = { "_type" => "Person", "title" => "Sir" } 11 | end 12 | 13 | it "instantiates based on the type" do 14 | person = Mongoid::Factory.build(Person, @attributes) 15 | person.title.should == "Sir" 16 | end 17 | end 18 | 19 | context "when _type is not preset" do 20 | 21 | before do 22 | @attributes = { "title" => "Sir" } 23 | end 24 | 25 | it "instantiates based on the type" do 26 | person = Mongoid::Factory.build(Person, @attributes) 27 | person.title.should == "Sir" 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/unit/mongoid/extensions/false_class/equality_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe FalseClass do 4 | 5 | describe "#is_a?" do 6 | 7 | context "when provided a Boolean" do 8 | 9 | it "returns true" do 10 | false.is_a?(Boolean).should be_true 11 | end 12 | end 13 | 14 | context "when provided a FalseClass" do 15 | 16 | it "returns true" do 17 | false.is_a?(FalseClass).should be_true 18 | end 19 | end 20 | 21 | context "when provided a TrueClass" do 22 | 23 | it "returns false" do 24 | false.is_a?(TrueClass).should be_false 25 | end 26 | end 27 | 28 | context "when provided an invalid class" do 29 | 30 | it "returns false" do 31 | false.is_a?(String).should be_false 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/models/parents.rb: -------------------------------------------------------------------------------- 1 | class ParentDoc 2 | include Mongoid::Document 3 | 4 | embeds_many :child_docs 5 | 6 | field :statistic 7 | field :children_order, :type => Array, :default => [] # hold all the children's id 8 | end 9 | 10 | 11 | class ChildDoc 12 | include Mongoid::Document 13 | 14 | embedded_in :parent_doc, :inverse_of => :child_docs 15 | 16 | attr_writer :position 17 | 18 | after_save :update_position 19 | 20 | def position 21 | exsited_position = parent_doc.children_order.index(id) 22 | exsited_position ? exsited_position + 1 : parent_doc.aspects.size 23 | end 24 | 25 | def update_position 26 | if @position && (@position.to_i > 0) 27 | parent_doc.children_order.delete(id) 28 | parent_doc.children_order.insert(@position.to_i - 1, id) 29 | parent_doc.save 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /spec/unit/mongoid/contexts_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Contexts do 4 | 5 | context ".context_for" do 6 | let(:klass) { stub('klass', :embedded? => false) } 7 | let(:criteria) { stub('criteria', :klass => klass) } 8 | 9 | context "when criteria is for a top-level Mongoid::Document" do 10 | it "creates a Mongo context" do 11 | Mongoid::Contexts::Mongo.expects(:new).with(criteria) 12 | Mongoid::Contexts.context_for(criteria) 13 | end 14 | end 15 | 16 | context "when criteria is for an embedded Mongoid::Document" do 17 | it "creates a Mongo context" do 18 | klass.stubs(:embedded?).returns(true) 19 | Mongoid::Contexts::Enumerable.expects(:new).with(criteria) 20 | Mongoid::Contexts.context_for(criteria) 21 | end 22 | end 23 | end 24 | 25 | end 26 | -------------------------------------------------------------------------------- /spec/unit/mongoid/extensions/big_decimal/conversions_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Extensions::BigDecimal::Conversions do 4 | 5 | let(:number) do 6 | BigDecimal.new("123456.789") 7 | end 8 | 9 | describe "#get" do 10 | 11 | it "converts the string to a big decimal" do 12 | BigDecimal.get("123456.789").should == number 13 | end 14 | 15 | context "when nil" do 16 | 17 | it "returns nil" do 18 | BigDecimal.get(nil).should be_nil 19 | end 20 | end 21 | end 22 | 23 | describe "#set" do 24 | 25 | it "converts the big decimal to a string" do 26 | BigDecimal.set(number).should == "123456.789" 27 | end 28 | 29 | context "when nil" do 30 | 31 | it "returns nil" do 32 | BigDecimal.set(nil).should be_nil 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/unit/mongoid/extensions/proc/scoping_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Extensions::Proc::Scoping do 4 | 5 | describe "#scoped" do 6 | 7 | context "when the proc accessed a hash" do 8 | 9 | before do 10 | @proc = lambda { |number| { :where => { :count => number } } } 11 | end 12 | 13 | it "calls the hash with the args" do 14 | @proc.scoped(10).should == { :where => { :count => 10 } } 15 | end 16 | 17 | end 18 | 19 | context "when the proc calls a criteria" do 20 | 21 | before do 22 | @proc = lambda { |title| Person.where(:title => title) } 23 | end 24 | 25 | it "returns the criteria scoped" do 26 | @proc.scoped("Sir").should == 27 | { :where => { :title => "Sir" } } 28 | end 29 | 30 | end 31 | 32 | end 33 | 34 | end 35 | -------------------------------------------------------------------------------- /spec/integration/mongoid/callback_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Callbacks do 4 | 5 | before do 6 | ValidationCallback.delete_all 7 | ParentDoc.delete_all 8 | end 9 | 10 | context "callback on valid?" do 11 | it 'should go in all validation callback in good order' do 12 | shin = ValidationCallback.new 13 | shin.valid? 14 | shin.history.should == [:before_validation, :validate, :after_validation] 15 | end 16 | end 17 | 18 | context "when creating child documents in callbacks" do 19 | 20 | let(:parent) do 21 | ParentDoc.new 22 | end 23 | 24 | before do 25 | parent.save 26 | end 27 | 28 | it "does not duplicate the child documents" do 29 | parent.child_docs.create(:position => 1) 30 | ParentDoc.find(parent.id).child_docs.size.should == 1 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/unit/mongoid/matchers/lt_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Matchers::Lt do 4 | 5 | describe "#matches?" do 6 | 7 | context "when the value is larger" do 8 | 9 | let(:matcher) { Mongoid::Matchers::Lt.new(5) } 10 | 11 | it "returns false" do 12 | matcher.matches?("$lt" => 3).should be_false 13 | end 14 | 15 | end 16 | 17 | context "when the value is smaller" do 18 | 19 | let(:matcher) { Mongoid::Matchers::Lt.new(5) } 20 | 21 | it "returns true" do 22 | matcher.matches?("$lt" => 10).should be_true 23 | end 24 | 25 | end 26 | 27 | context "when the value is nil" do 28 | 29 | let(:matcher) { Mongoid::Matchers::Lt.new(nil) } 30 | 31 | it "returns false" do 32 | matcher.matches?("$lt" => 5).should be_false 33 | end 34 | 35 | end 36 | 37 | end 38 | 39 | end 40 | -------------------------------------------------------------------------------- /spec/unit/mongoid/timestamps_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Timestamps do 4 | 5 | describe ".included" do 6 | 7 | before do 8 | @person = Person.new 9 | end 10 | 11 | it "adds created_at and updated_at to the document" do 12 | fields = Person.fields 13 | fields["created_at"].should_not be_nil 14 | fields["updated_at"].should_not be_nil 15 | end 16 | 17 | it "forces the timestamps to UTC" do 18 | @person.run_callbacks(:create) 19 | @person.run_callbacks(:save) 20 | @person.created_at.should be_close(Time.now.utc, 10.seconds) 21 | @person.updated_at.should be_close(Time.now.utc, 10.seconds) 22 | end 23 | 24 | it "includes a record_timestamps class_accessor to ease AR compatibility" do 25 | Person.should.respond_to? :record_timestamps 26 | end 27 | 28 | end 29 | 30 | end 31 | -------------------------------------------------------------------------------- /lib/rails/generators/mongoid/model/model_generator.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require "rails/generators/mongoid_generator" 3 | 4 | module Mongoid #:nodoc: 5 | module Generators #:nodoc: 6 | class ModelGenerator < Base #:nodoc: 7 | 8 | desc "Creates a Mongoid model" 9 | argument :attributes, :type => :array, :default => [], :banner => "field:type field:type" 10 | 11 | check_class_collision 12 | 13 | class_option :timestamps, :type => :boolean 14 | class_option :parent, :type => :string, :desc => "The parent class for the generated model" 15 | class_option :versioning, :type => :boolean, :default => false, :desc => "Enable mongoid versioning" 16 | 17 | def create_model_file 18 | template "model.rb", File.join("app/models", class_path, "#{file_name}.rb") 19 | end 20 | 21 | hook_for :test_framework 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/unit/mongoid/matchers/gt_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Matchers::Gt do 4 | 5 | describe "#matches?" do 6 | 7 | context "when the value is larger" do 8 | 9 | let(:matcher) { Mongoid::Matchers::Gt.new(5) } 10 | 11 | it "returns true" do 12 | matcher.matches?("$gte" => 3).should be_true 13 | end 14 | 15 | end 16 | 17 | context "when the value is smaller" do 18 | 19 | let(:matcher) { Mongoid::Matchers::Gt.new(5) } 20 | 21 | it "returns false" do 22 | matcher.matches?("$gte" => 10).should be_false 23 | end 24 | 25 | end 26 | 27 | context "when the value is nil" do 28 | 29 | let(:matcher) { Mongoid::Matchers::Gt.new(nil) } 30 | 31 | it "returns false" do 32 | matcher.matches?("$gte" => 5).should be_false 33 | end 34 | 35 | end 36 | 37 | end 38 | 39 | end 40 | -------------------------------------------------------------------------------- /lib/mongoid/collections/master.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Collections #:nodoc: 4 | class Master 5 | 6 | attr_reader :collection 7 | 8 | # All read and write operations should delegate to the master connection. 9 | # These operations mimic the methods on a Mongo:Collection. 10 | # 11 | # Example: 12 | # 13 | # collection.save({ :name => "Al" }) 14 | Operations::ALL.each do |name| 15 | define_method(name) { |*args| collection.send(name, *args) } 16 | end 17 | 18 | # Create the new database writer. Will create a collection from the 19 | # master database. 20 | # 21 | # Example: 22 | # 23 | # Master.new(master, "mongoid_people") 24 | def initialize(master, name) 25 | @collection = master.collection(name) 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/integration/mongoid/contexts/enumerable_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Contexts::Enumerable do 4 | 5 | before do 6 | @person = Person.new(:title => "Sir") 7 | 10.times do |n| 8 | @person.addresses << Address.new(:number => n, :street => "Upper Street") 9 | end 10 | end 11 | 12 | describe "#paginate" do 13 | 14 | it "paginates the embedded documents" do 15 | addresses = @person.addresses.paginate(:page => nil, :per_page => 5) 16 | addresses.current_page.should == 1 17 | addresses.size.should == 5 18 | end 19 | end 20 | 21 | describe "limit and skip" do 22 | 23 | it "limits" do 24 | @person.addresses.criteria.limit(5).size.should == 5 25 | end 26 | 27 | it "skips" do 28 | @person.addresses.criteria.skip(5).limit(10). 29 | map(&:number).should == [5, 6, 7, 8, 9] 30 | end 31 | 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/mongoid/indexes.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc 3 | module Indexes #:nodoc 4 | extend ActiveSupport::Concern 5 | included do 6 | cattr_accessor :indexed 7 | self.indexed = false 8 | end 9 | 10 | module ClassMethods #:nodoc 11 | # Add the default indexes to the root document if they do not already 12 | # exist. Currently this is only _type. 13 | def add_indexes 14 | if hereditary && !indexed 15 | self._collection.create_index(:_type, :unique => false, :background => true) 16 | self.indexed = true 17 | end 18 | end 19 | 20 | # Adds an index on the field specified. Options can be :unique => true or 21 | # :unique => false. It will default to the latter. 22 | def index(name, options = { :unique => false }) 23 | collection.create_index(name, options) 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/models/address.rb: -------------------------------------------------------------------------------- 1 | class Address 2 | include Mongoid::Document 3 | field :address_type 4 | field :number, :type => Integer 5 | field :street 6 | field :city 7 | field :state 8 | field :post_code 9 | field :parent_title 10 | field :services, :type => Array 11 | key :street 12 | embeds_many :locations 13 | 14 | embedded_in :addressable, :inverse_of => :addresses do 15 | def extension 16 | "Testing" 17 | end 18 | def doctor? 19 | title == "Dr" 20 | end 21 | end 22 | 23 | named_scope :rodeo, where(:street => "Rodeo Dr") 24 | 25 | validates_presence_of :street, :on => :update 26 | 27 | def set_parent=(set = false) 28 | self.parent_title = addressable.title if set 29 | end 30 | 31 | class << self 32 | def california 33 | where(:state => "CA") 34 | end 35 | 36 | def homes 37 | where(:address_type => "Home") 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/unit/mongoid/extensions/hash/conversions_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Extensions::Hash::Conversions do 4 | 5 | describe "#difference" do 6 | 7 | let(:first) do 8 | { :field1 => "old1", :field2 => "old2", :field3 => "old3" } 9 | end 10 | 11 | let(:second) do 12 | { :field1 => "new1", :field2 => "new2" } 13 | end 14 | 15 | it "returns a new hash of keys with old and new values" do 16 | first.difference(second).should == 17 | { :field1 => [ "old1", "new1" ], :field2 => [ "old2", "new2" ] } 18 | end 19 | end 20 | 21 | describe ".get" do 22 | 23 | it "returns the hash" do 24 | Hash.get({ :field => "test" }).should == { :field => "test" } 25 | end 26 | 27 | end 28 | 29 | describe ".set" do 30 | 31 | it "returns the hash" do 32 | Hash.set({ :field => "test" }).should == { :field => "test" } 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/mongoid/timestamps.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Timestamps 4 | extend ActiveSupport::Concern 5 | included do 6 | field :created_at, :type => Time 7 | field :updated_at, :type => Time 8 | set_callback :create, :before, :set_created_at 9 | set_callback :save, :before, :set_updated_at 10 | 11 | class_inheritable_accessor :record_timestamps, :instance_writer => false 12 | self.record_timestamps = true 13 | end 14 | 15 | # Update the created_at field on the Document to the current time. This is 16 | # only called on create. 17 | def set_created_at 18 | self.created_at = Time.now.utc if !created_at 19 | end 20 | 21 | # Update the updated_at field on the Document to the current time. 22 | # This is only called on create and on save. 23 | def set_updated_at 24 | self.updated_at = Time.now.utc 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 2 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib")) 3 | 4 | MODELS = File.join(File.dirname(__FILE__), "models") 5 | $LOAD_PATH.unshift(MODELS) 6 | 7 | require 'rubygems' 8 | 9 | gem "mocha", ">= 0.9.8" 10 | 11 | require "mongoid" 12 | require "mocha" 13 | require "rspec" 14 | 15 | Mongoid.configure do |config| 16 | name = "mongoid_test" 17 | host = "localhost" 18 | config.master = Mongo::Connection.new.db(name) 19 | config.logger = nil 20 | # config.slaves = [ 21 | # Mongo::Connection.new(host, 27018, :slave_ok => true).db(name) 22 | # ] 23 | end 24 | 25 | Dir[ File.join(MODELS, "*.rb") ].sort.each { |file| require File.basename(file) } 26 | 27 | Rspec.configure do |config| 28 | config.mock_with :mocha 29 | config.after :suite do 30 | Mongoid.master.collections.select {|c| c.name !~ /system/ }.each(&:drop) 31 | end 32 | end 33 | 34 | -------------------------------------------------------------------------------- /lib/mongoid/extensions/array/assimilation.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Extensions #:nodoc: 4 | module Array #:nodoc: 5 | module Assimilation #:nodoc: 6 | # Introduces a child object into the +Document+ object graph. This will 7 | # set up the relationships between the parent and child and update the 8 | # attributes of the parent +Document+. 9 | # 10 | # Options: 11 | # 12 | # parent: The +Document+ to assimilate into. 13 | # options: The association +Options+ for the child. 14 | # 15 | # Example: 16 | # 17 | # [{:street => "Queen St."}, {:street => "King St."}].assimilate(person, options) 18 | # 19 | # Returns: The child +Document+. 20 | def assimilate(parent, options) 21 | each { |child| child.assimilate(parent, options) } 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/mongoid/collections/cyclic_iterator.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Collections #:nodoc: 4 | class CyclicIterator 5 | 6 | attr_reader :counter 7 | 8 | # Performs iteration over an array, if the array gets to the end then loop 9 | # back to the first. 10 | # 11 | # Example: 12 | # 13 | # CyclicIterator.new([ first, second ]) 14 | def initialize(array) 15 | @array, @counter = array, -1 16 | end 17 | 18 | # Get the next element in the array. If the element is the last in the 19 | # array then return the first. 20 | # 21 | # Example: 22 | # 23 | # iterator.next 24 | # 25 | # Returns: 26 | # 27 | # The next element in the array. 28 | def next 29 | (@counter == @array.size - 1) ? @counter = 0 : @counter = @counter + 1 30 | @array[@counter] 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/unit/mongoid/collections/master_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Collections::Master do 4 | 5 | let(:collection) do 6 | stub.quacks_like(Mongo::Collection.allocate) 7 | end 8 | 9 | let(:db) do 10 | stub.quacks_like(Mongo::DB.allocate) 11 | end 12 | 13 | let(:master) do 14 | Mongoid::Collections::Master.new(db, "people") 15 | end 16 | 17 | before do 18 | db.expects(:collection).with("people").returns(collection) 19 | end 20 | 21 | context "Mongo::Collection operations" do 22 | 23 | Mongoid::Collections::Operations::ALL.each do |name| 24 | 25 | it "defines #{name}" do 26 | master.should respond_to(name) 27 | end 28 | 29 | describe "##{name}" do 30 | 31 | before do 32 | collection.expects(name) 33 | end 34 | 35 | it "delegates to the collection" do 36 | master.send(name) 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/mongoid/extensions/time_conversions.rb: -------------------------------------------------------------------------------- 1 | module Mongoid #:nodoc: 2 | module Extensions #:nodoc: 3 | module TimeConversions #:nodoc: 4 | def set(value) 5 | return nil if value.blank? 6 | time = convert_to_time(value) 7 | strip_milliseconds(time).utc 8 | end 9 | 10 | def get(value) 11 | return nil if value.blank? 12 | if Mongoid::Config.instance.use_utc? 13 | value 14 | else 15 | value.getlocal 16 | end 17 | end 18 | 19 | protected 20 | 21 | def strip_milliseconds(time) 22 | ::Time.at(time.to_i) 23 | end 24 | 25 | def convert_to_time(value) 26 | case value 27 | when ::String then ::Time.parse(value) 28 | when ::DateTime then ::Time.utc(value.year, value.month, value.day, value.hour, value.min, value.sec) 29 | when ::Date then ::Time.utc(value.year, value.month, value.day) 30 | else value 31 | end 32 | end 33 | end 34 | end 35 | end -------------------------------------------------------------------------------- /spec/unit/mongoid/extensions/boolean/conversions_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Extensions::Boolean::Conversions do 4 | 5 | describe ".set" do 6 | 7 | context "when 'true'" do 8 | 9 | it "returns true" do 10 | Boolean.set("true").should be_true 11 | end 12 | end 13 | 14 | context "when 'false'" do 15 | 16 | it "returns false" do 17 | Boolean.set("false").should be_false 18 | end 19 | end 20 | 21 | context "when 0" do 22 | 23 | it "returns false" do 24 | Boolean.set("0").should be_false 25 | end 26 | end 27 | 28 | context "when 1" do 29 | 30 | it "returns true" do 31 | Boolean.set("1").should be_true 32 | end 33 | end 34 | 35 | context "when nil" do 36 | 37 | it "returns nil" do 38 | Boolean.set(nil).should be_nil 39 | end 40 | end 41 | end 42 | 43 | describe ".get" do 44 | 45 | it "returns the boolean" do 46 | Boolean.get(false).should be_false 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/mongoid/state.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module State #:nodoc: 4 | # Returns true if the +Document+ has not been persisted to the database, 5 | # false if it has. This is determined by the variable @new_record 6 | # and NOT if the object has an id. 7 | def new_record? 8 | @new_record == true 9 | end 10 | 11 | # Sets the new_record boolean - used after document is saved. 12 | def new_record=(saved) 13 | @new_record = saved 14 | end 15 | 16 | # Checks if the document has been saved to the database. 17 | def persisted? 18 | !new_record? 19 | end 20 | 21 | # Returns true if the +Document+ has been succesfully destroyed, and false if it hasn't. 22 | # This is determined by the variable @destroyed and NOT by checking the database. 23 | def destroyed? 24 | @destroyed == true 25 | end 26 | 27 | # Sets the destroyed boolean - used after document is destroyed. 28 | def destroyed=(destroyed) 29 | @destroyed = destroyed && true 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /spec/unit/mongoid/versioning_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Versioning do 4 | 5 | describe "#version" do 6 | 7 | before do 8 | @post = Post.new 9 | end 10 | 11 | it "defaults to 1" do 12 | @post.version.should == 1 13 | end 14 | 15 | context "when document is saved" do 16 | 17 | before do 18 | @post.title = "New" 19 | @version = Post.new(:title => "Test") 20 | Post.expects(:first).at_least(1).with(:conditions => { :_id => @post.id, :version => 1 }).returns(@version) 21 | @post.revise 22 | end 23 | 24 | it "increments the version" do 25 | @post.version.should == 2 26 | end 27 | 28 | it "adds a snapshot of the document to the versions" do 29 | @post.title.should == "New" 30 | @post.version.should == 2 31 | @post.versions.size.should == 1 32 | version = @post.versions.first 33 | version.title.should == "Test" 34 | version.version.should == 1 35 | end 36 | 37 | end 38 | 39 | end 40 | 41 | end 42 | -------------------------------------------------------------------------------- /spec/models/callbacks.rb: -------------------------------------------------------------------------------- 1 | class Artist 2 | include Mongoid::Document 3 | field :name 4 | embeds_many :songs 5 | embeds_many :labels 6 | 7 | before_create :before_create_stub 8 | after_create :create_songs 9 | 10 | protected 11 | def before_create_stub 12 | true 13 | end 14 | 15 | def create_songs 16 | 2.times { |n| songs.create!(:title => "#{n}") } 17 | end 18 | end 19 | 20 | class Song 21 | include Mongoid::Document 22 | field :title 23 | embedded_in :artist, :inverse_of => :songs 24 | end 25 | 26 | class Label 27 | include Mongoid::Document 28 | field :name 29 | embedded_in :artist, :inverse_of => :labels 30 | before_validation :cleanup 31 | 32 | private 33 | def cleanup 34 | self.name = self.name.downcase.capitalize 35 | end 36 | end 37 | 38 | class ValidationCallback 39 | include Mongoid::Document 40 | field :history, :type => Array, :default => [] 41 | validate do 42 | self.history << :validate 43 | end 44 | 45 | before_validation { self.history << :before_validation } 46 | after_validation { self.history << :after_validation } 47 | end 48 | -------------------------------------------------------------------------------- /MIT_LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 Durran Jordan 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /lib/mongoid/versioning.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | # Include this module to get automatic versioning of root level documents. 4 | # This will add a version field to the +Document+ and a has_many association 5 | # with all the versions contained in it. 6 | module Versioning 7 | extend ActiveSupport::Concern 8 | included do 9 | field :version, :type => Integer, :default => 1 10 | embeds_many :versions, :class_name => self.name 11 | set_callback :save, :before, :revise 12 | end 13 | 14 | # Create a new version of the +Document+. This will load the previous 15 | # document from the database and set it as the next version before saving 16 | # the current document. It then increments the version number. 17 | def revise 18 | last_version = self.class.first(:conditions => { :_id => id, :version => version }) 19 | if last_version 20 | self.versions << last_version.clone 21 | self.version = version + 1 22 | @modifications["versions"] = [ nil, @attributes["versions"] ] if @modifications 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/mongoid/extensions/symbol/inflections.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Extensions #:nodoc: 4 | module Symbol #:nodoc: 5 | module Inflections #:nodoc: 6 | 7 | REVERSALS = { 8 | :asc => :desc, 9 | :ascending => :descending, 10 | :desc => :asc, 11 | :descending => :ascending 12 | } 13 | 14 | def invert 15 | REVERSALS[self] 16 | end 17 | 18 | def singular? 19 | to_s.singular? 20 | end 21 | 22 | def plural? 23 | to_s.plural? 24 | end 25 | 26 | [ "asc", "ascending", "desc", "descending", "gt", "lt", "gte", 27 | "lte", "ne", "near", "in", "nin", "mod", "all", "size", "exists", 28 | "within", ["matches","elemMatch"] ].each do |oper| 29 | m, oper = oper 30 | oper = m unless oper 31 | class_eval <<-OPERATORS 32 | def #{m} 33 | Criterion::Complex.new(:key => self, :operator => "#{oper}") 34 | end 35 | OPERATORS 36 | end 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/mongoid/persistence/remove.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Persistence #:nodoc: 4 | # Remove is a persistence command responsible for deleting a document from 5 | # the database. 6 | # 7 | # The underlying query resembles the following MongoDB query: 8 | # 9 | # collection.remove( 10 | # { "_id" : 1 }, 11 | # false 12 | # ); 13 | class Remove < Command 14 | # Remove the document from the database: delegates to the MongoDB 15 | # collection remove method. 16 | # 17 | # Example: 18 | # 19 | # Remove.persist 20 | # 21 | # Returns: 22 | # 23 | # +true+ if success, +false+ if not. 24 | def persist 25 | remove 26 | end 27 | 28 | protected 29 | # Remove the document from the database. 30 | def remove 31 | if @document.embedded? 32 | Persistence::RemoveEmbedded.new(@document, @validate).persist 33 | else 34 | @collection.remove({ :_id => @document.id }, @options) 35 | end 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/integration/mongoid/paranoia_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Paranoia do 4 | 5 | before :all do 6 | ParanoidPost.delete_all 7 | end 8 | 9 | context 'a soft deleted item' do 10 | 11 | before :each do 12 | @post = ParanoidPost.create(:title => 'Can I die more than once?') 13 | @post.delete 14 | @post.reload 15 | end 16 | 17 | after :each do 18 | @post.destroy! 19 | end 20 | 21 | it 'should have a deletion date' do 22 | @post.deleted_at.should_not be_nil 23 | end 24 | 25 | it 'should be restorable' do 26 | @post.restore 27 | @post.reload 28 | @post.deleted_at.should be_nil 29 | end 30 | 31 | it 'should be invisible to searches' do 32 | ParanoidPost.count.should == 0 33 | end 34 | 35 | it 'should be found overriding default deleted_at scoping' do 36 | ParanoidPost.where(:deleted_at.ne => nil).count.should == 1 37 | end 38 | 39 | it 'should be hard-destroyable' do 40 | @post.destroy! 41 | ParanoidPost.where(:deleted_at.ne => nil).count.should == 0 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /spec/unit/mongoid/matchers/gte_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Matchers::Gte do 4 | 5 | describe "#matches?" do 6 | 7 | context "when the value is larger" do 8 | 9 | let(:matcher) { Mongoid::Matchers::Gte.new(5) } 10 | 11 | it "returns true" do 12 | matcher.matches?("$gte" => 3).should be_true 13 | end 14 | 15 | end 16 | 17 | context "when the value is smaller" do 18 | 19 | let(:matcher) { Mongoid::Matchers::Gte.new(5) } 20 | 21 | it "returns false" do 22 | matcher.matches?("$gte" => 10).should be_false 23 | end 24 | 25 | end 26 | 27 | context "when the value is equal" do 28 | 29 | let(:matcher) { Mongoid::Matchers::Gte.new(5) } 30 | 31 | it "returns true" do 32 | matcher.matches?("$gte" => 5).should be_true 33 | end 34 | 35 | end 36 | 37 | context "when the value is nil" do 38 | 39 | let(:matcher) { Mongoid::Matchers::Gte.new(nil) } 40 | 41 | it "returns false" do 42 | matcher.matches?("$gte" => 5).should be_false 43 | end 44 | 45 | end 46 | 47 | end 48 | 49 | end 50 | -------------------------------------------------------------------------------- /spec/unit/mongoid/matchers/lte_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Matchers::Lte do 4 | 5 | describe "#matches?" do 6 | 7 | context "when the value is larger" do 8 | 9 | let(:matcher) { Mongoid::Matchers::Lte.new(5) } 10 | 11 | it "returns false" do 12 | matcher.matches?("$lte" => 3).should be_false 13 | end 14 | 15 | end 16 | 17 | context "when the value is smaller" do 18 | 19 | let(:matcher) { Mongoid::Matchers::Lte.new(5) } 20 | 21 | it "returns true" do 22 | matcher.matches?("$lte" => 10).should be_true 23 | end 24 | 25 | end 26 | 27 | context "when the value is equal" do 28 | 29 | let(:matcher) { Mongoid::Matchers::Lte.new(5) } 30 | 31 | it "returns true" do 32 | matcher.matches?("$lte" => 5).should be_true 33 | end 34 | 35 | end 36 | 37 | context "when the value is nil" do 38 | 39 | let(:matcher) { Mongoid::Matchers::Lte.new(nil) } 40 | 41 | it "returns false" do 42 | matcher.matches?("$lte" => 5).should be_false 43 | end 44 | 45 | end 46 | 47 | end 48 | 49 | end 50 | -------------------------------------------------------------------------------- /lib/mongoid/memoization.rb: -------------------------------------------------------------------------------- 1 | module Mongoid #:nodoc 2 | module Memoization 3 | 4 | # Handles cases when accessing an association that should be memoized in 5 | # the Mongoid specific manner. Does not memoize nil values though 6 | def memoized(name, &block) 7 | var = "@#{name}" 8 | if instance_variable_defined?(var) 9 | return instance_variable_get(var) 10 | end 11 | value = yield 12 | instance_variable_set(var, value) if value 13 | end 14 | 15 | # Removes an memozied association if it exists 16 | def unmemoize(name) 17 | var = "@#{name}" 18 | remove_instance_variable(var) if instance_variable_defined?(var) 19 | end 20 | 21 | # Mongoid specific behavior is to remove the memoized object when setting 22 | # the association, or if it wasn't previously memoized it will get set. 23 | def reset(name, &block) 24 | var = "@#{name}" 25 | value = yield 26 | if instance_variable_defined?(var) 27 | remove_instance_variable(var) 28 | else 29 | instance_variable_set(var, value) 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/mongoid/associations/proxy.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc 3 | module Associations #:nodoc 4 | class Proxy #:nodoc 5 | instance_methods.each do |method| 6 | undef_method(method) unless method =~ /(^__|^nil\?$|^send$|^object_id$|^extend$)/ 7 | end 8 | attr_reader \ 9 | :options, 10 | :target 11 | 12 | # Default behavior of method missing should be to delegate all calls 13 | # to the target of the proxy. This can be overridden in special cases. 14 | def method_missing(name, *args, &block) 15 | @target.send(name, *args, &block) 16 | end 17 | 18 | # If anonymous extensions are added this will take care of them. 19 | def extends(options) 20 | extend Module.new(&options.extension) if options.extension? 21 | end 22 | 23 | # Sets up the parent, klass, foreign_key, options 24 | def setup(document, options) 25 | @parent = document 26 | @klass = options.klass 27 | @options = options 28 | @foreign_key = options.foreign_key 29 | extends(options) 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/mongoid/persistence/remove_all.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Persistence #:nodoc: 4 | # Remove is a persistence command responsible for deleting a document from 5 | # the database. 6 | # 7 | # The underlying query resembles the following MongoDB query: 8 | # 9 | # collection.remove( 10 | # { "field" : value }, 11 | # false 12 | # ); 13 | class RemoveAll < Command 14 | # Remove the document from the database: delegates to the MongoDB 15 | # collection remove method. 16 | # 17 | # Example: 18 | # 19 | # Remove.persist 20 | # 21 | # Returns: 22 | # 23 | # +true+ if success, +false+ if not. 24 | def persist 25 | remove 26 | end 27 | 28 | protected 29 | # Remove the document from the database. 30 | def remove 31 | selector = (@klass.hereditary ? @selector.merge(:_type => @klass.name) : @selector) 32 | count = @collection.find(selector).count 33 | @collection.remove(selector, @options) 34 | count 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/unit/mongoid/logger_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Logger do 4 | describe ".logger" do 5 | it "returns Mongoid's configured logger" do 6 | Mongoid.expects(:logger) 7 | subject.logger 8 | end 9 | end 10 | 11 | context "log_levels" do 12 | log_levels = %w(info debug warn error fatal) 13 | 14 | context "with a logger set" do 15 | let(:logger) { stub.quacks_like(Logger.allocate) } 16 | log_levels.each do |log_level| 17 | before do 18 | subject.stubs(:logger => logger) 19 | end 20 | it "#{log_level} delegates to the configured logger" do 21 | logger.expects(log_level).with("message") 22 | subject.send(log_level.to_sym, "message") 23 | end 24 | end 25 | end 26 | 27 | context "with a nil logger" do 28 | log_levels.each do |log_level| 29 | before do 30 | subject.stubs(:logger => nil) 31 | end 32 | it "#{log_level} does nothing" do 33 | expect { subject.send(log_level.to_sym, "message") }.to_not raise_error 34 | end 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/mongoid/collections/operations.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Collections #:nodoc: 4 | module Operations #:nodoc: 5 | # Constant definining all the read operations available for a 6 | # Mongo:Collection. This is used in delegation. 7 | READ = [ 8 | :[], 9 | :db, 10 | :count, 11 | :distinct, 12 | :find, 13 | :find_one, 14 | :group, 15 | :index_information, 16 | :map_reduce, 17 | :mapreduce, 18 | :options 19 | ] 20 | 21 | # Constant definining all the write operations available for a 22 | # Mongo:Collection. This is used in delegation. 23 | WRITE = [ 24 | :<<, 25 | :create_index, 26 | :drop, 27 | :drop_index, 28 | :drop_indexes, 29 | :insert, 30 | :remove, 31 | :rename, 32 | :save, 33 | :update 34 | ] 35 | 36 | # Convenience constant for getting back all collection operations. 37 | ALL = (READ + WRITE) 38 | PROXIED = ALL - [ :find, :find_one, :map_reduce, :mapreduce ] 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/integration/mongoid/persistence/update_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Persistence::Update do 4 | 5 | before do 6 | Person.delete_all 7 | end 8 | 9 | after do 10 | Person.delete_all 11 | end 12 | 13 | describe "#persist" do 14 | 15 | let(:person) do 16 | Person.create!(:ssn => "111-11-1111", :title => "Sir") 17 | end 18 | 19 | context "when the document has changed" do 20 | 21 | before do 22 | @person = Person.find(person.id) 23 | @person.title = "Grand Poobah" 24 | end 25 | 26 | it "updates the document in the database" do 27 | update = Mongoid::Persistence::Update.new(@person) 28 | update.persist 29 | from_db = Person.find(@person.id) 30 | from_db.title.should == "Grand Poobah" 31 | end 32 | end 33 | 34 | context "when the document has not changed" do 35 | 36 | before do 37 | @person = Person.find(person.id) 38 | end 39 | 40 | it "returns true" do 41 | update = Mongoid::Persistence::Update.new(@person) 42 | update.persist.should == true 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /spec/unit/mongoid_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid do 4 | 5 | describe ".configure" do 6 | 7 | context "when no block supplied" do 8 | 9 | it "returns the config singleton" do 10 | Mongoid.configure.should == Mongoid::Config.instance 11 | end 12 | end 13 | 14 | context "when a block is supplied" do 15 | 16 | before do 17 | Mongoid.configure do |config| 18 | config.allow_dynamic_fields = false 19 | end 20 | end 21 | 22 | after do 23 | Mongoid.configure do |config| 24 | config.allow_dynamic_fields = true 25 | end 26 | end 27 | 28 | it "sets the values on the config instance" do 29 | Mongoid.allow_dynamic_fields.should be_false 30 | end 31 | end 32 | end 33 | 34 | describe ".deprecate" do 35 | let(:deprecation) { stub } 36 | 37 | before do 38 | Mongoid::Deprecation.expects(:instance).returns(deprecation) 39 | end 40 | 41 | it "calls alert on the deprecation singleton" do 42 | deprecation.expects(:alert).with("testing") 43 | Mongoid.deprecate("testing") 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /spec/unit/mongoid/extensions/object/conversions_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Extensions::Object::Conversions do 4 | 5 | describe "#get" do 6 | 7 | before do 8 | @attributes = { :_id => "test", :title => "Sir", :age => 100 } 9 | end 10 | 11 | it "instantiates a new class from the attributes" do 12 | Person.get(@attributes).should == Person.new(@attributes) 13 | end 14 | 15 | context "when the value is nil" do 16 | 17 | it "returns nil" do 18 | Person.get(nil).should be_nil 19 | end 20 | end 21 | end 22 | 23 | describe "#set" do 24 | 25 | context "when object has attributes" do 26 | 27 | before do 28 | @attributes = { 29 | "_id" => "test", 30 | "title" => "Sir", 31 | "age" => 100, 32 | "_type" => "Person", 33 | "blood_alcohol_content" => 0.0, 34 | "pets" => false 35 | } 36 | @person = Person.instantiate(@attributes) 37 | end 38 | 39 | it "converts the object to a hash" do 40 | Person.set(@person).except("_id").should == @attributes.except("_id") 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/mongoid/validations/associated.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Validations #:nodoc: 4 | # Validates whether or not an association is valid or not. Will correctly 5 | # handle has one and has many associations. 6 | # 7 | # Example: 8 | # 9 | # class Person 10 | # include Mongoid::Document 11 | # embeds_one :name 12 | # embeds_many :addresses 13 | # 14 | # validates_associated :name, :addresses 15 | # end 16 | class AssociatedValidator < ActiveModel::EachValidator 17 | 18 | # Validates that the associations provided are either all nil or all 19 | # valid. If neither is true then the appropriate errors will be added to 20 | # the parent document. 21 | # 22 | # Example: 23 | # 24 | # validator.validate_each(document, :name, name) 25 | def validate_each(document, attribute, value) 26 | values = value.is_a?(Array) ? value : [ value ] 27 | return if values.collect { |doc| doc.nil? || doc.valid? }.all? 28 | document.errors.add(attribute, :invalid, options.merge!(:value => value)) 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /spec/unit/mongoid/serialization_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Document do 4 | 5 | describe "#to_xml" do 6 | 7 | context "when an Array field is defined" do 8 | 9 | let(:person) do 10 | Person.new( 11 | :aliases => [ "Kelly", "Machine Gun" ] 12 | ) 13 | end 14 | 15 | it "properly types the array" do 16 | person.to_xml.should include("") 17 | end 18 | 19 | it "serializes the array" do 20 | person.to_xml.should include("Kelly") 21 | person.to_xml.should include("Machine Gun") 22 | end 23 | end 24 | 25 | context "when a Hash field is defined" do 26 | 27 | let(:person) do 28 | Person.new( 29 | :map => { :lat => 24.5, :long => 22.1 } 30 | ) 31 | end 32 | 33 | it "properly types the hash" do 34 | person.to_xml.should include("") 35 | end 36 | 37 | it "serializes the hash" do 38 | person.to_xml.should include("24.5") 39 | person.to_xml.should include("22.1") 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/mongoid/associations/foreign_key.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Associations #:nodoc: 4 | module ForeignKey #:nodoc: 5 | extend ActiveSupport::Concern 6 | 7 | module ClassMethods #:nodoc: 8 | # Determine the value for the foreign key constriant field in the 9 | # database, based on the type of association or if the actual value was 10 | # supplied as an option. 11 | # 12 | # Example: 13 | # 14 | # contraint(:posts, {}, :references_one) 15 | # 16 | # Returns 17 | # 18 | # A +String+ for the foreign key field. 19 | def constraint(name, options, association) 20 | key = options[:foreign_key] 21 | 22 | # Always return the supplied foreign_key option if it was supplied - 23 | # the user should always be ble to override. 24 | return key.to_s if key 25 | 26 | case association 27 | when :one, :many then self.name.foreign_key 28 | when :many_as_array then "#{name.to_s.singularize}_ids" 29 | else name.to_s.foreign_key 30 | end 31 | end 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/mongoid/matchers.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require "mongoid/matchers/default" 3 | require "mongoid/matchers/all" 4 | require "mongoid/matchers/exists" 5 | require "mongoid/matchers/gt" 6 | require "mongoid/matchers/gte" 7 | require "mongoid/matchers/in" 8 | require "mongoid/matchers/lt" 9 | require "mongoid/matchers/lte" 10 | require "mongoid/matchers/ne" 11 | require "mongoid/matchers/nin" 12 | require "mongoid/matchers/size" 13 | 14 | module Mongoid #:nodoc: 15 | module Matchers 16 | # Determines if this document has the attributes to match the supplied 17 | # MongoDB selector. Used for matching on embedded associations. 18 | def matches?(selector) 19 | selector.each_pair do |key, value| 20 | return false unless matcher(key, value).matches?(value) 21 | end; true 22 | end 23 | 24 | protected 25 | # Get the matcher for the supplied key and value. Will determine the class 26 | # name from the key. 27 | def matcher(key, value) 28 | if value.is_a?(Hash) 29 | name = "Mongoid::Matchers::#{value.keys.first.gsub("$", "").camelize}" 30 | return name.constantize.new(attributes[key]) 31 | end 32 | Default.new(attributes[key]) 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "rake" 2 | require "rake/rdoctask" 3 | require "rspec" 4 | require "rspec/core/rake_task" 5 | 6 | $LOAD_PATH.unshift File.expand_path("../lib", __FILE__) 7 | require "mongoid/version" 8 | 9 | task :build do 10 | system "gem build mongoid.gemspec" 11 | end 12 | 13 | task :install => :build do 14 | system "sudo gem install mongoid-#{Mongoid::VERSION}.gem" 15 | end 16 | 17 | task :release => :build do 18 | puts "Tagging #{Mongoid::VERSION}..." 19 | system "git tag -a #{Mongoid::VERSION} -m 'Tagging #{Mongoid::VERSION}'" 20 | puts "Pushing to Github..." 21 | system "git push --tags" 22 | puts "Pushing to Gemcutter..." 23 | system "gem push mongoid-#{Mongoid::VERSION}.gem" 24 | end 25 | 26 | Rspec::Core::RakeTask.new(:spec) do |spec| 27 | spec.pattern = "spec/**/*_spec.rb" 28 | end 29 | 30 | Rake::RDocTask.new do |rdoc| 31 | if File.exist?("VERSION.yml") 32 | config = File.read("VERSION") 33 | version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}" 34 | else 35 | version = "" 36 | end 37 | rdoc.rdoc_dir = "rdoc" 38 | rdoc.title = "mongoid #{version}" 39 | rdoc.rdoc_files.include("README*") 40 | rdoc.rdoc_files.include("lib/**/*.rb") 41 | end 42 | 43 | task :default => ["spec"] 44 | -------------------------------------------------------------------------------- /mongoid.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | lib = File.expand_path('../lib/', __FILE__) 3 | $:.unshift lib unless $:.include?(lib) 4 | 5 | require "mongoid/version" 6 | 7 | Gem::Specification.new do |s| 8 | s.name = "mongoid" 9 | s.version = Mongoid::VERSION 10 | s.platform = Gem::Platform::RUBY 11 | s.authors = ["Durran Jordan"] 12 | s.email = ["durran@gmail.com"] 13 | s.homepage = "http://mongoid.org" 14 | s.summary = "Elegent Persistance in Ruby for MongoDB." 15 | s.description = "Mongoid is an ODM (Object Document Mapper) Framework for MongoDB, written in Ruby." 16 | 17 | s.required_rubygems_version = ">= 1.3.6" 18 | s.rubyforge_project = "mongoid" 19 | 20 | s.add_runtime_dependency("activemodel", ["~>3.0.0.beta"]) 21 | s.add_runtime_dependency("tzinfo", ["~>0.3.22"]) 22 | s.add_runtime_dependency("will_paginate", ["~>3.0.pre"]) 23 | s.add_runtime_dependency("mongo", ["~>1.0.5"]) 24 | s.add_runtime_dependency("bson", ["~>1.0.4"]) 25 | 26 | s.add_development_dependency(%q, ["= 2.0.0.beta.16"]) 27 | s.add_development_dependency(%q, ["= 0.9.8"]) 28 | 29 | s.files = Dir.glob("lib/**/*") + %w(MIT_LICENSE README.rdoc) 30 | s.require_path = 'lib' 31 | end 32 | -------------------------------------------------------------------------------- /lib/mongoid/components.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc 3 | module Components #:nodoc 4 | extend ActiveSupport::Concern 5 | included do 6 | # All modules that a +Document+ is composed of are defined in this 7 | # module, to keep the document class from getting too cluttered. 8 | include ActiveModel::Conversion 9 | include ActiveModel::Naming 10 | include ActiveModel::Serialization 11 | include ActiveModel::Serializers::JSON 12 | include ActiveModel::Serializers::Xml 13 | include Mongoid::Associations 14 | include Mongoid::Atomicity 15 | include Mongoid::Attributes 16 | include Mongoid::Collections 17 | include Mongoid::Dirty 18 | include Mongoid::Extras 19 | include Mongoid::Fields 20 | include Mongoid::Hierarchy 21 | include Mongoid::Indexes 22 | include Mongoid::Matchers 23 | include Mongoid::Memoization 24 | include Mongoid::Paths 25 | include Mongoid::Persistence 26 | include Mongoid::State 27 | include Mongoid::Validations 28 | include Mongoid::Callbacks 29 | extend ActiveModel::Translation 30 | extend Mongoid::Finders 31 | extend Mongoid::NamedScope 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/mongoid/extensions/hash/conversions.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Extensions #:nodoc: 4 | module Hash #:nodoc: 5 | module Conversions #:nodoc: 6 | extend ActiveSupport::Concern 7 | 8 | # Get the difference between 2 hashes. This will give back a new hash 9 | # with the keys and pairs of [ old, new ] values. 10 | # 11 | # Example: 12 | # 13 | # first = { :field => "value" } 14 | # second = { :field => "new" } 15 | # first.difference(second) # => { :field => [ "value", "new" ] } 16 | # 17 | # Returns: 18 | # 19 | # A +Hash+ of modifications. 20 | def difference(other) 21 | changes = {} 22 | each_pair do |key, value| 23 | if other.has_key?(key) 24 | new_value = other[key] 25 | changes[key] = [ value, new_value ] if new_value != value 26 | end 27 | end 28 | changes 29 | end 30 | 31 | module ClassMethods #:nodoc: 32 | def get(value) 33 | value 34 | end 35 | def set(value) 36 | value 37 | end 38 | end 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /spec/unit/mongoid/extensions/array/conversions_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Extensions::Array::Conversions do 4 | 5 | describe "#get" do 6 | 7 | context "when the value is not an array" do 8 | 9 | it "raises an error" do 10 | lambda { Array.get("test") }.should raise_error(Mongoid::Errors::InvalidType) 11 | end 12 | end 13 | 14 | context "when the value is nil" do 15 | 16 | it "returns nil" do 17 | Array.get(nil).should be_nil 18 | end 19 | end 20 | 21 | context "when the value is an array" do 22 | 23 | it "returns the array" do 24 | Array.get(["test"]).should == ["test"] 25 | end 26 | end 27 | end 28 | 29 | describe "#set" do 30 | 31 | context "when the value is not an array" do 32 | 33 | it "raises an error" do 34 | lambda { Array.set("test") }.should raise_error(Mongoid::Errors::InvalidType) 35 | end 36 | end 37 | 38 | context "when the value is nil" do 39 | 40 | it "returns nil" do 41 | Array.get(nil).should be_nil 42 | end 43 | end 44 | 45 | context "when the value is an array" do 46 | 47 | it "returns the array" do 48 | Array.set(["test"]).should == ["test"] 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/mongoid/associations/meta_data.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Associations #:nodoc: 4 | # This class contains metadata about association proxies. 5 | class MetaData 6 | 7 | attr_reader :association, :options 8 | 9 | delegate :macro, :to => :association 10 | 11 | # Delegate all methods on +Options+ to the options instance. 12 | Associations::Options.public_instance_methods(false).each do |name| 13 | define_method(name) { |*args| @options.send(name) } 14 | end 15 | 16 | # Return true if this meta data is for an embedded association. 17 | # 18 | # Example: 19 | # 20 | # metadata.embedded? 21 | def embedded? 22 | [ EmbedsOne, EmbedsMany ].include?(association) 23 | end 24 | 25 | # Create the new associations MetaData object, which holds the type of 26 | # the association and its options, with convenience methods for getting 27 | # that information. 28 | # 29 | # Options: 30 | # 31 | # association: The association type as a class instance. 32 | # options: The association options 33 | def initialize(association, options) 34 | @association, @options = association, options 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/unit/mongoid/extensions/float/conversions_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Extensions::Float::Conversions do 4 | 5 | describe "#set" do 6 | 7 | context "when the value is a number" do 8 | 9 | it "converts the number to a float" do 10 | Float.set(3.45).should == 3.45 11 | end 12 | 13 | end 14 | 15 | context "when the value is not a number" do 16 | 17 | context "when the value is non numerical" do 18 | 19 | it "returns the string" do 20 | Float.set("foo").should == "foo" 21 | end 22 | 23 | end 24 | 25 | context "when the string is numerical" do 26 | 27 | it "returns the float value for the string" do 28 | Float.set("3.45").should == 3.45 29 | end 30 | 31 | end 32 | 33 | context "when the string is empty" do 34 | 35 | it "returns 0.0" do 36 | Float.set("").should be_nil 37 | end 38 | 39 | end 40 | 41 | context "when the string is nil" do 42 | 43 | it "returns 0.0" do 44 | Float.set(nil).should be_nil 45 | end 46 | 47 | end 48 | 49 | end 50 | 51 | end 52 | 53 | describe "#get" do 54 | 55 | it "returns the float" do 56 | Float.get(3.45).should == 3.45 57 | end 58 | 59 | end 60 | 61 | end 62 | -------------------------------------------------------------------------------- /spec/unit/mongoid/extensions/array/accessors_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Extensions::Array::Accessors do 4 | 5 | describe "#update" do 6 | 7 | context "when the attributes exist" do 8 | 9 | before do 10 | @array = [{ "_id" => 1, "name" => "James T. Kirk" }] 11 | end 12 | 13 | it "overwrites with the new attributes" do 14 | @array.update({ "_id" => 1, "name" => "Spock" }) 15 | @array.first["name"].should == "Spock" 16 | end 17 | 18 | end 19 | 20 | context "when the attributes do not exist" do 21 | 22 | before do 23 | @array = [{ "_id" => 1, "name" => "James T. Kirk" }] 24 | end 25 | 26 | it "appends the new attributes" do 27 | @array.update({ "_id" => 2, "name" => "Scotty" }) 28 | @array.size.should == 2 29 | @array.last["name"].should == "Scotty" 30 | end 31 | 32 | end 33 | 34 | context "when the new attribtues have no id" do 35 | 36 | before do 37 | @array = [{ "_id" => 1, :name => "James T. Kirk" }] 38 | end 39 | 40 | it "appends the new attributes" do 41 | @array.update({"name" => "Scotty" }) 42 | @array.size.should == 2 43 | @array.last["name"].should == "Scotty" 44 | end 45 | 46 | end 47 | 48 | end 49 | 50 | end 51 | -------------------------------------------------------------------------------- /lib/mongoid/collections/slaves.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Collections #:nodoc: 4 | class Slaves 5 | 6 | attr_reader :iterator 7 | 8 | # All read operations should delegate to the slave connections. 9 | # These operations mimic the methods on a Mongo:Collection. 10 | # 11 | # Example: 12 | # 13 | # collection.save({ :name => "Al" }) 14 | Operations::READ.each do |name| 15 | define_method(name) { |*args| collection.send(name, *args) } 16 | end 17 | 18 | # Is the collection of slaves empty or not? 19 | # 20 | # Return: 21 | # 22 | # True is the iterator is not set, false if not. 23 | def empty? 24 | @iterator.nil? 25 | end 26 | 27 | # Create the new database reader. Will create a collection from the 28 | # slave databases and cycle through them on each read. 29 | # 30 | # Example: 31 | # 32 | # Reader.new(slaves, "mongoid_people") 33 | def initialize(slaves, name) 34 | unless slaves.blank? 35 | @iterator = CyclicIterator.new(slaves.collect { |db| db.collection(name) }) 36 | end 37 | end 38 | 39 | protected 40 | def collection 41 | @iterator.next 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/mongoid/named_scope.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module NamedScope 4 | # Creates a named_scope for the +Document+, similar to ActiveRecord's 5 | # named_scopes. +NamedScopes+ are proxied +Criteria+ objects that can be 6 | # chained. 7 | # 8 | # Example: 9 | # 10 | # class Person 11 | # include Mongoid::Document 12 | # field :active, :type => Boolean 13 | # field :count, :type => Integer 14 | # 15 | # named_scope :active, :where => { :active => true } 16 | # named_scope :count_gt_one, :where => { :count.gt => 1 } 17 | # named_scope :at_least_count, lambda { |count| { :where => { :count.gt => count } } } 18 | # end 19 | def named_scope(name, options = {}, &block) 20 | name = name.to_sym 21 | scopes[name] = lambda do |parent, *args| 22 | Scope.new(parent, options.scoped(*args), &block) 23 | end 24 | (class << self; self; end).class_eval <<-EOT 25 | def #{name}(*args) 26 | scopes[:#{name}].call(self, *args) 27 | end 28 | EOT 29 | end 30 | alias :scope :named_scope 31 | 32 | # Return the scopes or default to an empty +Hash+. 33 | def scopes 34 | read_inheritable_attribute(:scopes) || write_inheritable_attribute(:scopes, {}) 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/unit/mongoid/extensions/integer/conversions_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Extensions::Integer::Conversions do 4 | 5 | describe "#set" do 6 | 7 | context "when the value is a number" do 8 | 9 | it "converts the number to an integer" do 10 | Integer.set(3).should == 3 11 | end 12 | 13 | end 14 | 15 | context "when the string is not a number" do 16 | 17 | context "when the string is non numerical" do 18 | 19 | it "returns the string" do 20 | Integer.set("foo").should == "foo" 21 | end 22 | 23 | end 24 | 25 | context "when the string is numerical" do 26 | 27 | it "returns the integer value for the string" do 28 | Integer.set("3").should == 3 29 | end 30 | 31 | end 32 | 33 | context "when the string is empty" do 34 | 35 | it "returns an empty string" do 36 | Integer.set("").should be_nil 37 | end 38 | 39 | end 40 | 41 | context "when the string is nil" do 42 | 43 | it "returns nil" do 44 | Integer.set(nil).should be_nil 45 | end 46 | 47 | end 48 | 49 | end 50 | 51 | end 52 | 53 | describe "#get" do 54 | 55 | it "returns the integer" do 56 | Integer.get(3).should == 3 57 | end 58 | 59 | end 60 | 61 | end 62 | -------------------------------------------------------------------------------- /lib/mongoid/persistence/command.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Persistence #:nodoc: 4 | # Persistence commands extend from this class to get basic functionality on 5 | # initialization. 6 | class Command 7 | attr_reader \ 8 | :collection, 9 | :document, 10 | :klass, 11 | :options, 12 | :selector, 13 | :validate 14 | 15 | # Initialize the persistence +Command+. 16 | # 17 | # Options: 18 | # 19 | # document_or_class: The +Document+ or +Class+ to get the collection. 20 | # validate: Is the document to be validated. 21 | # selector: Optional selector to use in query. 22 | # 23 | # Example: 24 | # 25 | # DeleteAll.new(Person, false, {}) 26 | def initialize(document_or_class, validate = true, selector = {}) 27 | if document_or_class.is_a?(Mongoid::Document) 28 | @document = document_or_class 29 | @collection = @document.embedded? ? @document._root.collection : @document.collection 30 | else 31 | @klass = document_or_class 32 | @collection = @klass.collection 33 | end 34 | @selector, @validate = selector, validate 35 | @options = { :safe => Mongoid.persist_in_safe_mode } 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/unit/mongoid/javascript_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Javascript do 4 | 5 | let(:js) do 6 | Mongoid::Javascript 7 | end 8 | 9 | describe ".aggregate" do 10 | 11 | it "returns the aggregate function" do 12 | js.aggregate.should == "function(obj, prev) { prev.count++; }" 13 | end 14 | end 15 | 16 | describe ".group" do 17 | 18 | it "returns the group function" do 19 | js.group.should == "function(obj, prev) { prev.group.push(obj); }" 20 | end 21 | end 22 | 23 | describe ".max" do 24 | 25 | it "returns the max function" do 26 | js.max.should == "function(obj, prev) { if (prev.max == 'start') { " + 27 | "prev.max = obj.[field]; } if (prev.max < obj.[field]) { prev.max = obj.[field];" + 28 | " } }" 29 | end 30 | end 31 | 32 | describe ".min" do 33 | 34 | it "returns the min function" do 35 | js.min.should == "function(obj, prev) { if (prev.min == 'start') { " + 36 | "prev.min = obj.[field]; } if (prev.min > obj.[field]) { prev.min = obj.[field];" + 37 | " } }" 38 | end 39 | end 40 | 41 | describe ".sum" do 42 | 43 | it "returns the sum function" do 44 | js.sum.should == "function(obj, prev) { if (prev.sum == 'start') { prev.sum = 0; " + 45 | "} prev.sum += obj.[field]; }" 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /spec/unit/mongoid/validations_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Validations do 4 | 5 | before(:all) do 6 | Person.logger = nil 7 | end 8 | 9 | describe ".validates_associated" do 10 | 11 | before do 12 | @class = MixedDrink 13 | end 14 | 15 | it "adds the associated validator" do 16 | @class.expects(:validates_with).with(Mongoid::Validations::AssociatedValidator, { :attributes => [ :name ] }) 17 | @class.validates_associated(:name) 18 | end 19 | 20 | it "is picked up by validates method" do 21 | @class.expects(:validates_with).with(Mongoid::Validations::AssociatedValidator, { :attributes => [ :name ] }) 22 | @class.validates(:name, :associated => true) 23 | end 24 | 25 | end 26 | 27 | describe ".validates_uniqueness_of" do 28 | 29 | before do 30 | @class = MixedDrink 31 | end 32 | 33 | it "adds the uniqueness validator" do 34 | @class.expects(:validates_with).with(Mongoid::Validations::UniquenessValidator, { :attributes => [ :title ] }) 35 | @class.validates_uniqueness_of(:title) 36 | end 37 | 38 | it "is picked up by validates method" do 39 | @class.expects(:validates_with).with(Mongoid::Validations::UniquenessValidator, { :attributes => [ :title ] }) 40 | @class.validates(:title, :uniqueness => true) 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/unit/mongoid/matchers/exists_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Matchers::Exists do 4 | 5 | describe "#matches?" do 6 | 7 | context "when checking for existence" do 8 | 9 | context "when the value exists" do 10 | 11 | let(:matcher) { Mongoid::Matchers::Exists.new("Test") } 12 | 13 | it "returns true" do 14 | matcher.matches?("$exists" => true).should be_true 15 | end 16 | 17 | end 18 | 19 | context "when the value does not exist" do 20 | 21 | let(:matcher) { Mongoid::Matchers::Exists.new(nil) } 22 | 23 | it "returns false" do 24 | matcher.matches?("$exists" => true).should be_false 25 | end 26 | 27 | end 28 | 29 | end 30 | 31 | context "when checking for nonexistence" do 32 | 33 | context "when the value exists" do 34 | 35 | let(:matcher) { Mongoid::Matchers::Exists.new("Test") } 36 | 37 | it "returns false" do 38 | matcher.matches?("$exists" => false).should be_false 39 | end 40 | 41 | end 42 | 43 | context "when the value does not exist" do 44 | 45 | let(:matcher) { Mongoid::Matchers::Exists.new(nil) } 46 | 47 | it "returns true" do 48 | matcher.matches?("$exists" => false).should be_true 49 | end 50 | 51 | end 52 | end 53 | 54 | end 55 | 56 | end 57 | -------------------------------------------------------------------------------- /lib/mongoid/extensions/hash/assimilation.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Extensions #:nodoc: 4 | module Hash #:nodoc: 5 | module Assimilation #:nodoc: 6 | # Introduces a child object into the +Document+ object graph. This will 7 | # set up the relationships between the parent and child and update the 8 | # attributes of the parent +Document+. 9 | # 10 | # Options: 11 | # 12 | # parent: The +Document+ to assimilate into. 13 | # options: The association +Options+ for the child. 14 | # 15 | # Example: 16 | # 17 | # {:first_name => "Hank", :last_name => "Moody"}.assimilate(name, options) 18 | # 19 | # Returns: The child +Document+. 20 | def assimilate(parent, options, type = nil) 21 | klass = self.klass || (type ? type : options.klass) 22 | child = klass.instantiate("_id" => self["_id"]) 23 | self.merge("_type" => klass.name) if klass.hereditary 24 | init(parent, child, options) 25 | end 26 | 27 | protected 28 | 29 | def init(parent, child, options) 30 | child.parentize(parent, options.name) 31 | child.write_attributes(self) 32 | child.identify 33 | child.reset_modifications 34 | child.notify 35 | child 36 | end 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/mongoid/identity.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | class Identity #:nodoc: 4 | # Create the identity for the +Document+. 5 | # 6 | # The id will be set in either in the form of a Mongo 7 | # +ObjectID+ or a composite key set up by defining a key on the document. 8 | # 9 | # The _type will be set to the document's class name. 10 | def create 11 | identify!; type! 12 | end 13 | 14 | # Create the new identity generator - this will be expanded in the future 15 | # to support pk generators. 16 | # 17 | # Options: 18 | # 19 | # document: The document to generate an id for. 20 | def initialize(document) 21 | @document = document 22 | end 23 | 24 | protected 25 | # Return the proper id for the document. 26 | def generate_id 27 | id = BSON::ObjectID.new 28 | Mongoid.use_object_ids ? id : id.to_s 29 | end 30 | 31 | # Set the id for the document. 32 | def identify! 33 | @document.id = compose.join(" ").identify if @document.primary_key 34 | @document.id = generate_id if @document.id.blank? 35 | end 36 | 37 | # Set the _type field on the @document.ment. 38 | def type! 39 | @document._type = @document.class.name if @document.hereditary? 40 | end 41 | 42 | # Generates the composite key for a @document.ment. 43 | def compose 44 | @document.primary_key.collect { |key| @document.attributes[key] }.reject { |val| val.nil? } 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/mongoid/extensions/hash/accessors.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Extensions #:nodoc: 4 | module Hash #:nodoc: 5 | module Accessors #:nodoc: 6 | 7 | # Remove a set of attributes from a hash. If the attributes are 8 | # contained in an array it will remove it from the array, otherwise it 9 | # will delete the child attribute completely. 10 | def remove(key, attrs) 11 | elements = self[key] 12 | if elements 13 | key.singular? ? self[key] = nil : elements.delete(attrs) 14 | end 15 | end 16 | 17 | # Inserts new attributes into the hash. If the elements are present in 18 | # the hash it will update them, otherwise it will add the new 19 | # attributes into the hash as either a child hash or child array of 20 | # hashes. 21 | def insert(key, attrs) 22 | elements = self[key] 23 | if elements 24 | if elements.kind_of?(::Array) 25 | elements.merge!(attrs) 26 | else 27 | self[key] = attrs.reverse_merge!(elements) 28 | end 29 | else 30 | self[key] = key.singular? ? attrs : [attrs] 31 | end 32 | end 33 | 34 | # If a _type key exists in the hash, return the class for the value. 35 | def klass 36 | class_name = self["_type"] 37 | class_name ? class_name.constantize : nil 38 | end 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/rails/generators/mongoid_generator.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require "rails/generators/named_base" 3 | require "rails/generators/active_model" 4 | 5 | module Mongoid #:nodoc: 6 | module Generators #:nodoc: 7 | class Base < ::Rails::Generators::NamedBase #:nodoc: 8 | 9 | def self.source_root 10 | @_mongoid_source_root ||= 11 | File.expand_path("../#{base_name}/#{generator_name}/templates", __FILE__) 12 | end 13 | end 14 | 15 | class ActiveModel < ::Rails::Generators::ActiveModel #:nodoc: 16 | def self.all(klass) 17 | "#{klass}.all" 18 | end 19 | 20 | def self.find(klass, params=nil) 21 | "#{klass}.find(#{params})" 22 | end 23 | 24 | def self.build(klass, params=nil) 25 | if params 26 | "#{klass}.new(#{params})" 27 | else 28 | "#{klass}.new" 29 | end 30 | end 31 | 32 | def save 33 | "#{name}.save" 34 | end 35 | 36 | def update_attributes(params=nil) 37 | "#{name}.update_attributes(#{params})" 38 | end 39 | 40 | def errors 41 | "#{name}.errors" 42 | end 43 | 44 | def destroy 45 | "#{name}.destroy" 46 | end 47 | end 48 | end 49 | end 50 | 51 | module Rails 52 | module Generators 53 | class GeneratedAttribute #:nodoc: 54 | def type_class 55 | return "Time" if type.to_s == "datetime" 56 | return "String" if type.to_s == "text" 57 | return type.to_s.camelcase 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /spec/models/inheritance.rb: -------------------------------------------------------------------------------- 1 | class Canvas 2 | include Mongoid::Document 3 | field :name 4 | embeds_many :shapes 5 | embeds_one :writer 6 | embeds_one :palette 7 | 8 | def render 9 | shapes.each { |shape| render } 10 | end 11 | end 12 | 13 | class Browser < Canvas 14 | field :version, :type => Integer 15 | def render; end 16 | end 17 | 18 | class Firefox < Browser 19 | field :user_agent 20 | def render; end 21 | end 22 | 23 | class Shape 24 | include Mongoid::Document 25 | field :x, :type => Integer, :default => 0 26 | field :y, :type => Integer, :default => 0 27 | 28 | embedded_in :canvas, :inverse_of => :shapes 29 | 30 | def render; end 31 | end 32 | 33 | class Square < Shape 34 | field :width, :type => Integer, :default => 0 35 | field :height, :type => Integer, :default => 0 36 | end 37 | 38 | class Circle < Shape 39 | field :radius, :type => Integer, :default => 0 40 | end 41 | 42 | class Writer 43 | include Mongoid::Document 44 | field :speed, :type => Integer, :default => 0 45 | 46 | embedded_in :canvas, :inverse_of => :writer 47 | 48 | def write; end 49 | end 50 | 51 | class HtmlWriter < Writer 52 | def write; end 53 | end 54 | 55 | class PdfWriter < Writer 56 | def write; end 57 | end 58 | 59 | class Palette 60 | include Mongoid::Document 61 | embedded_in :canvas, :inverse_of => :palette 62 | embeds_many :tools 63 | end 64 | 65 | class Tool 66 | include Mongoid::Document 67 | embedded_in :palette, :inverse_of => :tools 68 | end 69 | 70 | class Pencil < Tool; end 71 | 72 | class Eraser < Tool; end 73 | -------------------------------------------------------------------------------- /lib/mongoid/contexts/paging.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Contexts #:nodoc: 4 | module Paging 5 | # Paginates the documents. 6 | # 7 | # Example: 8 | # 9 | # context.paginate(:page => 6, :per_page => 25) 10 | # 11 | # Returns: 12 | # 13 | # A collection of documents paginated. 14 | # All previous limit and skip call will be ignored. 15 | def paginate(pager_options={}) 16 | if pager_options[:per_page] 17 | options[:limit] = pager_options[:per_page].to_i 18 | if pager_options[:page] 19 | options[:skip] = (pager_options[:page].to_i - 1) * pager_options[:per_page].to_i 20 | end 21 | end 22 | 23 | @collection ||= execute(true) 24 | WillPaginate::Collection.create(page, per_page, count) do |pager| 25 | pager.replace(@collection.to_a) 26 | end 27 | end 28 | 29 | # Either returns the page option and removes it from the options, or 30 | # returns a default value of 1. 31 | # 32 | # Returns: 33 | # 34 | # An +Integer+ page number. 35 | def page 36 | skips, limits = options[:skip], options[:limit] 37 | (skips && limits) ? (skips + limits) / limits : 1 38 | end 39 | 40 | # Get the number of results per page or the default of 20. 41 | # 42 | # Returns: 43 | # 44 | # The +Integer+ number of documents in each page. 45 | def per_page 46 | (options[:limit] || 20).to_i 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/mongoid/persistence/insert.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Persistence #:nodoc: 4 | # Insert is a persistence command responsible for taking a document that 5 | # has not been saved to the database and saving it. 6 | # 7 | # The underlying query resembles the following MongoDB query: 8 | # 9 | # collection.insert( 10 | # { "_id" : 1, "field" : "value" }, 11 | # false 12 | # ); 13 | class Insert < Command 14 | # Insert the new document in the database. This delegates to the standard 15 | # MongoDB collection's insert command. 16 | # 17 | # Example: 18 | # 19 | # Insert.persist 20 | # 21 | # Returns: 22 | # 23 | # The +Document+, whether the insert succeeded or not. 24 | def persist 25 | return @document if @validate && @document.invalid?(:create) 26 | @document.run_callbacks(:create) do 27 | @document.run_callbacks(:save) do 28 | if insert 29 | @document.new_record = false 30 | @document._children.each { |child| child.new_record = false } 31 | @document.move_changes 32 | end 33 | end 34 | end; @document 35 | end 36 | 37 | protected 38 | # Insert the document into the database. 39 | def insert 40 | if @document.embedded? 41 | Persistence::InsertEmbedded.new(@document, @validate).persist 42 | else 43 | @collection.insert(@document.raw_attributes, @options) 44 | end 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/mongoid/validations.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require "mongoid/validations/associated" 3 | require "mongoid/validations/uniqueness" 4 | 5 | I18n.load_path << File.join( 6 | File.dirname(__FILE__), "validations", "locale", "en.yml" 7 | ) 8 | 9 | module Mongoid #:nodoc: 10 | # This module provides additional validations that ActiveModel does not 11 | # provide: validates_associated and validates_uniqueness_of 12 | module Validations 13 | extend ActiveSupport::Concern 14 | included do 15 | include ActiveModel::Validations 16 | end 17 | 18 | module ClassMethods #:nodoc: 19 | # Validates whether or not an association is valid or not. Will correctly 20 | # handle has one and has many associations. 21 | # 22 | # Example: 23 | # 24 | # class Person 25 | # include Mongoid::Document 26 | # embeds_one :name 27 | # embeds_many :addresses 28 | # 29 | # validates_associated :name, :addresses 30 | # end 31 | def validates_associated(*args) 32 | validates_with(AssociatedValidator, _merge_attributes(args)) 33 | end 34 | 35 | # Validates whether or not a field is unique against the documents in the 36 | # database. 37 | # 38 | # Example: 39 | # 40 | # class Person 41 | # include Mongoid::Document 42 | # field :title 43 | # 44 | # validates_uniqueness_of :title 45 | # end 46 | def validates_uniqueness_of(*args) 47 | validates_with(UniquenessValidator, _merge_attributes(args)) 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/mongoid/persistence/insert_embedded.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Persistence #:nodoc: 4 | # Insert is a persistence command responsible for taking a document that 5 | # has not been saved to the database and saving it. This specific class 6 | # handles the case when the document is embedded in another. 7 | # 8 | # The underlying query resembles the following MongoDB query: 9 | # 10 | # collection.insert( 11 | # { "_id" : 1, "field" : "value" }, 12 | # false 13 | # ); 14 | class InsertEmbedded < Command 15 | # Insert the new document in the database. If the document's parent is a 16 | # new record, we will call save on the parent, otherwise we will $push 17 | # the document onto the parent. 18 | # 19 | # Example: 20 | # 21 | # Insert.persist 22 | # 23 | # Returns: 24 | # 25 | # The +Document+, whether the insert succeeded or not. 26 | def persist 27 | return @document if @validate && @document.invalid?(:create) 28 | parent = @document._parent 29 | @document.run_callbacks(:create) do 30 | @document.run_callbacks(:save) do 31 | if parent.new_record? 32 | parent.insert 33 | else 34 | update = { @document._inserter => { @document._position => @document.raw_attributes } } 35 | @collection.update(parent._selector, update, @options.merge(:multi => false)) 36 | @document.new_record = false 37 | end 38 | end 39 | end 40 | @document 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /spec/integration/mongoid/named_scope_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::NamedScope do 4 | 5 | describe ".scope" do 6 | 7 | before(:all) do 8 | Person.class_eval do 9 | scope :doctors, {:where => {:title => 'Dr.'} } 10 | scope :old, criteria.where(:age.gt => 50) 11 | scope :alki, where(:blood_alcohol_content.gt => 0.3).order_by(:blood_alcohol_content.asc) 12 | end 13 | end 14 | 15 | before do 16 | @document = Person.create(:title => "Dr.", :age => 65, :terms => true, :ssn => "123-22-8346") 17 | end 18 | 19 | after do 20 | Person.delete_all 21 | end 22 | 23 | context "accessing a single named scope" do 24 | 25 | it "returns the document" do 26 | Person.doctors.first.should == @document 27 | end 28 | end 29 | 30 | context "chaining named scopes" do 31 | 32 | it "returns the document" do 33 | Person.old.doctors.first.should == @document 34 | end 35 | end 36 | 37 | context "mixing named scopes and class methods" do 38 | 39 | it "returns the document" do 40 | Person.accepted.old.doctors.first.should == @document 41 | end 42 | end 43 | 44 | context "using order_by in a named scope" do 45 | 46 | before do 47 | Person.create(:blood_alcohol_content => 0.5, :ssn => "121-22-8346") 48 | Person.create(:blood_alcohol_content => 0.4, :ssn => "124-22-8346") 49 | Person.create(:blood_alcohol_content => 0.7, :ssn => "125-22-8346") 50 | end 51 | 52 | it "sorts the results" do 53 | docs = Person.alki 54 | docs.first.blood_alcohol_content.should == 0.4 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /spec/unit/mongoid/hierarchy_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Hierarchy do 4 | 5 | describe "#_children" do 6 | 7 | let(:person) do 8 | Person.new(:title => "King") 9 | end 10 | 11 | context "with one level of embedding" do 12 | 13 | let(:name) do 14 | Name.new(:first_name => "Titus") 15 | end 16 | 17 | let(:address) do 18 | Address.new(:street => "Queen St") 19 | end 20 | 21 | before do 22 | person.name = name 23 | person.addresses << address 24 | end 25 | 26 | it "includes embeds_one documents" do 27 | person._children.should include(name) 28 | end 29 | 30 | it "includes embeds_many documents" do 31 | person._children.should include(address) 32 | end 33 | end 34 | 35 | context "with multiple levels of embedding" do 36 | 37 | let(:name) do 38 | Name.new(:first_name => "Titus") 39 | end 40 | 41 | let(:address) do 42 | Address.new(:street => "Queen St") 43 | end 44 | 45 | let(:location) do 46 | Location.new(:name => "Work") 47 | end 48 | 49 | before do 50 | person.name = name 51 | address.locations << location 52 | person.addresses << address 53 | end 54 | 55 | it "includes embeds_one documents" do 56 | person._children.should include(name) 57 | end 58 | 59 | it "includes embeds_many documents" do 60 | person._children.should include(address) 61 | end 62 | 63 | it "includes embedded documents multiple levels deep" do 64 | person._children.should include(location) 65 | end 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = Overview 2 | 3 | == About Mongoid 4 | 5 | Mongoid is an ODM (Object-Document-Mapper) framework for MongoDB in Ruby. 6 | 7 | == Project Tracking 8 | 9 | * {Mongoid Google Group}[http://groups.google.com/group/mongoid] 10 | * {Mongoid Website and Documentation}[http://mongoid.org] 11 | 12 | == Compatibility 13 | 14 | Mongoid is developed against Ruby 1.8.7, 1.9.1, 1.9.2 15 | 16 | = Documentation 17 | 18 | Please see the new Mongoid website for up-to-date documentation: 19 | {mongoid.org}[http://mongoid.org] 20 | 21 | = License 22 | 23 | Copyright (c) 2009, 2010 Durran Jordan 24 | 25 | Permission is hereby granted, free of charge, to any person obtaining 26 | a copy of this software and associated documentation files (the 27 | "Software"), to deal in the Software without restriction, including 28 | without limitation the rights to use, copy, modify, merge, publish, 29 | distribute, sublicense, and/or sell copies of the Software, and to 30 | permit persons to whom the Software is furnished to do so, subject to 31 | the following conditions: 32 | 33 | The above copyright notice and this permission notice shall be 34 | included in all copies or substantial portions of the Software. 35 | 36 | 37 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 38 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 39 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 40 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 41 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 42 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 43 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 44 | 45 | = Credits 46 | 47 | Durran Jordan: durran at gmail dot com 48 | -------------------------------------------------------------------------------- /lib/mongoid/atomicity.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Atomicity #:nodoc: 4 | extend ActiveSupport::Concern 5 | 6 | # Get all the atomic updates that need to happen for the current 7 | # +Document+. This includes all changes that need to happen in the 8 | # entire hierarchy that exists below where the save call was made. 9 | # 10 | # Example: 11 | # 12 | # person.save # Saves entire tree 13 | # 14 | # Returns: 15 | # 16 | # A +Hash+ of all atomic updates that need to occur. 17 | def _updates 18 | processed = {} 19 | 20 | _children.inject({ "$set" => _sets, "$pushAll" => {}, :other => {} }) do |updates, child| 21 | changes = child._sets 22 | updates["$set"].update(changes) 23 | processed[child.class] = true unless changes.empty? 24 | 25 | target = processed.has_key?(child.class) ? :other : "$pushAll" 26 | 27 | child._pushes.each do |attr, val| 28 | if updates[target].has_key?(attr) 29 | updates[target][attr] << val 30 | else 31 | updates[target].update({attr => [val]}) 32 | end 33 | end 34 | updates 35 | end.delete_if do |key, value| 36 | value.empty? 37 | end 38 | end 39 | 40 | protected 41 | # Get all the push attributes that need to occur. 42 | def _pushes 43 | (new_record? && embedded_many? && !_parent.new_record?) ? { _path => raw_attributes } : {} 44 | end 45 | 46 | # Get all the attributes that need to be set. 47 | def _sets 48 | if changed? && !new_record? 49 | setters 50 | else 51 | embedded_one? && new_record? ? { _path => raw_attributes } : {} 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/mongoid/railties/database.rake: -------------------------------------------------------------------------------- 1 | namespace :db do 2 | 3 | if not Rake::Task.task_defined?("db:drop") 4 | desc 'Drops all the collections for the database for the current Rails.env' 5 | task :drop => :environment do 6 | Mongoid.master.collections.each{|col| col.drop unless col.name == 'system.users' || col.name == "system.indexes" } 7 | end 8 | end 9 | 10 | if not Rake::Task.task_defined?("db:seed") 11 | # if another ORM has defined db:seed, don't run it twice. 12 | desc 'Load the seed data from db/seeds.rb' 13 | task :seed => :environment do 14 | seed_file = File.join(Rails.root, 'db', 'seeds.rb') 15 | load(seed_file) if File.exist?(seed_file) 16 | end 17 | end 18 | 19 | if not Rake::Task.task_defined?("db:setup") 20 | desc 'Create the database, and initialize with the seed data' 21 | task :setup => [ 'db:create', 'db:seed' ] 22 | end 23 | 24 | if not Rake::Task.task_defined?("db:reseed") 25 | desc 'Delete data and seed' 26 | task :reseed => [ 'db:drop', 'db:seed' ] 27 | end 28 | 29 | if not Rake::Task.task_defined?("db:create") 30 | task :create => :environment do 31 | # noop 32 | end 33 | end 34 | 35 | if not Rake::Task.task_defined?("db:migrate") 36 | task :migrate => :environment do 37 | # noop 38 | end 39 | end 40 | 41 | if not Rake::Task.task_defined?("db:schema:load") 42 | namespace :schema do 43 | task :load do 44 | # noop 45 | end 46 | end 47 | end 48 | 49 | if not Rake::Task.task_defined?("db:test:prepare") 50 | namespace :test do 51 | task :prepare do 52 | # noop 53 | end 54 | end 55 | end 56 | 57 | ######## 58 | # TODO: lots more useful db tasks can be added here. stuff like copyDatabase, etc 59 | ######## 60 | end 61 | -------------------------------------------------------------------------------- /spec/unit/mongoid/collections/cyclic_iterator_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Collections::CyclicIterator do 4 | 5 | before do 6 | @first = stub 7 | @second = stub 8 | @third = stub 9 | @fourth = stub 10 | end 11 | 12 | let(:array) do 13 | [ @first, @second, @third, @fourth ] 14 | end 15 | 16 | describe "#initialize" do 17 | 18 | let(:iterator) do 19 | Mongoid::Collections::CyclicIterator.new(array) 20 | end 21 | 22 | it "defaults the counter to -1" do 23 | iterator.counter.should == -1 24 | end 25 | end 26 | 27 | describe "#next" do 28 | 29 | context "when the iterator has just been created" do 30 | 31 | let(:iterator) do 32 | Mongoid::Collections::CyclicIterator.new(array) 33 | end 34 | 35 | it "returns the first element" do 36 | iterator.next.should == @first 37 | end 38 | end 39 | 40 | context "when the iterator is in the middle" do 41 | 42 | let(:iterator) do 43 | Mongoid::Collections::CyclicIterator.new(array) 44 | end 45 | 46 | before do 47 | 2.times { iterator.next } 48 | end 49 | 50 | it "returns the next element given the index" do 51 | iterator.next.should == @third 52 | end 53 | end 54 | 55 | context "when the iterator is on the last element" do 56 | 57 | let(:iterator) do 58 | Mongoid::Collections::CyclicIterator.new(array) 59 | end 60 | 61 | before do 62 | 4.times { iterator.next } 63 | end 64 | 65 | it "returns the first element" do 66 | iterator.next.should == @first 67 | end 68 | 69 | it "resets the counter" do 70 | iterator.next 71 | iterator.counter.should == 0 72 | end 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /spec/unit/mongoid/memoization_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Memoization do 4 | 5 | let(:memo) { "Memo" } 6 | 7 | before do 8 | @person = Person.new 9 | end 10 | 11 | describe "#memoized" do 12 | 13 | context "when variable has been defined" do 14 | 15 | before do 16 | @person.instance_variable_set("@memo", memo) 17 | end 18 | 19 | it "returns the memoized value" do 20 | @person.memoized(:memo) { nil }.should == memo 21 | end 22 | 23 | end 24 | 25 | context "when variable has not been defined" do 26 | 27 | it "returns the new value" do 28 | @person.memoized(:memo) { memo }.should == memo 29 | end 30 | 31 | it "memoizes the new value" do 32 | @person.memoized(:memo) { memo } 33 | @person.instance_variable_get("@memo").should == memo 34 | end 35 | 36 | end 37 | 38 | end 39 | 40 | describe "#reset" do 41 | 42 | context "when variable has been defined" do 43 | 44 | before do 45 | @person.instance_variable_set("@memo", memo) 46 | end 47 | 48 | it "removes the memoized value" do 49 | @person.reset(:memo) { nil } 50 | @person.instance_variable_defined?("@memo").should be_false 51 | end 52 | 53 | it "returns the new value" do 54 | @person.reset(:memo) { memo }.should == memo 55 | end 56 | 57 | end 58 | 59 | context "when variable has not been defined" do 60 | 61 | it "memoizes the new value" do 62 | @person.reset(:memo) { memo } 63 | @person.instance_variable_get("@memo").should == memo 64 | end 65 | 66 | it "returns the value" do 67 | @person.reset(:memo) { memo }.should == memo 68 | end 69 | 70 | end 71 | 72 | 73 | end 74 | 75 | end 76 | -------------------------------------------------------------------------------- /lib/mongoid/extras.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Extras #:nodoc: 4 | extend ActiveSupport::Concern 5 | included do 6 | class_inheritable_accessor :cached, :enslaved 7 | self.cached = false 8 | self.enslaved = false 9 | delegate :cached?, :enslaved?, :to => "self.class" 10 | end 11 | 12 | module ClassMethods #:nodoc 13 | # Sets caching on for this class. This class level configuration will 14 | # default all queries to cache the results of the first iteration over 15 | # the cursor into an internal array. This should only be used for queries 16 | # that return a small number of results or have small documents, as after 17 | # the first iteration the entire results will be stored in memory. 18 | # 19 | # Example: 20 | # 21 | # class Person 22 | # include Mongoid::Document 23 | # cache 24 | # end 25 | def cache 26 | self.cached = true 27 | end 28 | 29 | # Determines if the class is cached or not. 30 | # 31 | # Returns: 32 | # 33 | # True if cached, false if not. 34 | def cached? 35 | !!self.cached 36 | end 37 | 38 | # Set whether or not this documents read operations should delegate to 39 | # the slave database by default. 40 | # 41 | # Example: 42 | # 43 | # class Person 44 | # include Mongoid::Document 45 | # enslave 46 | # end 47 | def enslave 48 | self.enslaved = true 49 | end 50 | 51 | # Determines if the class is enslaved or not. 52 | # 53 | # Returns: 54 | # 55 | # True if enslaved, false if not. 56 | def enslaved? 57 | !!self.enslaved 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /spec/integration/mongoid/attributes_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Attributes do 4 | 5 | context "when persisting nil attributes" do 6 | 7 | before do 8 | @person = Person.create(:score => nil, :ssn => "555-66-7777") 9 | end 10 | 11 | after do 12 | Person.delete_all 13 | end 14 | 15 | it "the field should exist with a nil value" do 16 | from_db = Person.find(@person.id) 17 | from_db.attributes.has_key?(:score).should be_true 18 | end 19 | 20 | end 21 | 22 | context "when persisting nested attributes" do 23 | 24 | before do 25 | @survey = Survey.new 26 | 3.times do 27 | @question = @survey.questions.build 28 | 4.times { @question.answers.build } 29 | end 30 | end 31 | 32 | end 33 | 34 | context "when persisting nested with accepts_nested_attributes_for" do 35 | 36 | before do 37 | @survey = Survey.new 38 | @survey.questions.build(:content => 'Do you like cheesecake ?') 39 | @survey.questions.build(:content => 'Do you like cuppcake ?') 40 | @survey.questions.build(:content => 'Do you like ace cream ?') 41 | @survey.save 42 | @attributes = { 43 | "0" => { :content => "lorem", "_destroy" => "true" }, 44 | "1" => { :content => "lorem", "_destroy" => "true" }, 45 | "2" => { :content => "Do you like ice cream ?" }, 46 | "new_record" => { :content => "Do you carrot cake ?" } 47 | } 48 | end 49 | 50 | it "adds/updates/removes embedded documents" do 51 | @survey.update_attributes(:questions_attributes => @attributes) 52 | @survey.reload 53 | @survey.questions.size.should == 2 54 | @survey.questions.first.content.should == "Do you like ice cream ?" 55 | end 56 | 57 | end 58 | 59 | end 60 | -------------------------------------------------------------------------------- /lib/mongoid/persistence/remove_embedded.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Persistence #:nodoc: 4 | # Remove is a persistence command responsible for deleting a document from 5 | # the database. 6 | # 7 | # The underlying query resembles the following MongoDB query: 8 | # 9 | # collection.remove( 10 | # { "_id" : 1 }, 11 | # false 12 | # ); 13 | class RemoveEmbedded < Command 14 | # Insert the new document in the database. If the document's parent is a 15 | # new record, we will call save on the parent, otherwise we will $push 16 | # the document onto the parent. 17 | # 18 | # Remove the document from the database. If the parent is a new record, 19 | # it will get removed in Ruby only. If the parent is not a new record 20 | # then either an $unset or $set will occur, depending if it's an 21 | # embeds_one or embeds_many. 22 | # 23 | # Example: 24 | # 25 | # RemoveEmbedded.persist 26 | # 27 | # Returns: 28 | # 29 | # +true+ or +false+, depending on if the removal passed. 30 | def persist 31 | parent = @document._parent 32 | parent.remove(@document) 33 | unless parent.new_record? 34 | update = { @document._remover => removal_selector } 35 | @collection.update(parent._selector, update, @options.merge(:multi => false)) 36 | end; true 37 | end 38 | 39 | protected 40 | # Get the value to pass to the removal modifier. 41 | def setter 42 | @document._index ? @document.id : true 43 | end 44 | 45 | def removal_selector 46 | @document._index ? { @document._pull => { "_id" => @document.id } } : { @document._path => setter } 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/mongoid/paths.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Paths #:nodoc: 4 | extend ActiveSupport::Concern 5 | included do 6 | cattr_accessor :__path 7 | attr_accessor :_index 8 | end 9 | 10 | # Get the insertion modifier for the document. Will be nil on root 11 | # documents, $set on embeds_one, $push on embeds_many. 12 | # 13 | # Example: 14 | # 15 | # name.inserter 16 | def _inserter 17 | embedded? ? (embedded_many? ? "$push" : "$set") : nil 18 | end 19 | 20 | # Return the path to this +Document+ in JSON notation, used for atomic 21 | # updates via $set in MongoDB. 22 | # 23 | # Example: 24 | # 25 | # address.path # returns "addresses" 26 | def _path 27 | _position.sub!(/\.\d+$/, '') || _position 28 | end 29 | alias :_pull :_path 30 | 31 | # Returns the positional operator of this document for modification. 32 | # 33 | # Example: 34 | # 35 | # address.position 36 | def _position 37 | locator = _index ? (new_record? ? "" : ".#{_index}") : "" 38 | embedded? ? "#{_parent._position}#{"." unless _parent._position.blank?}#{@association_name}#{locator}" : "" 39 | end 40 | 41 | # Get the removal modifier for the document. Will be nil on root 42 | # documents, $unset on embeds_one, $set on embeds_many. 43 | # 44 | # Example: 45 | # 46 | # name.remover 47 | def _remover 48 | embedded? ? (_index ? "$pull" : "$unset") : nil 49 | end 50 | 51 | # Return the selector for this document to be matched exactly for use 52 | # with MongoDB's $ operator. 53 | # 54 | # Example: 55 | # 56 | # address.selector 57 | def _selector 58 | embedded? ? _parent._selector.merge("#{_path}._id" => id) : { "_id" => id } 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/mongoid/validations/uniqueness.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Validations #:nodoc: 4 | # Validates whether or not a field is unique against the documents in the 5 | # database. 6 | # 7 | # Example: 8 | # 9 | # class Person 10 | # include Mongoid::Document 11 | # field :title 12 | # 13 | # validates_uniqueness_of :title 14 | # end 15 | class UniquenessValidator < ActiveModel::EachValidator 16 | # Unfortunately, we have to tie Uniqueness validators to a class. 17 | def setup(klass) 18 | @klass = klass 19 | end 20 | 21 | def validate_each(document, attribute, value) 22 | if document.embedded? 23 | return if document._parent.nil? 24 | 25 | criteria = document._parent.send(document.association_name) 26 | 27 | # If the parent document embeds_one, no need to validate uniqueness 28 | return if criteria.is_a?(Mongoid::Document) 29 | 30 | criteria = criteria.where(attribute => value, :_id => {'$ne' => document._id}) 31 | else 32 | criteria = @klass.where(attribute => value) 33 | 34 | unless document.new_record? 35 | criteria = criteria.where(:_id => {'$ne' => document._id}) 36 | end 37 | end 38 | 39 | Array.wrap(options[:scope]).each do |item| 40 | criteria = criteria.where(item => document.attributes[item]) 41 | end 42 | 43 | if criteria.exists? 44 | document.errors.add(attribute, :taken, :default => options[:message], :value => value) 45 | end 46 | end 47 | 48 | protected 49 | def key_changed?(document) 50 | (document.primary_key || {}).each do |key| 51 | return true if document.send("#{key}_changed?") 52 | end; false 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /spec/unit/mongoid/collections/slaves_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Collections::Master do 4 | 5 | let(:collection) do 6 | stub.quacks_like(Mongo::Collection.allocate) 7 | end 8 | 9 | let(:db) do 10 | stub.quacks_like(Mongo::DB.allocate) 11 | end 12 | 13 | describe "#empty?" do 14 | 15 | context "when the slaves exist" do 16 | 17 | let(:slave) do 18 | Mongoid::Collections::Slaves.new([ db ], "people") 19 | end 20 | 21 | before do 22 | db.expects(:collection).with("people").returns(collection) 23 | end 24 | 25 | it "returns false" do 26 | slave.should_not be_empty 27 | end 28 | end 29 | 30 | context "when the slaves do not exist" do 31 | 32 | let(:slave) do 33 | Mongoid::Collections::Slaves.new([], "people") 34 | end 35 | 36 | it "returns true" do 37 | slave.should be_empty 38 | end 39 | end 40 | 41 | context "when the slaves are nil" do 42 | 43 | let(:slave) do 44 | Mongoid::Collections::Slaves.new(nil, "people") 45 | end 46 | 47 | it "returns true" do 48 | slave.should be_empty 49 | end 50 | end 51 | end 52 | 53 | context "Mongo::Collection operations" do 54 | 55 | let(:slave) do 56 | Mongoid::Collections::Slaves.new([ db ], "people") 57 | end 58 | 59 | before do 60 | db.expects(:collection).with("people").returns(collection) 61 | end 62 | 63 | Mongoid::Collections::Operations::READ.each do |name| 64 | 65 | it "defines #{name}" do 66 | slave.should respond_to(name) 67 | end 68 | 69 | describe "##{name}" do 70 | 71 | before do 72 | collection.expects(name) 73 | end 74 | 75 | it "delegates to the collection" do 76 | slave.send(name) 77 | end 78 | end 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /spec/unit/mongoid/state_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::State do 4 | 5 | describe "#new_record?" do 6 | 7 | context "when calling new on the document" do 8 | 9 | before do 10 | @person = Person.new("_id" => "1") 11 | end 12 | 13 | it "returns true" do 14 | @person.new_record?.should == true 15 | end 16 | end 17 | 18 | context "when the object has been saved" do 19 | 20 | before do 21 | @person = Person.instantiate("_id" => "1") 22 | end 23 | 24 | it "returns false" do 25 | @person.new_record?.should be_false 26 | end 27 | 28 | end 29 | 30 | context "when the object has not been saved" do 31 | 32 | before do 33 | @person = Person.new 34 | end 35 | 36 | it "returns true" do 37 | @person.new_record?.should be_true 38 | end 39 | 40 | end 41 | 42 | end 43 | 44 | describe "#persisted?" do 45 | 46 | before do 47 | @person = Person.new 48 | end 49 | 50 | it "delegates to new_record?" do 51 | @person.persisted?.should be_false 52 | end 53 | end 54 | 55 | describe "destroyed?" do 56 | 57 | before do 58 | @person = Person.new 59 | end 60 | 61 | context "when destroyed is true" do 62 | 63 | before do 64 | @person.destroyed = true 65 | end 66 | 67 | it "returns true" do 68 | @person.destroyed?.should be_true 69 | end 70 | end 71 | 72 | context "when destroyed is false" do 73 | 74 | before do 75 | @person.destroyed = false 76 | end 77 | 78 | it "returns true" do 79 | @person.destroyed?.should be_false 80 | end 81 | end 82 | 83 | context "when destroyed is nil" do 84 | 85 | before do 86 | @person.destroyed = nil 87 | end 88 | 89 | it "returns false" do 90 | @person.destroyed?.should be_false 91 | end 92 | end 93 | end 94 | end 95 | -------------------------------------------------------------------------------- /lib/mongoid/associations/options.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Associations #:nodoc: 4 | class Options #:nodoc: 5 | 6 | # Create the new +Options+ object, which provides convenience methods for 7 | # accessing values out of an options +Hash+. 8 | def initialize(attributes = {}) 9 | @attributes = attributes 10 | end 11 | 12 | # Returns the extension if it exists, nil if not. 13 | def extension 14 | @attributes[:extend] 15 | end 16 | 17 | # Returns true is the options have extensions. 18 | def extension? 19 | !extension.nil? 20 | end 21 | 22 | # Return the foreign key if it exists, otherwise inflect it from the 23 | # associated class name. 24 | def foreign_key 25 | key = @attributes[:foreign_key] || klass.name.to_s.foreign_key 26 | key.to_s 27 | end 28 | 29 | # Returns the name of the inverse_of association 30 | def inverse_of 31 | @attributes[:inverse_of] 32 | end 33 | 34 | # Return a +Class+ for the options. See #class_name 35 | def klass 36 | class_name.constantize 37 | end 38 | 39 | # Return a +String+ representing the associated class_name. If a class_name 40 | # was provided, then the constantized class_name will be returned. If not, 41 | # a constant based on the association name will be returned. 42 | def class_name 43 | @attributes[:class_name] || name.to_s.classify 44 | end 45 | 46 | # Returns the association name of the options. 47 | def name 48 | @attributes[:name].to_s 49 | end 50 | 51 | # Returns whether or not this association is polymorphic. 52 | def polymorphic 53 | @attributes[:polymorphic] == true 54 | end 55 | 56 | # Used with references_many to save as array of ids. 57 | def stored_as 58 | @attributes[:stored_as] 59 | end 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /spec/unit/mongoid/cursor_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Cursor do 4 | 5 | let(:collection) do 6 | stub.quacks_like(Mongoid::Collection.allocate) 7 | end 8 | 9 | let(:mongo_cursor) do 10 | Mongo::Cursor.allocate.tap do |cursor| 11 | cursor.stubs(:inspect => "") 12 | end 13 | end 14 | 15 | let(:proxy) do 16 | stub.quacks_like(mongo_cursor) 17 | end 18 | 19 | let(:cursor) do 20 | Mongoid::Cursor.new(Person, collection, proxy) 21 | end 22 | 23 | (Mongoid::Cursor::OPERATIONS - [ :timeout ]).each do |name| 24 | 25 | describe "##{name}" do 26 | 27 | before do 28 | proxy.expects(name) 29 | end 30 | 31 | it "delegates to the proxy" do 32 | cursor.send(name) 33 | end 34 | end 35 | end 36 | 37 | describe "#collection" do 38 | 39 | it "returns the mongoid collection" do 40 | cursor.collection.should == collection 41 | end 42 | end 43 | 44 | describe "#each" do 45 | 46 | before do 47 | proxy.expects(:each).yields({ "_type" => "Person" }) 48 | end 49 | 50 | it "yields to the next document" do 51 | cursor.each do |doc| 52 | doc.attributes.except(:_id).should == Person.new.attributes.except(:_id) 53 | end 54 | end 55 | end 56 | 57 | describe "#next_document" do 58 | 59 | before do 60 | proxy.expects(:next_document).returns({ "_type" => "Person" }) 61 | end 62 | 63 | it "returns the next document from the proxied cursor" do 64 | doc = cursor.next_document 65 | doc.attributes.except(:_id).should == Person.new.attributes.except(:_id) 66 | end 67 | end 68 | 69 | describe "#to_a" do 70 | 71 | before do 72 | proxy.expects(:to_a).returns([{ "_type" => "Person" }]) 73 | end 74 | 75 | it "converts the proxy cursor to an array of documents" do 76 | docs = cursor.to_a 77 | docs[0].attributes.except(:_id).should == Person.new.attributes.except(:_id) 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /spec/unit/mongoid/extensions/hash/assimilation_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Extensions::Hash::Assimilation do 4 | 5 | describe "#assimilate" do 6 | 7 | context "when a type is not provided" do 8 | 9 | before do 10 | @child = { :first_name => "Hank", :last_name => "Moody" } 11 | @parent = Person.new(:title => "Mr.") 12 | @options = Mongoid::Associations::Options.new(:name => :name) 13 | end 14 | 15 | it "incorporates the hash into the object graph" do 16 | @child.assimilate(@parent, @options) 17 | @parent.name.first_name.should == "Hank" 18 | @parent.name.last_name.should == "Moody" 19 | end 20 | 21 | end 22 | 23 | context "when a type is provided" do 24 | 25 | before do 26 | @child = { :speed => 300 } 27 | @parent = Canvas.new(:name => "web page") 28 | @options = Mongoid::Associations::Options.new(:name => :writer) 29 | end 30 | 31 | it "incorporates the hash into the object graph with the supplied type" do 32 | @child.assimilate(@parent, @options, HtmlWriter) 33 | @parent.writer.should be_a_kind_of(HtmlWriter) 34 | @parent.writer.speed.should == 300 35 | end 36 | 37 | it "adds the _type field to the hash" do 38 | @child.assimilate(@parent, @options, HtmlWriter) 39 | @parent.writer._type.should == "HtmlWriter" 40 | end 41 | 42 | end 43 | 44 | context "when the child provides the type" do 45 | 46 | before do 47 | @child = { "radius" => 10, "_type" => "Circle" } 48 | @parent = Canvas.new() 49 | @options = Mongoid::Associations::Options.new(:name => :shapes, :class_name => "Shape") 50 | end 51 | 52 | it "should use the _type information from the child object" do 53 | @child.assimilate(@parent, @options) 54 | @parent.shapes.first.should be_a_kind_of(Circle) 55 | @parent.shapes.first.radius.should == 10 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /spec/unit/mongoid/extras_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Extras do 4 | 5 | before do 6 | @klass = Class.new do 7 | include Mongoid::Extras 8 | end 9 | end 10 | 11 | describe ".cache" do 12 | 13 | before do 14 | @klass.cache 15 | end 16 | 17 | it "sets the cached boolean on the class" do 18 | @klass.cached.should be_true 19 | end 20 | 21 | end 22 | 23 | describe ".cached?" do 24 | 25 | context "when the class is cached" do 26 | 27 | before do 28 | @klass.cache 29 | end 30 | 31 | it "returns true" do 32 | @klass.should be_cached 33 | end 34 | end 35 | 36 | context "when the class is not cached" do 37 | 38 | it "returns false" do 39 | @klass.should_not be_cached 40 | end 41 | end 42 | 43 | end 44 | 45 | describe "#cached?" do 46 | 47 | before do 48 | @klass.cache 49 | @doc = @klass.new 50 | end 51 | 52 | it "returns the class cached? value" do 53 | @doc.should be_cached 54 | end 55 | end 56 | 57 | describe ".enslave" do 58 | 59 | before do 60 | @klass.enslave 61 | end 62 | 63 | it "sets the enslaved boolean on the class" do 64 | @klass.enslaved.should be_true 65 | end 66 | 67 | end 68 | 69 | describe ".enslaved?" do 70 | 71 | context "when the class is enslaved" do 72 | 73 | before do 74 | @klass.enslave 75 | end 76 | 77 | it "returns true" do 78 | @klass.should be_enslaved 79 | end 80 | end 81 | 82 | context "when the class is not enslaved" do 83 | 84 | it "returns false" do 85 | @klass.should_not be_enslaved 86 | end 87 | end 88 | 89 | end 90 | 91 | describe "#enslaved?" do 92 | 93 | before do 94 | @klass.enslave 95 | @doc = @klass.new 96 | end 97 | 98 | it "returns the class enslaved? value" do 99 | @doc.should be_enslaved 100 | end 101 | end 102 | end 103 | -------------------------------------------------------------------------------- /spec/unit/mongoid/persistence/remove_all_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Persistence::RemoveAll do 4 | 5 | let(:document) do 6 | Patient.new(:title => "Mr") 7 | end 8 | 9 | let(:collection) do 10 | stub_everything.quacks_like(Mongoid::Collection.allocate) 11 | end 12 | 13 | let(:cursor) do 14 | stub.quacks_like(Mongoid::Cursor.allocate) 15 | end 16 | 17 | let(:selector) do 18 | { :field1 => { "$exists" => true } } 19 | end 20 | 21 | before do 22 | Patient.stubs(:_collection).returns(collection) 23 | end 24 | 25 | describe "#initialize" do 26 | 27 | let(:remove_all) do 28 | Mongoid::Persistence::RemoveAll.new(Patient, false, selector) 29 | end 30 | 31 | it "sets the collection" do 32 | remove_all.collection.should == document.collection 33 | end 34 | 35 | it "sets the options" do 36 | remove_all.options.should == 37 | { :safe => Mongoid.persist_in_safe_mode } 38 | end 39 | 40 | it "sets the selector" do 41 | remove_all.selector.should == selector 42 | end 43 | end 44 | 45 | describe "#persist" do 46 | 47 | def root_delete_expectation 48 | lambda { 49 | collection.expects(:remove).with(selector, :safe => true).returns(true) 50 | } 51 | end 52 | 53 | def root_find_expectation 54 | lambda { 55 | collection.expects(:find).with( 56 | selector 57 | ).returns(cursor) 58 | } 59 | end 60 | 61 | let(:remove_all) do 62 | Mongoid::Persistence::RemoveAll.new(Patient, false, selector) 63 | end 64 | 65 | context "when the document is a root document" do 66 | 67 | it "remove_alls the document from the collection" do 68 | root_delete_expectation.call 69 | root_find_expectation.call 70 | cursor.expects(:count).returns(30) 71 | remove_all.persist 72 | end 73 | 74 | it "returns the count of documents removed" do 75 | root_delete_expectation.call 76 | root_find_expectation.call 77 | cursor.expects(:count).returns(30) 78 | remove_all.persist.should == 30 79 | end 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /lib/mongoid/fields.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc 3 | module Fields #:nodoc 4 | extend ActiveSupport::Concern 5 | included do 6 | # Set up the class attributes that must be available to all subclasses. 7 | # These include defaults, fields 8 | class_inheritable_accessor :fields 9 | 10 | self.fields = {} 11 | delegate :defaults, :fields, :to => "self.class" 12 | end 13 | 14 | module ClassMethods #:nodoc 15 | # Defines all the fields that are accessable on the Document 16 | # For each field that is defined, a getter and setter will be 17 | # added as an instance method to the Document. 18 | # 19 | # Options: 20 | # 21 | # name: The name of the field, as a +Symbol+. 22 | # options: A +Hash+ of options to supply to the +Field+. 23 | # 24 | # Example: 25 | # 26 | # field :score, :default => 0 27 | def field(name, options = {}) 28 | access = name.to_s 29 | set_field(access, options) 30 | attr_protected name if options[:accessible] == false 31 | end 32 | 33 | # Returns the default values for the fields on the document 34 | def defaults 35 | fields.inject({}) do |defs,(field_name,field)| 36 | next(defs) if field.default.nil? 37 | defs[field_name.to_s] = field.default 38 | defs 39 | end 40 | end 41 | 42 | protected 43 | # Define a field attribute for the +Document+. 44 | def set_field(name, options = {}) 45 | meth = options.delete(:as) || name 46 | fields[name] = Field.new(name, options) 47 | create_accessors(name, meth, options) 48 | add_dirty_methods(name) 49 | end 50 | 51 | # Create the field accessors. 52 | def create_accessors(name, meth, options = {}) 53 | define_method(meth) { read_attribute(name) } 54 | define_method("#{meth}=") { |value| write_attribute(name, value) } 55 | define_method("#{meth}?") do 56 | attr = read_attribute(name) 57 | (options[:type] == Boolean) ? attr == true : attr.present? 58 | end 59 | end 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /lib/mongoid/associations/referenced_in.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Associations #:nodoc: 4 | # Represents a relational association to a "parent" object. 5 | class ReferencedIn < Proxy 6 | 7 | # Initializing a related association only requires looking up the object 8 | # by its id. 9 | # 10 | # Options: 11 | # 12 | # document: The +Document+ that contains the relationship. 13 | # options: The association +Options+. 14 | def initialize(document, foreign_key, options, target = nil) 15 | @options = options 16 | @target = target || options.klass.find(foreign_key) 17 | extends(options) 18 | end 19 | 20 | class << self 21 | # Instantiate a new +ReferencedIn+ or return nil if the foreign key is 22 | # nil. It is preferrable to use this method over the traditional call 23 | # to new. 24 | # 25 | # Options: 26 | # 27 | # document: The +Document+ that contains the relationship. 28 | # options: The association +Options+. 29 | def instantiate(document, options, target = nil) 30 | foreign_key = document.send(options.foreign_key) 31 | return nil if foreign_key.blank? && target.nil? 32 | new(document, foreign_key, options, target) 33 | end 34 | 35 | # Returns the macro used to create the association. 36 | def macro 37 | :referenced_in 38 | end 39 | 40 | # Perform an update of the relationship of the parent and child. This 41 | # will assimilate the child +Document+ into the parent's object graph. 42 | # 43 | # Options: 44 | # 45 | # target: The target(parent) object 46 | # document: The +Document+ to update. 47 | # options: The association +Options+ 48 | # 49 | # Example: 50 | # 51 | # ReferencedIn.update(person, game, options) 52 | def update(target, document, options) 53 | document.send("#{options.foreign_key}=", target ? target.id : nil) 54 | instantiate(document, options, target) 55 | end 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /lib/mongoid/cursor.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc 3 | class Cursor 4 | include Enumerable 5 | # Operations on the Mongo::Cursor object that will not get overriden by the 6 | # Mongoid::Cursor are defined here. 7 | OPERATIONS = [ 8 | :close, 9 | :closed?, 10 | :count, 11 | :explain, 12 | :fields, 13 | :full_collection_name, 14 | :hint, 15 | :limit, 16 | :order, 17 | :query_options_hash, 18 | :query_opts, 19 | :selector, 20 | :skip, 21 | :snapshot, 22 | :sort, 23 | :timeout 24 | ] 25 | 26 | attr_reader :collection 27 | 28 | # The operations above will all delegate to the proxied Mongo::Cursor. 29 | # 30 | # Example: 31 | # 32 | # cursor.close 33 | OPERATIONS.each do |name| 34 | define_method(name) { |*args| @cursor.send(name, *args) } 35 | end 36 | 37 | # Iterate over each document in the cursor and yield to it. 38 | # 39 | # Example: 40 | # 41 | # cursor.each { |doc| p doc.title } 42 | def each 43 | @cursor.each do |document| 44 | yield Mongoid::Factory.build(@klass, document) 45 | end 46 | end 47 | 48 | # Create the new +Mongoid::Cursor+. 49 | # 50 | # Options: 51 | # 52 | # collection: The Mongoid::Collection instance. 53 | # cursor: The Mongo::Cursor to be proxied. 54 | # 55 | # Example: 56 | # 57 | # Mongoid::Cursor.new(Person, cursor) 58 | def initialize(klass, collection, cursor) 59 | @klass, @collection, @cursor = klass, collection, cursor 60 | end 61 | 62 | # Return the next document in the cursor. Will instantiate a new Mongoid 63 | # document with the attributes. 64 | # 65 | # Example: 66 | # 67 | # cursor.next_document 68 | def next_document 69 | Mongoid::Factory.build(@klass, @cursor.next_document) 70 | end 71 | 72 | # Returns an array of all the documents in the cursor. 73 | # 74 | # Example: 75 | # 76 | # cursor.to_a 77 | def to_a 78 | @cursor.to_a.collect { |attrs| Mongoid::Factory.build(@klass, attrs) } 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /spec/unit/mongoid/extensions/symbol/inflections_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Extensions::Symbol::Inflections do 4 | 5 | describe "#singular?" do 6 | 7 | context "when singular" do 8 | 9 | it "returns true" do 10 | :bat.singular?.should be_true 11 | end 12 | 13 | end 14 | 15 | context "when plural" do 16 | 17 | it "returns false" do 18 | :bats.singular?.should be_false 19 | end 20 | 21 | end 22 | 23 | end 24 | 25 | describe "plural?" do 26 | 27 | context "when singular" do 28 | 29 | it "returns false" do 30 | :bat.plural?.should be_false 31 | end 32 | 33 | end 34 | 35 | context "when plural" do 36 | 37 | it "returns true" do 38 | :bats.plural?.should be_true 39 | end 40 | 41 | end 42 | 43 | end 44 | 45 | describe "invert" do 46 | 47 | context "when :asc" do 48 | 49 | it "returns :desc" do 50 | :asc.invert.should == :desc 51 | end 52 | 53 | end 54 | 55 | context "when :ascending" do 56 | 57 | it "returns :descending" do 58 | :ascending.invert.should == :descending 59 | end 60 | 61 | end 62 | 63 | context "when :desc" do 64 | 65 | it "returns :asc" do 66 | :desc.invert.should == :asc 67 | end 68 | 69 | end 70 | 71 | context "when :descending" do 72 | 73 | it "returns :ascending" do 74 | :descending.invert.should == :ascending 75 | end 76 | 77 | end 78 | 79 | end 80 | 81 | describe "#gt" do 82 | 83 | it 'returns :"foo $gt"' do 84 | ret = :foo.gt 85 | ret.key.should == :foo 86 | ret.operator.should == "gt" 87 | end 88 | end 89 | 90 | describe "#near" do 91 | 92 | it 'returns :"foo $near"' do 93 | ret = :foo.near 94 | ret.key.should == :foo 95 | ret.operator.should == "near" 96 | end 97 | end 98 | 99 | describe "#within" do 100 | 101 | it "returns :foo $within" do 102 | ret = :foo.within 103 | ret.key.should == :foo 104 | ret.operator.should == "within" 105 | end 106 | end 107 | end 108 | -------------------------------------------------------------------------------- /spec/unit/mongoid/callbacks_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Callbacks do 4 | 5 | describe ".included" do 6 | 7 | before do 8 | @class = Class.new do 9 | include Mongoid::Callbacks 10 | end 11 | end 12 | 13 | it "includes the before_create callback" do 14 | @class.should respond_to(:before_create) 15 | end 16 | 17 | it "includes the after_create callback" do 18 | @class.should respond_to(:after_create) 19 | end 20 | 21 | it "includes the before_destroy callback" do 22 | @class.should respond_to(:before_destroy) 23 | end 24 | 25 | it "includes the after_destroy callback" do 26 | @class.should respond_to(:after_destroy) 27 | end 28 | 29 | it "includes the before_save callback" do 30 | @class.should respond_to(:before_save) 31 | end 32 | 33 | it "includes the after_save callback" do 34 | @class.should respond_to(:after_save) 35 | end 36 | 37 | it "includes the before_update callback" do 38 | @class.should respond_to(:before_update) 39 | end 40 | 41 | it "includes the after_update callback" do 42 | @class.should respond_to(:after_update) 43 | end 44 | 45 | it "includes the before_validation callback" do 46 | @class.should respond_to(:before_validation) 47 | end 48 | 49 | it "includes the after_validation callback" do 50 | @class.should respond_to(:after_validation) 51 | end 52 | 53 | end 54 | 55 | describe ".before_create" do 56 | 57 | before do 58 | @artist = Artist.new(:name => "Depeche Mode") 59 | end 60 | 61 | context "callback returns true" do 62 | before do 63 | @artist.expects(:before_create_stub).returns(true) 64 | end 65 | 66 | it "should get saved" do 67 | @artist.save.should == true 68 | @artist.persisted?.should == true 69 | end 70 | end 71 | 72 | context "callback returns false" do 73 | before do 74 | @artist.expects(:before_create_stub).returns(false) 75 | end 76 | 77 | it "should not get saved" do 78 | @artist.save.should == false 79 | @artist.persisted?.should == false 80 | end 81 | end 82 | 83 | end 84 | 85 | end 86 | -------------------------------------------------------------------------------- /lib/mongoid/collections.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc 3 | # The collections module is used for providing functionality around setting 4 | # up and updating collections. 5 | module Collections 6 | extend ActiveSupport::Concern 7 | included do 8 | cattr_accessor :_collection, :collection_name 9 | self.collection_name = self.name.collectionize 10 | 11 | delegate :collection, :db, :to => "self.class" 12 | end 13 | 14 | module ClassMethods #:nodoc: 15 | # Returns the collection associated with this +Document+. If the 16 | # document is embedded, there will be no collection associated 17 | # with it. 18 | # 19 | # Returns: Mongo::Collection 20 | def collection 21 | raise Errors::InvalidCollection.new(self) if embedded? 22 | self._collection || set_collection 23 | add_indexes; self._collection 24 | end 25 | 26 | # Return the database associated with this collection. 27 | # 28 | # Example: 29 | # 30 | # Person.db 31 | def db 32 | collection.db 33 | end 34 | 35 | # Convenience method for getting index information from the collection. 36 | # 37 | # Example: 38 | # 39 | # Person.index_information 40 | def index_information 41 | collection.index_information 42 | end 43 | 44 | # The MongoDB logger is not exposed through the driver to be changed 45 | # after initialization of the connection, this is a hacky way around that 46 | # if logging needs to be changed at runtime. 47 | # 48 | # Example: 49 | # 50 | # Person.logger = Logger.new($stdout) 51 | def logger=(logger) 52 | db.connection.instance_variable_set(:@logger, logger) 53 | end 54 | 55 | # Macro for setting the collection name to store in. 56 | # 57 | # Example: 58 | # 59 | # Person.store_in :populdation 60 | def store_in(name) 61 | self.collection_name = name.to_s 62 | set_collection 63 | end 64 | 65 | protected 66 | def set_collection 67 | self._collection = Mongoid::Collection.new(self, self.collection_name) 68 | end 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /spec/unit/mongoid/associations/foreign_key_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Associations::ForeignKey do 4 | 5 | describe ".constraint" do 6 | 7 | context "when foreign key option provided" do 8 | 9 | it "returns the key" do 10 | Person.constraint( 11 | :post, 12 | { :foreign_key => :user_id }, 13 | :in 14 | ).should == "user_id" 15 | end 16 | end 17 | 18 | context "for a references_one" do 19 | 20 | context "when the class is one word" do 21 | 22 | it "returns the class name plus _id" do 23 | Person.constraint( 24 | :post, 25 | {}, 26 | :one 27 | ).should == "person_id" 28 | end 29 | end 30 | 31 | context "when the class is multiple words" do 32 | 33 | it "returns the underscored class name plus _id" do 34 | MixedDrink.constraint( 35 | :post, 36 | {}, 37 | :one 38 | ).should == "mixed_drink_id" 39 | end 40 | end 41 | end 42 | 43 | context "for a references_many" do 44 | 45 | context "when the class is one word" do 46 | 47 | it "returns the class name plus _id" do 48 | Person.constraint( 49 | :posts, 50 | {}, 51 | :many 52 | ).should == "person_id" 53 | end 54 | end 55 | 56 | context "when the class is multiple words" do 57 | 58 | it "returns the underscored class name plus _id" do 59 | MixedDrink.constraint( 60 | :posts, 61 | {}, 62 | :many 63 | ).should == "mixed_drink_id" 64 | end 65 | end 66 | end 67 | 68 | context "for a references_many_as_array" do 69 | 70 | it "returns the singularized association name plus _ids" do 71 | Person.constraint( 72 | :posts, 73 | {}, 74 | :many_as_array 75 | ).should == "post_ids" 76 | end 77 | end 78 | 79 | context "for a referenced_in" do 80 | 81 | it "returns the name plus _id" do 82 | Post.constraint( 83 | :person, 84 | {}, 85 | :in 86 | ).should == "person_id" 87 | end 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /spec/unit/mongoid/persistence/remove_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Persistence::Remove do 4 | 5 | let(:document) do 6 | Patient.new(:title => "Mr") 7 | end 8 | 9 | let(:address) do 10 | Address.new(:street => "Oxford St") 11 | end 12 | 13 | let(:collection) do 14 | stub.quacks_like(Mongoid::Collection.allocate) 15 | end 16 | 17 | before do 18 | document.stubs(:collection).returns(collection) 19 | end 20 | 21 | describe "#initialize" do 22 | 23 | let(:remove) do 24 | Mongoid::Persistence::Remove.new(document) 25 | end 26 | 27 | it "sets the document" do 28 | remove.document.should == document 29 | end 30 | 31 | it "sets the collection" do 32 | remove.collection.should == document.collection 33 | end 34 | 35 | it "defaults validation to true" do 36 | remove.validate.should == true 37 | end 38 | 39 | it "sets the options" do 40 | remove.options.should == 41 | { :safe => Mongoid.persist_in_safe_mode } 42 | end 43 | end 44 | 45 | describe "#persist" do 46 | 47 | def root_delete_expectation 48 | lambda { 49 | collection.expects(:remove).with( 50 | { :_id => document.id }, 51 | :safe => true 52 | ).returns(true) 53 | } 54 | end 55 | 56 | let(:remove) do 57 | Mongoid::Persistence::Remove.new(document) 58 | end 59 | 60 | context "when the document is a root document" do 61 | 62 | it "removes the document from the collection" do 63 | root_delete_expectation.call 64 | remove.persist.should == true 65 | end 66 | end 67 | 68 | context "when the document is embedded" do 69 | 70 | before do 71 | document.addresses << address 72 | end 73 | 74 | let(:remove) do 75 | Mongoid::Persistence::Remove.new(address) 76 | end 77 | 78 | let(:persister) do 79 | stub.quacks_like(Mongoid::Persistence::RemoveEmbedded.allocate) 80 | end 81 | 82 | it "delegates to the embedded persister" do 83 | Mongoid::Persistence::RemoveEmbedded.expects(:new).with(address, true).returns(persister) 84 | persister.expects(:persist).returns(true) 85 | remove.persist.should == true 86 | end 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /lib/mongoid/railtie.rb: -------------------------------------------------------------------------------- 1 | require "singleton" 2 | require "rails" 3 | require "mongoid/config" 4 | module Rails #:nodoc: 5 | module Mongoid #:nodoc: 6 | class Railtie < Rails::Railtie #:nodoc: 7 | 8 | config.generators.orm :mongoid, :migration => false 9 | 10 | rake_tasks do 11 | load "mongoid/railties/database.rake" 12 | end 13 | 14 | # Exposes Mongoid's configuration to the Rails application configuration. 15 | # 16 | # Example: 17 | # 18 | # module MyApplication 19 | # class Application < Rails::Application 20 | # config.mongoid.logger = Logger.new($stdout, :warn) 21 | # config.mongoid.reconnect_time = 10 22 | # end 23 | # end 24 | config.mongoid = ::Mongoid::Config.instance 25 | 26 | # Initialize Mongoid. This will look for a mongoid.yml in the config 27 | # directory and configure mongoid appropriately. 28 | # 29 | # Example mongoid.yml: 30 | # 31 | # defaults: &defaults 32 | # host: localhost 33 | # slaves: 34 | # # - host: localhost 35 | # # port: 27018 36 | # # - host: localhost 37 | # # port: 27019 38 | # allow_dynamic_fields: false 39 | # parameterize_keys: false 40 | # persist_in_safe_mode: false 41 | # 42 | # development: 43 | # <<: *defaults 44 | # database: mongoid 45 | initializer "setup database" do 46 | config_file = Rails.root.join("config", "mongoid.yml") 47 | if config_file.file? 48 | settings = YAML.load(ERB.new(config_file.read).result)[Rails.env] 49 | ::Mongoid.from_hash(settings) if settings.present? 50 | end 51 | end 52 | 53 | initializer "verify that mongoid is configured" do 54 | config.after_initialize do 55 | begin 56 | ::Mongoid.master 57 | rescue ::Mongoid::Errors::InvalidDatabase => e 58 | unless Rails.root.join("config", "mongoid.yml").file? 59 | puts "\nMongoid config not found. Create a config file at: config/mongoid.yml" 60 | puts "to generate one run: script/rails generate mongoid:config\n\n" 61 | end 62 | end 63 | end 64 | end 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /lib/mongoid/field.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | class Field 4 | attr_reader :name, :type 5 | 6 | # Determine if the field is able to be accessible via a mass update. 7 | # 8 | # Returns: 9 | # 10 | # true if accessible, false if not. 11 | def accessible? 12 | !!@accessible 13 | end 14 | 15 | # Get the declared options for this field 16 | # 17 | # Returns: 18 | # 19 | # a hash of options 20 | def options 21 | @options 22 | end 23 | 24 | # Get the default value for the field. 25 | # 26 | # Returns: 27 | # 28 | # The primitive value or a copy of the default. 29 | def default 30 | copy 31 | end 32 | 33 | # Create the new field with a name and optional additional options. Valid 34 | # options are :default 35 | # 36 | # Options: 37 | # 38 | # name: The name of the field as a +Symbol+. 39 | # options: A +Hash+ of options for the field. 40 | # 41 | # Example: 42 | # 43 | # Field.new(:score, :default => 0) 44 | def initialize(name, options = {}) 45 | check_name!(name) 46 | @type = options[:type] || String 47 | @name, @default = name, options[:default] 48 | @copyable = (@default.is_a?(Array) || @default.is_a?(Hash)) 49 | @accessible = options.has_key?(:accessible) ? options[:accessible] : true 50 | @options = options 51 | check_default! 52 | end 53 | 54 | # Used for setting an object in the attributes hash. If nil is provided the 55 | # default will get returned if it exists. 56 | def set(object) 57 | type.set(object) 58 | end 59 | 60 | # Used for retrieving the object out of the attributes hash. 61 | def get(object) 62 | type.get(object) 63 | end 64 | 65 | protected 66 | # Slightly faster default check. 67 | def copy 68 | @copyable ? @default.dup : @default 69 | end 70 | 71 | # Check if the name is valid. 72 | def check_name!(name) 73 | raise Mongoid::Errors::InvalidField.new(name) if Mongoid.destructive_fields.include?(name.to_s) 74 | end 75 | 76 | def check_default! 77 | return if @default.is_a?(Proc) 78 | if !@default.nil? && !@default.is_a?(@type) 79 | raise Mongoid::Errors::InvalidType.new(@type, @default) 80 | end 81 | end 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /spec/integration/mongoid/dirty_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Dirty do 4 | 5 | before do 6 | Person.collection.remove 7 | end 8 | 9 | after do 10 | Person.collection.remove 11 | end 12 | 13 | context "when fields are getting changed" do 14 | 15 | before do 16 | @person = Person.create(:title => "MC", :ssn => "234-11-2533", :some_dynamic_field => 'blah') 17 | @person.title = "DJ" 18 | @person.write_attribute(:ssn, "222-22-2222") 19 | 20 | @person.some_dynamic_field = 'bloop' 21 | end 22 | 23 | it "marks the document as changed" do 24 | @person.changed?.should == true 25 | end 26 | 27 | it "marks field changes" do 28 | @person.changes.should == { 29 | "title" => [ "MC", "DJ" ], 30 | "ssn" => [ "234-11-2533", "222-22-2222" ], 31 | "some_dynamic_field" => [ "blah", "bloop" ] 32 | } 33 | end 34 | 35 | it "marks changed fields" do 36 | @person.changed.should == [ "title", "ssn", "some_dynamic_field" ] 37 | end 38 | 39 | it "marks the field as changed" do 40 | @person.title_changed?.should == true 41 | end 42 | 43 | it "stores previous field values" do 44 | @person.title_was.should == "MC" 45 | end 46 | 47 | it "marks field changes" do 48 | @person.title_change.should == [ "MC", "DJ" ] 49 | end 50 | 51 | it "allows reset of field changes" do 52 | @person.reset_title! 53 | @person.title.should == "MC" 54 | @person.changed.should == [ "ssn", "some_dynamic_field" ] 55 | end 56 | 57 | context "after a save" do 58 | 59 | before do 60 | @person.save! 61 | end 62 | 63 | it "clears changes" do 64 | @person.changed?.should == false 65 | end 66 | 67 | it "stores previous changes" do 68 | @person.previous_changes["title"].should == [ "MC", "DJ" ] 69 | @person.previous_changes["ssn"].should == [ "234-11-2533", "222-22-2222" ] 70 | end 71 | end 72 | end 73 | 74 | context "when associations are getting changed" do 75 | 76 | before do 77 | @person = Person.create(:addresses => [Address.new]) 78 | @person.addresses = [Address.new] 79 | end 80 | 81 | it "should not set the association to nil when hitting the database" do 82 | @person.setters.should_not == {"addresses" => nil} 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /spec/unit/mongoid/indexes_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Indexes do 4 | 5 | describe ".included" do 6 | 7 | before do 8 | @class = Class.new do 9 | include Mongoid::Indexes 10 | end 11 | end 12 | 13 | it "adds an indexed accessor" do 14 | @class.should respond_to(:indexed) 15 | end 16 | 17 | it "defaults indexed to false" do 18 | @class.indexed.should be_false 19 | end 20 | 21 | end 22 | 23 | describe ".add_indexes" do 24 | 25 | before do 26 | @collection = mock 27 | end 28 | 29 | context "when indexes have not been added" do 30 | 31 | before do 32 | @class = Class.new do 33 | include Mongoid::Indexes 34 | def self.hereditary 35 | true 36 | end 37 | end 38 | end 39 | 40 | it "adds the _type index" do 41 | @class.expects(:_collection).returns(@collection) 42 | @collection.expects(:create_index).with(:_type, :unique => false, :background => true) 43 | @class.add_indexes 44 | @class.indexed.should be_true 45 | end 46 | 47 | end 48 | 49 | context "when indexes have been added" do 50 | 51 | before do 52 | @class = Class.new do 53 | include Mongoid::Indexes 54 | def self.hereditary 55 | true 56 | end 57 | end 58 | @class.indexed = true 59 | end 60 | 61 | it "ignores the request" do 62 | @class.add_indexes 63 | end 64 | 65 | end 66 | 67 | end 68 | 69 | describe ".index" do 70 | 71 | before do 72 | @collection = mock 73 | @class = Class.new do 74 | include Mongoid::Indexes 75 | end 76 | @class.expects(:collection).returns(@collection) 77 | end 78 | 79 | context "when unique" do 80 | 81 | it "creates a unique index on the collection" do 82 | @collection.expects(:create_index).with(:name, :unique => true) 83 | @class.index(:name, :unique => true) 84 | end 85 | 86 | end 87 | 88 | context "when not unique" do 89 | 90 | it "creates an index on the collection" do 91 | @collection.expects(:create_index).with(:name, :unique => false) 92 | @class.index(:name) 93 | end 94 | 95 | end 96 | 97 | end 98 | 99 | end 100 | -------------------------------------------------------------------------------- /lib/mongoid/criterion/exclusion.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Criterion #:nodoc: 4 | module Exclusion 5 | # Adds a criterion to the +Criteria+ that specifies values that are not allowed 6 | # to match any document in the database. The MongoDB conditional operator that 7 | # will be used is "$ne". 8 | # 9 | # Options: 10 | # 11 | # attributes: A +Hash+ where the key is the field name and the value is a 12 | # value that must not be equal to the corresponding field value in the database. 13 | # 14 | # Example: 15 | # 16 | # criteria.excludes(:field => "value1") 17 | # 18 | # criteria.excludes(:field1 => "value1", :field2 => "value1") 19 | # 20 | # Returns: self 21 | def excludes(attributes = {}) 22 | mongo_id = attributes.delete(:id) 23 | attributes = attributes.merge(:_id => mongo_id) if mongo_id 24 | update_selector(attributes, "$ne") 25 | end 26 | 27 | # Adds a criterion to the +Criteria+ that specifies values where none 28 | # should match in order to return results. This is similar to an SQL "NOT IN" 29 | # clause. The MongoDB conditional operator that will be used is "$nin". 30 | # 31 | # Options: 32 | # 33 | # attributes: A +Hash+ where the key is the field name and the value is an 34 | # +Array+ of values that none can match. 35 | # 36 | # Example: 37 | # 38 | # criteria.not_in(:field => ["value1", "value2"]) 39 | # 40 | # criteria.not_in(:field1 => ["value1", "value2"], :field2 => ["value1"]) 41 | # 42 | # Returns: self 43 | def not_in(attributes) 44 | update_selector(attributes, "$nin") 45 | end 46 | 47 | # Adds a criterion to the +Criteria+ that specifies the fields that will 48 | # get returned from the Document. Used mainly for list views that do not 49 | # require all fields to be present. This is similar to SQL "SELECT" values. 50 | # 51 | # Options: 52 | # 53 | # args: A list of field names to retrict the returned fields to. 54 | # 55 | # Example: 56 | # 57 | # criteria.only(:field1, :field2, :field3) 58 | # 59 | # Returns: self 60 | def only(*args) 61 | @options[:fields] = args.flatten if args.any?; self 62 | end 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /lib/mongoid/scope.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | class Scope #:nodoc: 4 | 5 | delegate :scopes, :to => :parent 6 | 7 | attr_reader :parent, :conditions 8 | 9 | # If the other is a scope then compare the parent and conditions, otherwise 10 | # if its enumerable collect and compare. 11 | def ==(other) 12 | case other 13 | when Scope 14 | @parent == other.parent && @conditions == other.conditions 15 | when Enumerable 16 | @collection ||= entries 17 | return (@collection == other) 18 | else 19 | return false 20 | end 21 | end 22 | 23 | # Create the new +Scope+. If a block is passed in, this Scope will extend 24 | # the block. 25 | # 26 | # Options: 27 | # 28 | # parent: The class the scope belongs to, or a parent +Scope+. 29 | # conditions: A +Hash+ of conditions. 30 | # 31 | # Example: 32 | # 33 | # Mongoid::Scope.new(Person, { :title => "Sir" }) do 34 | # def knighted? 35 | # title == "Sir" 36 | # end 37 | # end 38 | def initialize(parent, conditions, &block) 39 | @parent, @conditions = parent, conditions 40 | extend Module.new(&block) if block_given? 41 | end 42 | 43 | # Return the class for the +Scope+. This will be the parent if the parent 44 | # is a class, otherwise will be nil. 45 | def klass 46 | @klass ||= @parent unless @parent.is_a?(Scope) 47 | end 48 | 49 | # Chaining is supported through method_missing. If a scope is already 50 | # defined with the method name the call will be passed there, otherwise it 51 | # will be passed to the target or parent. 52 | def method_missing(name, *args, &block) 53 | if scopes.include?(name) 54 | scopes[name].call(self, *args) 55 | elsif klass 56 | target.send(name, *args, &block) 57 | else 58 | @parent.fuse(@conditions); @parent.send(name, *args, &block) 59 | end 60 | end 61 | 62 | # The +Scope+ must respond like a +Criteria+ object. If this is a parent 63 | # criteria delegate to the target, otherwise bubble up to the parent. 64 | def respond_to?(name) 65 | super || (klass ? target.respond_to?(name) : @parent.respond_to?(name)) 66 | end 67 | 68 | # Returns the target criteria if it has already been set or creates a new 69 | # criteria from the parent class. 70 | def target 71 | @target ||= klass.criteria.fuse(@conditions) 72 | end 73 | end 74 | end 75 | 76 | -------------------------------------------------------------------------------- /lib/mongoid/hierarchy.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc 3 | module Hierarchy #:nodoc 4 | extend ActiveSupport::Concern 5 | included do 6 | cattr_accessor :hereditary 7 | self.hereditary = false 8 | 9 | attr_accessor :_parent 10 | end 11 | 12 | module InstanceMethods #:nodoc: 13 | 14 | # Get all child +Documents+ to this +Document+, going n levels deep if 15 | # necessary. This is used when calling update persistence operations from 16 | # the root document, where changes in the entire tree need to be 17 | # determined. Note that persistence from the embedded documents will 18 | # always be preferred, since they are optimized calls... This operation 19 | # can get expensive in domains with large hierarchies. 20 | # 21 | # Example: 22 | # 23 | # person._children 24 | # 25 | # Returns: 26 | # 27 | # All child +Documents+ to this +Document+ in the entire hierarchy. 28 | def _children 29 | associations.inject([]) do |children, (name, metadata)| 30 | if metadata.embedded? && name != "versions" 31 | child = send(name) 32 | child.to_a.each do |doc| 33 | children.push(doc).concat(doc._children) 34 | end unless child.blank? 35 | end 36 | children 37 | end 38 | end 39 | 40 | # Is inheritance in play here? 41 | # 42 | # Returns: 43 | # 44 | # true if inheritance used, false if not. 45 | def hereditary? 46 | !!self.hereditary 47 | end 48 | 49 | # Sets up a child/parent association. This is used for newly created 50 | # objects so they can be properly added to the graph. 51 | # 52 | # Options: 53 | # 54 | # abject: The parent object that needs to be set for the child. 55 | # association_name: The name of the association for the child. 56 | # 57 | # Example: 58 | # 59 | # address.parentize(person, :addresses) 60 | def parentize(object, association_name) 61 | self._parent = object 62 | self.association_name = association_name.to_s 63 | end 64 | 65 | # Return the root +Document+ in the object graph. If the current +Document+ 66 | # is the root object in the graph it will return self. 67 | def _root 68 | object = self 69 | while (object._parent) do object = object._parent; end 70 | object || self 71 | end 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /lib/mongoid/associations/embedded_in.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Associations #:nodoc: 4 | # Represents an association that is embedded within another document in the 5 | # database, either as one or many. 6 | class EmbeddedIn < Proxy 7 | 8 | # Creates the new association by setting the internal 9 | # target as the passed in Document. This should be the 10 | # parent. 11 | # 12 | # All method calls on this object will then be delegated 13 | # to the internal document itself. 14 | # 15 | # Options: 16 | # 17 | # target: The parent +Document+ 18 | # options: The association options 19 | def initialize(target, options) 20 | @target, @options = target, options 21 | extends(options) 22 | end 23 | 24 | # Returns the parent document. The id param is present for 25 | # compatibility with rails, however this could be overwritten 26 | # in the future. 27 | def find(id) 28 | @target 29 | end 30 | 31 | class << self 32 | # Creates the new association by setting the internal 33 | # document as the passed in Document. This should be the 34 | # parent. 35 | # 36 | # Options: 37 | # 38 | # document: The parent +Document+ 39 | # options: The association options 40 | def instantiate(document, options) 41 | target = document._parent 42 | target.nil? ? nil : new(target, options) 43 | end 44 | 45 | # Returns the macro used to create the association. 46 | def macro 47 | :embedded_in 48 | end 49 | 50 | # Perform an update of the relationship of the parent and child. This 51 | # is initialized by setting a parent object as the association on the 52 | # +Document+. Will properly set an embeds_one or an embeds_many. 53 | # 54 | # Returns: 55 | # 56 | # A new +EmbeddedIn+ association proxy. 57 | def update(target, child, options) 58 | inverse = determine_name(target, options) 59 | child.parentize(target, inverse) 60 | child.notify 61 | target.unmemoize(inverse) 62 | instantiate(child, options) 63 | end 64 | 65 | protected 66 | def determine_name(target, options) 67 | inverse = options.inverse_of 68 | return inverse unless inverse.is_a?(Array) 69 | inverse.detect { |name| target.respond_to?(name) } 70 | end 71 | end 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /lib/mongoid/persistence/update.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid #:nodoc: 3 | module Persistence #:nodoc: 4 | # Update is a persistence command responsible for taking a document that 5 | # has already been saved to the database and saving it, depending on 6 | # whether or not the document has been modified. 7 | # 8 | # Before persisting the command will check via dirty attributes if the 9 | # document has changed, if not, it will simply return true. If it has it 10 | # will go through the validation steps, run callbacks, and set the changed 11 | # fields atomically on the document. The underlying query resembles the 12 | # following MongoDB query: 13 | # 14 | # collection.update( 15 | # { "_id" : 1, 16 | # { "$set" : { "field" : "value" }, 17 | # false, 18 | # false 19 | # ); 20 | # 21 | # For embedded documents it will use the positional locator: 22 | # 23 | # collection.update( 24 | # { "_id" : 1, "addresses._id" : 2 }, 25 | # { "$set" : { "addresses.$.field" : "value" }, 26 | # false, 27 | # false 28 | # ); 29 | # 30 | class Update < Command 31 | # Persist the document that is to be updated to the database. This will 32 | # only write changed fields via MongoDB's $set modifier operation. 33 | # 34 | # Example: 35 | # 36 | # Update.persist 37 | # 38 | # Returns: 39 | # 40 | # +true+ or +false+, depending on validation. 41 | def persist 42 | return false if validate && @document.invalid?(:update) 43 | @document.run_callbacks(:save) do 44 | @document.run_callbacks(:update) do 45 | if update 46 | @document.move_changes 47 | @document._children.each do |child| 48 | child.move_changes 49 | child.new_record = false if child.new_record? 50 | end 51 | else 52 | return false 53 | end 54 | end 55 | end; true 56 | end 57 | 58 | protected 59 | # Update the document in the database atomically. 60 | def update 61 | updates = @document._updates 62 | unless updates.empty? 63 | other_pushes = updates.delete(:other) 64 | 65 | @collection.update(@document._selector, updates, @options.merge(:multi => false)) 66 | @collection.update(@document._selector, { "$pushAll" => other_pushes }, @options.merge(:multi => false)) if other_pushes 67 | end; true 68 | end 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /spec/unit/mongoid/collections_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Collections do 4 | 5 | describe ".collection" do 6 | 7 | before do 8 | @person = Person.new 9 | end 10 | 11 | it "sets the collection name to the class pluralized" do 12 | Person.collection.name.should == "people" 13 | end 14 | 15 | context "when document is embedded" do 16 | 17 | before do 18 | @address = Address.new 19 | end 20 | 21 | it "raises an error" do 22 | lambda { Address.collection }.should raise_error 23 | end 24 | end 25 | end 26 | 27 | describe ".collection_name=" do 28 | 29 | context "on a parent class" do 30 | 31 | it "sets the collection name on the document class" do 32 | Patient.collection_name = "pats" 33 | Patient.collection_name.should == "pats" 34 | end 35 | end 36 | 37 | context "on a subclass" do 38 | 39 | after do 40 | Canvas.collection_name = "canvases" 41 | end 42 | 43 | it "sets the collection name for the entire hierarchy" do 44 | Firefox.collection_name = "browsers" 45 | Canvas.collection_name.should == "browsers" 46 | end 47 | end 48 | end 49 | 50 | describe ".index_information" do 51 | 52 | it "returns index information from the collection" do 53 | Person.index_information["title_1"].should_not be_nil 54 | end 55 | end 56 | 57 | describe ".logger=" do 58 | 59 | before do 60 | @logger = stub.quacks_like(Logger.allocate) 61 | Person.logger = @logger 62 | end 63 | 64 | after do 65 | Person.logger = nil 66 | end 67 | 68 | it "sets the logger on the connection" do 69 | Person.db.connection.logger.should == @logger 70 | end 71 | end 72 | 73 | describe ".store_in" do 74 | 75 | context "on a parent class" do 76 | 77 | it "sets the collection name and collection for the document" do 78 | Mongoid::Collection.expects(:new).with(Patient, "population").returns(@collection) 79 | Patient.store_in :population 80 | Patient.collection_name.should == "population" 81 | end 82 | end 83 | 84 | context "on a subclass" do 85 | 86 | after do 87 | Mongoid::Collection.expects(:new).with(Firefox, "canvases") 88 | Firefox.store_in :canvases 89 | end 90 | 91 | it "changes the collection name for the entire hierarchy" do 92 | Mongoid::Collection.expects(:new).with(Firefox, "browsers").returns(@collection) 93 | Firefox.store_in :browsers 94 | Canvas.collection_name.should == "browsers" 95 | end 96 | end 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /spec/unit/mongoid/associations/meta_data_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Associations::MetaData do 4 | 5 | before do 6 | @extension = lambda { "Test" } 7 | end 8 | 9 | let(:association) do 10 | Mongoid::Associations::ReferencesMany 11 | end 12 | 13 | let(:options) do 14 | Mongoid::Associations::Options.new( 15 | :name => :games, 16 | :extend => @extension, 17 | :foreign_key => "person_id", 18 | :inverse_of => :people 19 | ) 20 | end 21 | 22 | let(:metadata) do 23 | Mongoid::Associations::MetaData.new(association, options) 24 | end 25 | 26 | describe "#embedded?" do 27 | 28 | context "when association is embedded" do 29 | 30 | before do 31 | @embedded = Mongoid::Associations::EmbedsOne 32 | @meta = Mongoid::Associations::MetaData.new(@embedded, nil) 33 | end 34 | 35 | it "returns true" do 36 | @meta.embedded?.should == true 37 | end 38 | end 39 | 40 | context "when association is not embedded" do 41 | 42 | it "returns false" do 43 | metadata.embedded?.should == false 44 | end 45 | end 46 | end 47 | 48 | describe "#extension" do 49 | 50 | it "delegates to the options" do 51 | metadata.extension.should == @extension 52 | end 53 | end 54 | 55 | describe "#foreign_key" do 56 | 57 | it "delegates to the options" do 58 | metadata.foreign_key.should == "person_id" 59 | end 60 | end 61 | 62 | describe "#inverse_of" do 63 | 64 | it "delegates to the options" do 65 | metadata.inverse_of.should == :people 66 | end 67 | end 68 | 69 | describe "#klass" do 70 | 71 | it "returns the associated klass" do 72 | metadata.klass.should == Game 73 | end 74 | end 75 | 76 | describe "#macro" do 77 | 78 | it "returns the association macro" do 79 | metadata.macro.should == :references_many 80 | end 81 | end 82 | 83 | describe "#name" do 84 | 85 | it "delegates to the options" do 86 | metadata.name.should == "games" 87 | end 88 | end 89 | 90 | describe "#options" do 91 | 92 | it "returns the association options" do 93 | metadata.options.should == options 94 | end 95 | end 96 | 97 | describe "#polymorphic" do 98 | 99 | it "delegates to the options" do 100 | metadata.polymorphic.should be_false 101 | end 102 | end 103 | 104 | describe "#association" do 105 | 106 | it "returns the association type" do 107 | metadata.association.should == Mongoid::Associations::ReferencesMany 108 | end 109 | end 110 | end 111 | --------------------------------------------------------------------------------