├── .document ├── tasks ├── ci.rake ├── yard.rake ├── yardstick.rake └── spec.rake ├── .yardopts ├── lib └── dm-core │ ├── version.rb │ ├── property │ ├── text.rb │ ├── date.rb │ ├── time.rb │ ├── date_time.rb │ ├── serial.rb │ ├── float.rb │ ├── class.rb │ ├── binary.rb │ ├── boolean.rb │ ├── integer.rb │ ├── invalid_value_error.rb │ ├── lookup.rb │ ├── object.rb │ ├── string.rb │ ├── decimal.rb │ ├── numeric.rb │ └── discriminator.rb │ ├── core_ext │ ├── pathname.rb │ ├── symbol.rb │ └── kernel.rb │ ├── identity_map.rb │ ├── support │ ├── ext │ │ ├── try_dup.rb │ │ ├── array.rb │ │ ├── blank.rb │ │ ├── string.rb │ │ ├── module.rb │ │ ├── hash.rb │ │ └── object.rb │ ├── assertions.rb │ ├── local_object_space.rb │ ├── chainable.rb │ ├── deprecate.rb │ ├── subject.rb │ ├── equalizer.rb │ ├── descendant_set.rb │ └── inflections.rb │ ├── backwards.rb │ ├── spec │ └── lib │ │ ├── collection_helpers.rb │ │ ├── counter_adapter.rb │ │ ├── pending_helpers.rb │ │ ├── adapter_helpers.rb │ │ └── spec_helper.rb │ ├── resource │ ├── persistence_state │ │ ├── deleted.rb │ │ ├── persisted.rb │ │ ├── immutable.rb │ │ ├── clean.rb │ │ ├── transient.rb │ │ └── dirty.rb │ └── persistence_state.rb │ ├── query │ ├── operator.rb │ ├── direction.rb │ └── sort.rb │ ├── model │ ├── is.rb │ ├── scope.rb │ └── hook.rb │ ├── relationship_set.rb │ └── associations │ └── one_to_one.rb ├── Rakefile ├── spec ├── rcov.opts ├── spec.opts ├── support │ ├── core_ext │ │ ├── hash.rb │ │ └── inheritable_attributes.rb │ └── properties │ │ └── huge_integer.rb ├── semipublic │ ├── shared │ │ ├── condition_shared_spec.rb │ │ ├── subject_shared_spec.rb │ │ └── resource_state_shared_spec.rb │ ├── property │ │ ├── serial_spec.rb │ │ ├── binary_spec.rb │ │ ├── string_spec.rb │ │ ├── discriminator_spec.rb │ │ ├── text_spec.rb │ │ ├── lookup_spec.rb │ │ ├── class_spec.rb │ │ ├── date_spec.rb │ │ ├── date_time_spec.rb │ │ ├── boolean_spec.rb │ │ ├── time_spec.rb │ │ ├── integer_spec.rb │ │ ├── float_spec.rb │ │ └── decimal_spec.rb │ ├── adapters │ │ ├── in_memory_adapter_spec.rb │ │ └── abstract_adapter_spec.rb │ ├── resource_spec.rb │ ├── associations │ │ ├── one_to_one_spec.rb │ │ ├── one_to_many_spec.rb │ │ └── many_to_one_spec.rb │ ├── resource │ │ └── state │ │ │ ├── deleted_spec.rb │ │ │ ├── clean_spec.rb │ │ │ └── immutable_spec.rb │ └── model_spec.rb ├── unit │ ├── data_mapper │ │ ├── ordered_set │ │ │ ├── shared │ │ │ │ ├── entries_spec.rb │ │ │ │ ├── empty_spec.rb │ │ │ │ ├── clear_spec.rb │ │ │ │ ├── include_spec.rb │ │ │ │ ├── to_ary_spec.rb │ │ │ │ ├── index_spec.rb │ │ │ │ ├── size_spec.rb │ │ │ │ ├── each_spec.rb │ │ │ │ ├── append_spec.rb │ │ │ │ ├── initialize_spec.rb │ │ │ │ ├── delete_spec.rb │ │ │ │ └── merge_spec.rb │ │ │ ├── hash_spec.rb │ │ │ ├── each_spec.rb │ │ │ ├── empty_spec.rb │ │ │ ├── entries_spec.rb │ │ │ ├── clear_spec.rb │ │ │ ├── to_ary_spec.rb │ │ │ ├── include_spec.rb │ │ │ ├── append_spec.rb │ │ │ ├── size_spec.rb │ │ │ ├── delete_spec.rb │ │ │ ├── index_spec.rb │ │ │ ├── initialize_spec.rb │ │ │ ├── eql_spec.rb │ │ │ ├── merge_spec.rb │ │ │ └── equal_value_spec.rb │ │ └── subject_set │ │ │ ├── shared │ │ │ ├── each_spec.rb │ │ │ ├── get_spec.rb │ │ │ ├── named_spec.rb │ │ │ ├── entries_spec.rb │ │ │ ├── empty_spec.rb │ │ │ ├── clear_spec.rb │ │ │ ├── to_ary_spec.rb │ │ │ ├── include_spec.rb │ │ │ ├── delete_spec.rb │ │ │ ├── size_spec.rb │ │ │ ├── append_spec.rb │ │ │ └── values_at_spec.rb │ │ │ ├── each_spec.rb │ │ │ ├── entries_spec.rb │ │ │ ├── empty_spec.rb │ │ │ ├── include_spec.rb │ │ │ ├── clear_spec.rb │ │ │ ├── to_ary_spec.rb │ │ │ ├── named_spec.rb │ │ │ ├── get_spec.rb │ │ │ ├── delete_spec.rb │ │ │ ├── size_spec.rb │ │ │ ├── append_spec.rb │ │ │ └── values_at_spec.rb │ ├── inflections_spec.rb │ ├── array_spec.rb │ ├── hash_spec.rb │ ├── try_dup_spec.rb │ ├── object_spec.rb │ ├── blank_spec.rb │ └── module_spec.rb ├── public │ ├── property │ │ ├── integer_spec.rb │ │ ├── boolean_spec.rb │ │ ├── date_spec.rb │ │ ├── time_spec.rb │ │ ├── serial_spec.rb │ │ ├── string_spec.rb │ │ ├── float_spec.rb │ │ ├── date_time_spec.rb │ │ ├── decimal_spec.rb │ │ ├── class_spec.rb │ │ ├── binary_spec.rb │ │ ├── text_spec.rb │ │ └── object_spec.rb │ ├── associations │ │ ├── many_to_one_with_boolean_cpk_spec.rb │ │ ├── many_to_one_with_custom_fk_spec.rb │ │ ├── one_to_one_with_boolean_cpk_spec.rb │ │ ├── many_to_many │ │ │ └── read_multiple_join_spec.rb │ │ ├── many_to_one_spec.rb │ │ └── one_to_many_spec.rb │ ├── sel_spec.rb │ ├── finalize_spec.rb │ └── collection_spec.rb ├── spec_helper.rb └── lib │ └── rspec_immediate_feedback_formatter.rb ├── .gitignore ├── dm-core.gemspec ├── .autotest ├── LICENSE └── Gemfile /.document: -------------------------------------------------------------------------------- 1 | lib/**/*.rb 2 | bin/* 3 | -- 4 | README.rdoc 5 | LICENSE 6 | -------------------------------------------------------------------------------- /tasks/ci.rake: -------------------------------------------------------------------------------- 1 | task :ci => [ :verify_measurements, 'metrics:all' ] 2 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --markup rdoc --title 'DataMapper Documentation' --protected 2 | -------------------------------------------------------------------------------- /lib/dm-core/version.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | VERSION = '1.3.0.beta' 3 | end 4 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'rake' 3 | 4 | FileList['tasks/**/*.rake'].each { |task| import task } 5 | -------------------------------------------------------------------------------- /spec/rcov.opts: -------------------------------------------------------------------------------- 1 | --exclude "spec,^/" 2 | --sort coverage 3 | --callsites 4 | --xrefs 5 | --profile 6 | --text-summary 7 | -------------------------------------------------------------------------------- /spec/spec.opts: -------------------------------------------------------------------------------- 1 | --colour 2 | --loadby random 3 | --require ./spec/lib/rspec_immediate_feedback_formatter.rb 4 | --format Spec::Runner::Formatter::ImmediateFeedbackFormatter 5 | --backtrace 6 | -------------------------------------------------------------------------------- /spec/support/core_ext/hash.rb: -------------------------------------------------------------------------------- 1 | class Hash 2 | def except(*keys) 3 | dup.except!(*keys) 4 | end 5 | 6 | def except!(*keys) 7 | keys.each { |key| delete(key) } 8 | self 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/dm-core/property/text.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | class Property 3 | class Text < String 4 | length 65535 5 | lazy true 6 | 7 | end # class Text 8 | end # class Property 9 | end # module DataMapper 10 | -------------------------------------------------------------------------------- /tasks/yard.rake: -------------------------------------------------------------------------------- 1 | begin 2 | require 'yard' 3 | 4 | YARD::Rake::YardocTask.new 5 | rescue LoadError 6 | task :yard do 7 | abort 'YARD is not available. In order to run yard, you must: gem install yard' 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/dm-core/core_ext/pathname.rb: -------------------------------------------------------------------------------- 1 | class Pathname 2 | # alias_method :to_s, :to to_str when to_str not defined 3 | unless public_instance_methods(false).any? { |m| m.to_sym == :to_str } 4 | alias_method :to_str, :to_s 5 | end 6 | end # class Pathname 7 | -------------------------------------------------------------------------------- /lib/dm-core/identity_map.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | 3 | # Tracks objects to help ensure that each object gets loaded only once. 4 | # See: http://www.martinfowler.com/eaaCatalog/identityMap.html 5 | class IdentityMap < Hash 6 | end # class IdentityMap 7 | end # module DataMapper 8 | -------------------------------------------------------------------------------- /lib/dm-core/property/date.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | class Property 3 | class Date < Object 4 | load_as ::Date 5 | dump_as ::Date 6 | coercion_method :to_date 7 | 8 | end # class Date 9 | end # class Property 10 | end # module DataMapper 11 | -------------------------------------------------------------------------------- /lib/dm-core/property/time.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | class Property 3 | class Time < Object 4 | load_as ::Time 5 | dump_as ::Time 6 | coercion_method :to_time 7 | 8 | end # class Time 9 | end # class Property 10 | end # module DataMapper 11 | -------------------------------------------------------------------------------- /spec/semipublic/shared/condition_shared_spec.rb: -------------------------------------------------------------------------------- 1 | share_examples_for 'A valid query condition' do 2 | before :all do 3 | raise "+@comp+ should be defined in before block" unless instance_variable_get(:@comp) 4 | end 5 | 6 | it 'should be valid' do 7 | @comp.should be_valid 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/dm-core/property/date_time.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | class Property 3 | class DateTime < Object 4 | load_as ::DateTime 5 | dump_as ::DateTime 6 | coercion_method :to_datetime 7 | 8 | end # class DateTime 9 | end # class Property 10 | end # module DataMapper 11 | -------------------------------------------------------------------------------- /lib/dm-core/support/ext/try_dup.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | module Ext 3 | def self.try_dup(value) 4 | case value 5 | when ::TrueClass, ::FalseClass, ::NilClass, ::Module, ::Numeric, ::Symbol 6 | value 7 | else 8 | value.dup 9 | end 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/ordered_set/shared/entries_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | shared_examples_for 'DataMapper::OrderedSet#entries with no entries' do 4 | it { should be_empty } 5 | end 6 | 7 | shared_examples_for 'DataMapper::OrderedSet#entries with entries' do 8 | it { should include(entry) } 9 | end 10 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/ordered_set/shared/empty_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | shared_examples_for 'DataMapper::OrderedSet#empty? with no entries in it' do 4 | it { should be(true) } 5 | end 6 | 7 | shared_examples_for 'DataMapper::OrderedSet#empty? with entries in it' do 8 | it { should be(false) } 9 | end 10 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/ordered_set/shared/clear_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | shared_examples_for 'DataMapper::OrderedSet#clear when no entries are present' do 4 | it { should be_empty } 5 | end 6 | 7 | shared_examples_for 'DataMapper::OrderedSet#clear when entries are present' do 8 | it { should be_empty } 9 | end 10 | -------------------------------------------------------------------------------- /lib/dm-core/property/serial.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | class Property 3 | class Serial < Integer 4 | serial true 5 | min 1 6 | 7 | # @api private 8 | def to_child_key 9 | Property::Integer 10 | end 11 | 12 | end # class Text 13 | end # module Property 14 | end # module DataMapper 15 | -------------------------------------------------------------------------------- /lib/dm-core/support/assertions.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | module Assertions 3 | def assert_kind_of(name, value, *klasses) 4 | klasses.each { |k| return if value.kind_of?(k) } 5 | raise ArgumentError, "+#{name}+ should be #{klasses.map { |k| k.name } * ' or '}, but was #{value.class.name}", caller(2) 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/ordered_set/shared/include_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | shared_examples_for 'DataMapper::OrderedSet#include? when the entry is present' do 4 | it { should be(true) } 5 | end 6 | 7 | shared_examples_for 'DataMapper::OrderedSet#include? when the entry is not present' do 8 | it { should be(false) } 9 | end 10 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/subject_set/shared/each_spec.rb: -------------------------------------------------------------------------------- 1 | require 'unit/data_mapper/ordered_set/shared/each_spec' 2 | 3 | shared_examples_for 'DataMapper::SubjectSet' do 4 | it_should_behave_like 'DataMapper::OrderedSet' 5 | end 6 | 7 | shared_examples_for 'DataMapper::SubjectSet#each' do 8 | it_should_behave_like 'DataMapper::OrderedSet#each' 9 | end 10 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/subject_set/shared/get_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | shared_examples_for 'DataMapper::SubjectSet#[] when the entry with the given name is not present' do 4 | it { should be_nil } 5 | end 6 | 7 | shared_examples_for 'DataMapper::SubjectSet#[] when the entry with the given name is present' do 8 | it { should == entry } 9 | end 10 | -------------------------------------------------------------------------------- /spec/semipublic/property/serial_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe DataMapper::Property::Serial do 4 | before :all do 5 | @name = :id 6 | @type = described_class 7 | @value = 1 8 | @other_value = 2 9 | @invalid_value = 'foo' 10 | end 11 | 12 | it_should_behave_like 'A semipublic Property' 13 | end 14 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/subject_set/shared/named_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | shared_examples_for 'DataMapper::SubjectSet#named? when no entry with the given name is present' do 4 | it { should be(false) } 5 | end 6 | 7 | shared_examples_for 'DataMapper::SubjectSet#named? when an entry with the given name is present' do 8 | it { should be(true) } 9 | end 10 | -------------------------------------------------------------------------------- /lib/dm-core/backwards.rb: -------------------------------------------------------------------------------- 1 | require "dm-core/support/deprecate" 2 | 3 | module DataMapper 4 | module Resource 5 | extend Deprecate 6 | 7 | deprecate :persisted_state, :persistence_state 8 | deprecate :persisted_state=, :persistence_state= 9 | deprecate :persisted_state?, :persistence_state? 10 | 11 | end # module Resource 12 | 13 | end # module DataMapper 14 | -------------------------------------------------------------------------------- /spec/semipublic/property/binary_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe DataMapper::Property::Binary do 4 | before :all do 5 | @name = :title 6 | @type = described_class 7 | @value = 'value' 8 | @other_value = 'return value' 9 | @invalid_value = 1 10 | end 11 | 12 | it_should_behave_like 'A semipublic Property' 13 | end 14 | -------------------------------------------------------------------------------- /spec/semipublic/property/string_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe DataMapper::Property::String do 4 | before :all do 5 | @name = :name 6 | @type = described_class 7 | @value = 'value' 8 | @other_value = 'return value' 9 | @invalid_value = 1 10 | end 11 | 12 | it_should_behave_like 'A semipublic Property' 13 | end 14 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/ordered_set/shared/to_ary_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | shared_examples_for 'DataMapper::OrderedSet#to_ary when no entries are present' do 4 | it { should be_empty } 5 | it { should == entries } 6 | end 7 | 8 | shared_examples_for 'DataMapper::OrderedSet#to_ary when entries are present' do 9 | it { should_not be_empty } 10 | it { should == entries } 11 | end 12 | -------------------------------------------------------------------------------- /lib/dm-core/support/local_object_space.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | module LocalObjectSpace 3 | def self.extended(klass) 4 | (class << klass; self; end).send :attr_accessor, :hook_scopes 5 | klass.hook_scopes = [] 6 | super 7 | end 8 | 9 | def object_by_id(object_id) 10 | self.hook_scopes.detect { |object| object.object_id == object_id } 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/support/properties/huge_integer.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | class Property 3 | class HugeInteger < DataMapper::Property::String 4 | def load(value) 5 | value.to_i unless value.nil? 6 | end 7 | 8 | def dump(value) 9 | value.to_s unless value.nil? 10 | end 11 | 12 | def typecast(value) 13 | load(value) 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## MAC OS 2 | .DS_Store 3 | 4 | ## TEXTMATE 5 | *.tmproj 6 | tmtags 7 | 8 | ## EMACS 9 | *~ 10 | \#* 11 | .\#* 12 | 13 | ## VIM 14 | *.swp 15 | 16 | ## Rubinius 17 | *.rbc 18 | 19 | ## PROJECT::GENERAL 20 | *.gem 21 | coverage 22 | rdoc 23 | pkg 24 | tmp 25 | doc 26 | log 27 | .yardoc 28 | measurements 29 | 30 | ## BUNDLER 31 | .bundle 32 | Gemfile.* 33 | 34 | ## PROJECT::SPECIFIC 35 | spec/db/ 36 | -------------------------------------------------------------------------------- /lib/dm-core/support/chainable.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | module Chainable 3 | 4 | # @api private 5 | def chainable(&block) 6 | mod = Module.new(&block) 7 | include mod 8 | mod 9 | end 10 | 11 | # @api private 12 | def extendable(&block) 13 | mod = Module.new(&block) 14 | extend mod 15 | mod 16 | end 17 | end # module Chainable 18 | end # module DataMapper 19 | -------------------------------------------------------------------------------- /lib/dm-core/property/float.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | class Property 3 | class Float < Numeric 4 | load_as ::Float 5 | dump_as ::Float 6 | coercion_method :to_float 7 | 8 | DEFAULT_PRECISION = 10 9 | DEFAULT_SCALE = nil 10 | 11 | precision(DEFAULT_PRECISION) 12 | scale(DEFAULT_SCALE) 13 | 14 | end # class Float 15 | end # class Property 16 | end # module DataMapper 17 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/subject_set/shared/entries_spec.rb: -------------------------------------------------------------------------------- 1 | require 'unit/data_mapper/ordered_set/shared/entries_spec' 2 | 3 | shared_examples_for 'DataMapper::SubjectSet#entries with no entries' do 4 | it_should_behave_like 'DataMapper::OrderedSet#entries with no entries' 5 | end 6 | 7 | shared_examples_for 'DataMapper::SubjectSet#entries with entries' do 8 | it_should_behave_like 'DataMapper::OrderedSet#entries with entries' 9 | end 10 | -------------------------------------------------------------------------------- /spec/semipublic/adapters/in_memory_adapter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'dm-core/spec/shared/adapter_spec' 3 | 4 | describe 'Adapter' do 5 | supported_by :in_memory do 6 | describe 'DataMapper::Adapters::InMemoryAdapter' do 7 | let(:adapter) { DataMapper::Spec.adapter } 8 | let(:repository) { DataMapper.repository(adapter.name) } 9 | 10 | it_should_behave_like 'An Adapter' 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/ordered_set/shared/index_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | shared_examples_for 'DataMapper::OrderedSet#index when the entry is not present' do 4 | it { should be(nil) } 5 | end 6 | 7 | shared_examples_for 'DataMapper::OrderedSet#index when 1 entry is present' do 8 | it { should == 0 } 9 | end 10 | 11 | shared_examples_for 'DataMapper::OrderedSet#index when 2 entries are present' do 12 | it { should == 1 } 13 | end 14 | -------------------------------------------------------------------------------- /lib/dm-core/core_ext/symbol.rb: -------------------------------------------------------------------------------- 1 | class Symbol 2 | (DataMapper::Query::Conditions::Comparison.slugs | [ :not, :asc, :desc ]).each do |sym| 3 | class_eval <<-RUBY, __FILE__, __LINE__ + 1 4 | def #{sym} 5 | #{"raise \"explicit use of '#{sym}' operator is deprecated (#{caller.first})\"" if sym == :eql || sym == :in} 6 | DataMapper::Query::Operator.new(self, #{sym.inspect}) 7 | end 8 | RUBY 9 | end 10 | end # class Symbol 11 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/ordered_set/shared/size_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | shared_examples_for 'DataMapper::OrderedSet#size when no entry is present' do 4 | it { should == 0 } 5 | end 6 | 7 | shared_examples_for 'DataMapper::OrderedSet#size when 1 entry is present' do 8 | it { should == 1 } 9 | end 10 | 11 | shared_examples_for 'DataMapper::OrderedSet#size when more than 1 entry is present' do 12 | it { should == expected_size } 13 | end 14 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/subject_set/shared/empty_spec.rb: -------------------------------------------------------------------------------- 1 | require 'unit/data_mapper/ordered_set/shared/empty_spec' 2 | 3 | shared_examples_for 'DataMapper::SubjectSet#empty? with no entries in it' do 4 | it_should_behave_like 'DataMapper::OrderedSet#empty? with no entries in it' 5 | end 6 | 7 | shared_examples_for 'DataMapper::SubjectSet#empty? with entries in it' do 8 | it_should_behave_like 'DataMapper::OrderedSet#empty? with entries in it' 9 | end 10 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/ordered_set/hash_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'dm-core/support/ordered_set' 3 | 4 | describe 'DataMapper::OrderedSet#hash' do 5 | subject { ordered_set.hash } 6 | 7 | let(:entry) { 1 } 8 | let(:ordered_set) { DataMapper::OrderedSet.new([ entry ]) } 9 | 10 | it { should be_kind_of(Integer) } 11 | it { should == ordered_set.class.hash ^ ordered_set.entries.hash } 12 | end 13 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/subject_set/shared/clear_spec.rb: -------------------------------------------------------------------------------- 1 | require 'unit/data_mapper/ordered_set/shared/clear_spec' 2 | 3 | shared_examples_for 'DataMapper::SubjectSet#clear when no entries are present' do 4 | it_should_behave_like 'DataMapper::OrderedSet#clear when no entries are present' 5 | end 6 | 7 | shared_examples_for 'DataMapper::SubjectSet#clear when entries are present' do 8 | it_should_behave_like 'DataMapper::OrderedSet#clear when entries are present' 9 | end 10 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/subject_set/shared/to_ary_spec.rb: -------------------------------------------------------------------------------- 1 | require 'unit/data_mapper/ordered_set/shared/to_ary_spec' 2 | 3 | shared_examples_for 'DataMapper::SubjectSet#to_ary when no entries are present' do 4 | it_should_behave_like 'DataMapper::OrderedSet#to_ary when no entries are present' 5 | end 6 | 7 | shared_examples_for 'DataMapper::SubjectSet#to_ary when entries are present' do 8 | it_should_behave_like 'DataMapper::OrderedSet#to_ary when entries are present' 9 | end 10 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/subject_set/shared/include_spec.rb: -------------------------------------------------------------------------------- 1 | require 'unit/data_mapper/ordered_set/shared/include_spec' 2 | 3 | shared_examples_for 'DataMapper::SubjectSet#include? when the entry is present' do 4 | it_should_behave_like 'DataMapper::OrderedSet#include? when the entry is present' 5 | end 6 | 7 | shared_examples_for 'DataMapper::SubjectSet#include? when the entry is not present' do 8 | it_should_behave_like 'DataMapper::OrderedSet#include? when the entry is not present' 9 | end 10 | -------------------------------------------------------------------------------- /lib/dm-core/property/class.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | class Property 3 | class Class < Object 4 | load_as ::Class 5 | dump_as ::Class 6 | coercion_method :to_constant 7 | 8 | # @api semipublic 9 | def typecast(value) 10 | DataMapper::Ext::Module.find_const(model, value.to_s) unless value.nil? 11 | rescue NameError 12 | value 13 | end 14 | 15 | end # class Class 16 | end # class Property 17 | end # module DataMapper 18 | -------------------------------------------------------------------------------- /lib/dm-core/support/deprecate.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | module Deprecate 3 | def deprecate(old_method, new_method) 4 | class_eval <<-RUBY, __FILE__, __LINE__ + 1 5 | def #{old_method}(*args, &block) 6 | warn "\#{self.class}##{old_method} is deprecated, use \#{self.class}##{new_method} instead (\#{caller.first})" 7 | send(#{new_method.inspect}, *args, &block) 8 | end 9 | RUBY 10 | end 11 | end # module Deprecate 12 | end # module DataMapper 13 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/ordered_set/shared/each_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | shared_examples_for 'DataMapper::OrderedSet' do 4 | it { should be_kind_of(Enumerable) } 5 | 6 | it 'case matches Enumerable' do 7 | (Enumerable === subject).should be(true) 8 | end 9 | end 10 | 11 | shared_examples_for 'DataMapper::OrderedSet#each' do 12 | it { should equal(set) } 13 | 14 | it 'yields each column' do 15 | expect { subject }.to change { yields.dup }.from([]).to([ entry ]) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/dm-core/core_ext/kernel.rb: -------------------------------------------------------------------------------- 1 | module Kernel 2 | 3 | # Returns the object's singleton class. 4 | # 5 | # @return [Class] 6 | # 7 | # @api private 8 | def singleton_class 9 | class << self 10 | self 11 | end 12 | end unless respond_to?(:singleton_class) # exists in 1.9.2 13 | 14 | private 15 | 16 | # Delegates to DataMapper.repository() 17 | # 18 | # @api public 19 | def repository(*args, &block) 20 | DataMapper.repository(*args, &block) 21 | end 22 | 23 | end # module Kernel 24 | -------------------------------------------------------------------------------- /lib/dm-core/property/binary.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | class Property 3 | class Binary < String 4 | 5 | if RUBY_VERSION >= "1.9" 6 | 7 | def load(value) 8 | super.dup.force_encoding("BINARY") unless value.nil? 9 | end 10 | 11 | def dump(value) 12 | value.dup.force_encoding("BINARY") unless value.nil? 13 | rescue 14 | value 15 | end 16 | 17 | end 18 | 19 | end # class Binary 20 | end # class Property 21 | end # module DataMapper 22 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/subject_set/shared/delete_spec.rb: -------------------------------------------------------------------------------- 1 | require 'unit/data_mapper/ordered_set/shared/delete_spec' 2 | 3 | shared_examples_for 'DataMapper::SubjectSet#delete when deleting an already included entry' do 4 | it_should_behave_like 'DataMapper::OrderedSet#delete when deleting an already included entry' 5 | end 6 | 7 | shared_examples_for 'DataMapper::SubjectSet#delete when deleting a not yet included entry' do 8 | it_should_behave_like 'DataMapper::OrderedSet#delete when deleting a not yet included entry' 9 | end 10 | -------------------------------------------------------------------------------- /lib/dm-core/property/boolean.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | class Property 3 | class Boolean < Object 4 | load_as ::TrueClass 5 | dump_as ::TrueClass 6 | coercion_method :to_boolean 7 | 8 | # @api semipublic 9 | def value_dumped?(value) 10 | value_loaded?(value) 11 | end 12 | 13 | # @api semipublic 14 | def value_loaded?(value) 15 | value == true || value == false 16 | end 17 | 18 | end # class Boolean 19 | end # class Property 20 | end # module DataMapper 21 | -------------------------------------------------------------------------------- /spec/semipublic/property/discriminator_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe DataMapper::Property::Discriminator do 4 | before :all do 5 | Object.send(:remove_const, :Foo) if defined?(Foo) 6 | Object.send(:remove_const, :Bar) if defined?(Bar) 7 | 8 | class ::Foo; end 9 | class ::Bar; end 10 | 11 | @name = :type 12 | @type = described_class 13 | @value = Foo 14 | @other_value = Bar 15 | @invalid_value = 1 16 | end 17 | 18 | it_should_behave_like 'A semipublic Property' 19 | end 20 | -------------------------------------------------------------------------------- /spec/unit/inflections_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'dm-core/support/inflector/inflections' 3 | 4 | describe DataMapper::Inflector do 5 | 6 | it "should singularize 'status' correctly" do 7 | DataMapper::Inflector.singularize('status').should eql 'status' 8 | DataMapper::Inflector.singularize('status').should_not eql 'statu' 9 | end 10 | 11 | it "should singularize 'alias' correctly" do 12 | DataMapper::Inflector.singularize('alias').should eql 'alias' 13 | DataMapper::Inflector.singularize('alias').should_not eql 'alia' 14 | end 15 | 16 | end 17 | -------------------------------------------------------------------------------- /tasks/yardstick.rake: -------------------------------------------------------------------------------- 1 | begin 2 | require 'pathname' 3 | require 'yardstick/rake/measurement' 4 | require 'yardstick/rake/verify' 5 | 6 | # yardstick_measure task 7 | Yardstick::Rake::Measurement.new 8 | 9 | # verify_measurements task 10 | Yardstick::Rake::Verify.new do |verify| 11 | verify.threshold = 100 12 | end 13 | rescue LoadError 14 | %w[ yardstick_measure verify_measurements ].each do |name| 15 | task name.to_s do 16 | abort "Yardstick is not available. In order to run #{name}, you must: gem install yardstick" 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/dm-core/spec/lib/collection_helpers.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | module Spec 3 | module CollectionHelpers 4 | module GroupMethods 5 | def self.extended(base) 6 | base.class_inheritable_accessor :loaded 7 | base.loaded = false 8 | super 9 | end 10 | 11 | def should_not_be_a_kicker(ivar = :@articles) 12 | unless loaded 13 | it 'should not be a kicker' do 14 | instance_variable_get(ivar).should_not be_loaded 15 | end 16 | end 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/semipublic/resource_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe DataMapper::Resource do 4 | before :all do 5 | class ::User 6 | include DataMapper::Resource 7 | 8 | property :name, String, :key => true 9 | property :age, Integer 10 | property :description, Text 11 | end 12 | 13 | @user_model = User 14 | end 15 | 16 | supported_by :all do 17 | before :all do 18 | @user = @user_model.create(:name => 'dbussink', :age => 25, :description => "Test") 19 | end 20 | 21 | it_should_behave_like 'A semipublic Resource' 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/public/property/integer_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe DataMapper::Property::Integer do 4 | before :all do 5 | @name = :age 6 | @type = described_class 7 | @load_as = Integer 8 | @value = 1 9 | @other_value = 2 10 | @invalid_value = '1' 11 | end 12 | 13 | it_should_behave_like 'A public Property' 14 | 15 | describe '.options' do 16 | subject { described_class.options } 17 | 18 | it { should be_kind_of(Hash) } 19 | 20 | it { should eql(:load_as => @load_as, :dump_as => @load_as, :coercion_method => :to_integer) } 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/public/property/boolean_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe DataMapper::Property::Boolean do 4 | before :all do 5 | @name = :active 6 | @type = described_class 7 | @load_as = TrueClass 8 | @value = true 9 | @other_value = false 10 | @invalid_value = 1 11 | end 12 | 13 | it_should_behave_like 'A public Property' 14 | 15 | describe '.options' do 16 | subject { described_class.options } 17 | 18 | it { should be_kind_of(Hash) } 19 | 20 | it { should eql(:load_as => @load_as, :dump_as => @load_as, :coercion_method => :to_boolean) } 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/public/property/date_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe DataMapper::Property::Date do 4 | before :all do 5 | @name = :created_on 6 | @type = described_class 7 | @load_as = Date 8 | @value = Date.today 9 | @other_value = Date.today + 1 10 | @invalid_value = 1 11 | end 12 | 13 | it_should_behave_like 'A public Property' 14 | 15 | describe '.options' do 16 | subject { described_class.options } 17 | 18 | it { should be_kind_of(Hash) } 19 | 20 | it { should eql(:load_as => @load_as, :dump_as => @load_as, :coercion_method => :to_date) } 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/public/property/time_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe DataMapper::Property::Time do 4 | before :all do 5 | @name = :deleted_at 6 | @type = described_class 7 | @load_as = Time 8 | @value = Time.now 9 | @other_value = Time.now + 15 10 | @invalid_value = 1 11 | end 12 | 13 | it_should_behave_like 'A public Property' 14 | 15 | describe '.options' do 16 | subject { described_class.options } 17 | 18 | it { should be_kind_of(Hash) } 19 | 20 | it { should eql(:load_as => @load_as, :dump_as => @load_as, :coercion_method => :to_time) } 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/subject_set/shared/size_spec.rb: -------------------------------------------------------------------------------- 1 | require 'unit/data_mapper/ordered_set/shared/size_spec' 2 | 3 | shared_examples_for 'DataMapper::SubjectSet#size when no entry is present' do 4 | it_should_behave_like 'DataMapper::OrderedSet#size when no entry is present' 5 | end 6 | 7 | shared_examples_for 'DataMapper::SubjectSet#size when 1 entry is present' do 8 | it_should_behave_like 'DataMapper::OrderedSet#size when 1 entry is present' 9 | end 10 | 11 | shared_examples_for 'DataMapper::SubjectSet#size when more than 1 entry is present' do 12 | it_should_behave_like 'DataMapper::OrderedSet#size when more than 1 entry is present' 13 | end 14 | -------------------------------------------------------------------------------- /spec/public/property/serial_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe DataMapper::Property::Serial do 4 | before :all do 5 | @name = :id 6 | @type = described_class 7 | @load_as = Integer 8 | @value = 1 9 | @other_value = 2 10 | @invalid_value = 'foo' 11 | end 12 | 13 | it_should_behave_like 'A public Property' 14 | 15 | describe '.options' do 16 | subject { described_class.options } 17 | 18 | it { should be_kind_of(Hash) } 19 | 20 | it { should eql(:load_as => @load_as, :dump_as => @load_as, :coercion_method => :to_integer, :min => 1, :serial => true) } 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/public/property/string_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe DataMapper::Property::String do 4 | before :all do 5 | @name = :name 6 | @type = described_class 7 | @load_as = String 8 | @value = 'value' 9 | @other_value = 'return value' 10 | @invalid_value = 1 11 | end 12 | 13 | it_should_behave_like 'A public Property' 14 | 15 | describe '.options' do 16 | subject { described_class.options } 17 | 18 | it { should be_kind_of(Hash) } 19 | 20 | it { should eql(:load_as => @load_as, :dump_as => @load_as, :coercion_method => :to_string, :length => 50) } 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/public/property/float_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe DataMapper::Property::Float do 4 | before :all do 5 | @name = :rating 6 | @type = described_class 7 | @load_as = Float 8 | @value = 0.1 9 | @other_value = 0.2 10 | @invalid_value = '1' 11 | end 12 | 13 | it_should_behave_like 'A public Property' 14 | 15 | describe '.options' do 16 | subject { described_class.options } 17 | 18 | it { should be_kind_of(Hash) } 19 | 20 | it { should eql(:load_as => @load_as, :dump_as => @load_as, :coercion_method => :to_float, :precision => 10, :scale => nil) } 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/unit/array_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'dm-core/support/ext/array' 3 | require 'dm-core/support/mash' 4 | 5 | describe DataMapper::Ext::Array do 6 | before :all do 7 | @array = [ [ :a, [ 1 ] ], [ :b, [ 2 ] ], [ :c, [ 3 ] ] ].freeze 8 | end 9 | 10 | describe '.to_mash' do 11 | before :all do 12 | @return = DataMapper::Ext::Array.to_mash(@array) 13 | end 14 | 15 | it 'should return a Mash' do 16 | @return.should be_kind_of(DataMapper::Mash) 17 | end 18 | 19 | it 'should return expected value' do 20 | @return.should == { 'a' => [ 1 ], 'b' => [ 2 ], 'c' => [ 3 ] } 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/public/property/date_time_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe DataMapper::Property::DateTime do 4 | before :all do 5 | @name = :created_at 6 | @type = described_class 7 | @load_as = DateTime 8 | @value = DateTime.now 9 | @other_value = DateTime.now + 15 10 | @invalid_value = 1 11 | end 12 | 13 | it_should_behave_like 'A public Property' 14 | 15 | describe '.options' do 16 | subject { described_class.options } 17 | 18 | it { should be_kind_of(Hash) } 19 | 20 | it { should eql(:load_as => @load_as, :dump_as => @load_as, :coercion_method => :to_datetime) } 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/dm-core/property/integer.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | class Property 3 | class Integer < Numeric 4 | load_as ::Integer 5 | dump_as ::Integer 6 | coercion_method :to_integer 7 | 8 | accept_options :serial 9 | 10 | protected 11 | 12 | # @api semipublic 13 | def initialize(model, name, options = {}) 14 | if options.key?(:serial) && !kind_of?(Serial) 15 | raise "Integer #{name} with explicit :serial option is deprecated, use Serial instead (#{caller[2]})" 16 | end 17 | super 18 | end 19 | 20 | end # class Integer 21 | end # class Property 22 | end # module DataMapper 23 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/ordered_set/each_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'dm-core/support/ordered_set' 3 | require 'unit/data_mapper/ordered_set/shared/each_spec' 4 | 5 | describe 'DataMapper::OrderedSet' do 6 | subject { DataMapper::OrderedSet.new } 7 | 8 | it_should_behave_like 'DataMapper::OrderedSet' 9 | end 10 | 11 | describe 'DataMapper::OrderedSet#each' do 12 | subject { set.each { |entry| yields << entry } } 13 | 14 | let(:set) { DataMapper::OrderedSet.new([ entry ]) } 15 | let(:entry) { 1 } 16 | let(:yields) { [] } 17 | 18 | it_should_behave_like 'DataMapper::OrderedSet#each' 19 | end 20 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/ordered_set/empty_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'dm-core/support/ordered_set' 3 | require 'unit/data_mapper/ordered_set/shared/empty_spec' 4 | 5 | describe 'DataMapper::OrderedSet#empty?' do 6 | subject { set.empty? } 7 | 8 | context 'with no entries in it' do 9 | let(:set) { DataMapper::OrderedSet.new } 10 | 11 | it_should_behave_like 'DataMapper::OrderedSet#empty? with no entries in it' 12 | end 13 | 14 | context 'with entries in it' do 15 | let(:set) { DataMapper::OrderedSet.new([ entry ]) } 16 | let(:entry) { 1 } 17 | 18 | it_should_behave_like 'DataMapper::OrderedSet#empty? with entries in it' 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/dm-core/support/ext/array.rb: -------------------------------------------------------------------------------- 1 | module DataMapper; module Ext 2 | module Array 3 | # Transforms an Array of key/value pairs into a {Mash}. 4 | # 5 | # This is a better idiom than using Mash[*array.flatten] in Ruby 1.8.6 6 | # because it is not possible to limit the flattening to a single 7 | # level. 8 | # 9 | # @param [Array] array 10 | # The array of key/value pairs to transform. 11 | # 12 | # @return [Mash] 13 | # A {Mash} where each entry in the Array is turned into a key/value. 14 | # 15 | # @api semipublic 16 | def self.to_mash(array) 17 | m = Mash.new 18 | array.each { |k,v| m[k] = v } 19 | m 20 | end 21 | end # class Array 22 | end; end 23 | -------------------------------------------------------------------------------- /lib/dm-core/property/invalid_value_error.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | class Property 3 | # Exception raised when DataMapper is about to work with 4 | # invalid property values. 5 | class InvalidValueError < StandardError 6 | attr_reader :property, :value 7 | 8 | def initialize(property, value) 9 | msg = "Invalid value %s for property %s (%s) on model %s" % 10 | [ value.inspect, 11 | property.name.inspect, 12 | property.class.name, 13 | property.model.name 14 | ] 15 | super(msg) 16 | @property = property 17 | @value = value 18 | end 19 | 20 | end # class InvalidValueError 21 | end # class Property 22 | end # module DataMapper 23 | -------------------------------------------------------------------------------- /lib/dm-core/property/lookup.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | class Property 3 | module Lookup 4 | 5 | # Provides transparent access to the Properties defined in 6 | # {Property}. 7 | # 8 | # @param [Symbol] name 9 | # The name of the property to lookup. 10 | # 11 | # @return [Property] 12 | # The property with the given name. 13 | # 14 | # @raise [NameError] 15 | # The property could not be found. 16 | # 17 | # @api private 18 | # 19 | # @since 1.0.1 20 | # 21 | def const_missing(name) 22 | Property.find_class(name.to_s) || super 23 | end 24 | 25 | end # module Lookup 26 | end # class Property 27 | end # module DataMapper 28 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/ordered_set/entries_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'dm-core/support/ordered_set' 3 | require 'unit/data_mapper/ordered_set/shared/entries_spec' 4 | 5 | describe 'DataMapper::OrderedSet#entries' do 6 | subject { ordered_set.entries } 7 | 8 | let(:ordered_set) { set } 9 | 10 | context 'with no entries' do 11 | let(:set) { DataMapper::OrderedSet.new } 12 | 13 | it_should_behave_like 'DataMapper::OrderedSet#entries with no entries' 14 | end 15 | 16 | context 'with entries' do 17 | let(:set) { DataMapper::OrderedSet.new([ entry ]) } 18 | let(:entry) { 1 } 19 | 20 | it_should_behave_like 'DataMapper::OrderedSet#entries with entries' 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/public/property/decimal_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe DataMapper::Property::Decimal do 4 | before :all do 5 | @name = :rate 6 | @type = described_class 7 | @options = { :precision => 5, :scale => 2 } 8 | @load_as = BigDecimal 9 | @value = BigDecimal('1.0') 10 | @other_value = BigDecimal('2.0') 11 | @invalid_value = true 12 | end 13 | 14 | it_should_behave_like 'A public Property' 15 | 16 | describe '.options' do 17 | subject { described_class.options } 18 | 19 | it { should be_kind_of(Hash) } 20 | 21 | it { should eql(:load_as => @load_as, :dump_as => @load_as, :coercion_method => :to_decimal, :precision => 10, :scale => 0) } 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/dm-core/support/ext/blank.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | module Ext 3 | # Determines whether the specified +value+ is blank. 4 | # 5 | # An object is blank if it's false, empty, or a whitespace string. 6 | # For example, "", " ", +nil+, [], and {} are blank. 7 | # 8 | # @api semipublic 9 | def self.blank?(value) 10 | return value.blank? if value.respond_to?(:blank?) 11 | case value 12 | when ::NilClass, ::FalseClass 13 | true 14 | when ::TrueClass, ::Numeric 15 | false 16 | when ::Array, ::Hash 17 | value.empty? 18 | when ::String 19 | value !~ /\S/ 20 | else 21 | value.nil? || (value.respond_to?(:empty?) && value.empty?) 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/ordered_set/clear_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'dm-core/support/ordered_set' 3 | require 'unit/data_mapper/ordered_set/shared/clear_spec' 4 | 5 | describe 'DataMapper::OrderedSet#clear' do 6 | subject { ordered_set.clear } 7 | 8 | let(:ordered_set) { DataMapper::OrderedSet.new(entries) } 9 | 10 | let(:entry1) { 1 } 11 | let(:entry2) { 2 } 12 | 13 | context 'when no entries are present' do 14 | let(:entries) { [] } 15 | 16 | it_should_behave_like 'DataMapper::OrderedSet#clear when no entries are present' 17 | end 18 | 19 | context 'when entries are present' do 20 | let(:entries) { [ entry1, entry2 ] } 21 | 22 | it_should_behave_like 'DataMapper::OrderedSet#clear when entries are present' 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/ordered_set/to_ary_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'dm-core/support/ordered_set' 3 | require 'unit/data_mapper/ordered_set/shared/to_ary_spec' 4 | 5 | describe 'DataMapper::OrderedSet#to_ary' do 6 | subject { ordered_set.to_ary } 7 | 8 | let(:ordered_set) { DataMapper::OrderedSet.new(entries) } 9 | let(:entry1) { 1 } 10 | let(:entry2) { 2 } 11 | 12 | context 'when no entries are present' do 13 | let(:entries) { [] } 14 | 15 | it_should_behave_like 'DataMapper::OrderedSet#to_ary when no entries are present' 16 | end 17 | 18 | context 'when entries are present' do 19 | let(:entries) { [ entry1, entry2 ] } 20 | 21 | it_should_behave_like 'DataMapper::OrderedSet#to_ary when entries are present' 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/public/property/class_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe DataMapper::Property::Class do 4 | before :all do 5 | Object.send(:remove_const, :Foo) if defined?(Foo) 6 | Object.send(:remove_const, :Bar) if defined?(Bar) 7 | 8 | class ::Foo; end 9 | class ::Bar; end 10 | 11 | @name = :type 12 | @type = described_class 13 | @load_as = Class 14 | @value = Foo 15 | @other_value = Bar 16 | @invalid_value = 1 17 | end 18 | 19 | it_should_behave_like 'A public Property' 20 | 21 | describe '.options' do 22 | subject { described_class.options } 23 | 24 | it { should be_kind_of(Hash) } 25 | 26 | it { should eql(:load_as => @load_as, :dump_as => @load_as, :coercion_method => :to_constant) } 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/dm-core/resource/persistence_state/deleted.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | module Resource 3 | class PersistenceState 4 | 5 | # a persisted/deleted resource 6 | class Deleted < Persisted 7 | def set(subject, value) 8 | raise ImmutableDeletedError, 'Deleted resource cannot be modified' 9 | end 10 | 11 | def delete 12 | self 13 | end 14 | 15 | def commit 16 | delete_resource 17 | remove_from_identity_map 18 | Immutable.new(resource) 19 | end 20 | 21 | private 22 | 23 | def delete_resource 24 | repository.delete(collection_for_self) 25 | end 26 | 27 | end # class Deleted 28 | end # class PersistenceState 29 | end # module Resource 30 | end # module DataMapper 31 | -------------------------------------------------------------------------------- /lib/dm-core/resource/persistence_state/persisted.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | module Resource 3 | class PersistenceState 4 | 5 | # a persisted resource (abstract) 6 | class Persisted < PersistenceState 7 | def get(subject, *args) 8 | lazy_load(subject) 9 | super 10 | end 11 | 12 | private 13 | 14 | def repository 15 | @repository ||= resource.instance_variable_get(:@_repository) 16 | end 17 | 18 | def collection_for_self 19 | @collection_for_self ||= resource.collection_for_self 20 | end 21 | 22 | def lazy_load(subject) 23 | subject.lazy_load(resource) 24 | end 25 | 26 | end # class Persisted 27 | end # class PersistenceState 28 | end # module Resource 29 | end # module DataMapper 30 | -------------------------------------------------------------------------------- /spec/semipublic/property/text_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe DataMapper::Property::Text do 4 | before :all do 5 | @name = :title 6 | @type = described_class 7 | @value = 'value' 8 | @other_value = 'return value' 9 | @invalid_value = 1 10 | end 11 | 12 | it_should_behave_like 'A semipublic Property' 13 | 14 | describe '#load' do 15 | before :all do 16 | @value = mock('value') 17 | end 18 | 19 | subject { @property.load(@value) } 20 | 21 | before do 22 | @property = @type.new(@model, @name) 23 | end 24 | 25 | it 'should delegate to #type.load' do 26 | return_value = mock('return value') 27 | @property.should_receive(:load).with(@value).and_return(return_value) 28 | should == return_value 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/ordered_set/include_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'dm-core/support/ordered_set' 3 | require 'unit/data_mapper/ordered_set/shared/include_spec' 4 | 5 | describe 'DataMapper::OrderedSet#include?' do 6 | subject { ordered_set.include?(entry) } 7 | 8 | let(:ordered_set) { set } 9 | 10 | context 'when the entry is present' do 11 | let(:set) { DataMapper::OrderedSet.new([ entry ]) } 12 | let(:entry) { 1 } 13 | 14 | it_should_behave_like 'DataMapper::OrderedSet#include? when the entry is present' 15 | end 16 | 17 | context 'when the entry is not present' do 18 | let(:set) { DataMapper::OrderedSet.new } 19 | let(:entry) { 1 } 20 | 21 | it_should_behave_like 'DataMapper::OrderedSet#include? when the entry is not present' 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/subject_set/each_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'dm-core/support/subject_set' 3 | require 'unit/data_mapper/subject_set/shared/each_spec' 4 | 5 | describe 'DataMapper::SubjectSet' do 6 | subject { DataMapper::SubjectSet.new } 7 | 8 | it_should_behave_like 'DataMapper::SubjectSet' 9 | end 10 | 11 | describe 'DataMapper::SubjectSet#each' do 12 | before :all do 13 | 14 | class ::Person 15 | attr_reader :name 16 | def initialize(name) 17 | @name = name 18 | end 19 | end 20 | 21 | end 22 | 23 | subject { set.each { |entry| yields << entry } } 24 | 25 | let(:set) { DataMapper::SubjectSet.new([ entry ]) } 26 | let(:entry) { Person.new('Alice') } 27 | let(:yields) { [] } 28 | 29 | it_should_behave_like 'DataMapper::SubjectSet#each' 30 | end 31 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/ordered_set/append_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'dm-core/support/ordered_set' 3 | require 'unit/data_mapper/ordered_set/shared/append_spec' 4 | 5 | describe 'DataMapper::OrderedSet#<<' do 6 | subject { set << entry2 } 7 | 8 | let(:set) { DataMapper::OrderedSet.new([ entry1 ]) } 9 | let(:entry1) { Object.new } 10 | 11 | before do 12 | @old_index = set.index(entry1) 13 | end 14 | 15 | context 'when appending a not yet included entry' do 16 | let(:entry2) { Object.new } 17 | 18 | it_should_behave_like 'DataMapper::OrderedSet#<< when appending a not yet included entry' 19 | end 20 | 21 | context 'when updating an already included entry' do 22 | let(:entry2) { entry1 } 23 | 24 | it_should_behave_like 'DataMapper::OrderedSet#<< when updating an already included entry' 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/subject_set/entries_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'dm-core/support/subject_set' 3 | require 'unit/data_mapper/subject_set/shared/entries_spec' 4 | 5 | describe 'DataMapper::SubjectSet#entries' do 6 | before :all do 7 | 8 | class ::Person 9 | attr_reader :name 10 | def initialize(name) 11 | @name = name 12 | end 13 | end 14 | 15 | end 16 | 17 | subject { set.entries } 18 | 19 | context 'with no entries' do 20 | let(:set) { DataMapper::SubjectSet.new } 21 | 22 | it_should_behave_like 'DataMapper::SubjectSet#entries with no entries' 23 | end 24 | 25 | context 'with entries' do 26 | let(:set) { DataMapper::SubjectSet.new([ entry ]) } 27 | let(:entry) { Person.new('Alice') } 28 | 29 | it_should_behave_like 'DataMapper::SubjectSet#entries with entries' 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/dm-core/property/object.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | class Property 3 | class Object < Property 4 | load_as ::Object 5 | dump_as ::Object 6 | coercion_method :to_object 7 | 8 | # @api semipublic 9 | def dump(value) 10 | instance_of?(Object) ? marshal(value) : value 11 | end 12 | 13 | # @api semipublic 14 | def load(value) 15 | typecast(instance_of?(Object) ? unmarshal(value) : value) 16 | end 17 | 18 | # @api semipublic 19 | def marshal(value) 20 | [ Marshal.dump(value) ].pack('m') unless value.nil? 21 | end 22 | 23 | # @api semipublic 24 | def unmarshal(value) 25 | Marshal.load(value.unpack('m').first) unless value.nil? 26 | end 27 | 28 | # @api private 29 | def to_child_key 30 | self.class 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/subject_set/empty_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'dm-core/support/subject_set' 3 | require 'unit/data_mapper/subject_set/shared/empty_spec' 4 | 5 | describe 'DataMapper::SubjectSet#empty?' do 6 | before :all do 7 | 8 | class ::Person 9 | attr_reader :name 10 | def initialize(name) 11 | @name = name 12 | end 13 | end 14 | 15 | end 16 | 17 | subject { set.empty? } 18 | 19 | context 'with no entries in it' do 20 | let(:set) { DataMapper::SubjectSet.new } 21 | 22 | it_should_behave_like 'DataMapper::SubjectSet#empty? with no entries in it' 23 | end 24 | 25 | context 'with entries in it' do 26 | let(:set) { DataMapper::SubjectSet.new([ entry ]) } 27 | let(:entry) { Person.new('Alice') } 28 | 29 | it_should_behave_like 'DataMapper::SubjectSet#empty? with entries in it' 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/dm-core/support/ext/string.rb: -------------------------------------------------------------------------------- 1 | module DataMapper; module Ext 2 | module String 3 | # Replace sequences of whitespace (including newlines) with either 4 | # a single space or remove them entirely (according to param _spaced_). 5 | # 6 | # compress_lines(< "SELECT name FROM users" 10 | # 11 | # @param [String] string 12 | # The input string. 13 | # 14 | # @param [TrueClass, FalseClass] spaced (default=true) 15 | # Determines whether returned string has whitespace collapsed or removed. 16 | # 17 | # @return [String] The input string with whitespace (including newlines) replaced. 18 | # 19 | # @api semipublic 20 | def self.compress_lines(string, spaced = true) 21 | string.split($/).map { |line| line.strip }.join(spaced ? ' ' : '') 22 | end 23 | end 24 | end; end 25 | -------------------------------------------------------------------------------- /spec/unit/hash_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'dm-core/support/ext/hash' 3 | require 'dm-core/support/mash' 4 | 5 | describe DataMapper::Ext::Hash, "only" do 6 | before do 7 | @hash = { :one => 'ONE', 'two' => 'TWO', 3 => 'THREE', 4 => nil } 8 | end 9 | 10 | it "should return a hash with only the given key(s)" do 11 | DataMapper::Ext::Hash.only(@hash, :not_in_there).should == {} 12 | DataMapper::Ext::Hash.only(@hash, 4).should == {4 => nil} 13 | DataMapper::Ext::Hash.only(@hash, :one).should == { :one => 'ONE' } 14 | DataMapper::Ext::Hash.only(@hash, :one, 3).should == { :one => 'ONE', 3 => 'THREE' } 15 | end 16 | end 17 | 18 | 19 | describe Hash, 'to_mash' do 20 | before do 21 | @hash = Hash.new(10) 22 | end 23 | 24 | it "copies default Hash value to Mash" do 25 | @mash = DataMapper::Ext::Hash.to_mash(@hash) 26 | @mash[:merb].should == 10 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/subject_set/include_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'dm-core/support/subject_set' 3 | require 'unit/data_mapper/subject_set/shared/include_spec' 4 | 5 | describe 'DataMapper::SubjectSet#include?' do 6 | before :all do 7 | 8 | class ::Person 9 | attr_reader :name 10 | def initialize(name) 11 | @name = name 12 | end 13 | end 14 | 15 | end 16 | 17 | subject { set.include?(entry) } 18 | 19 | let(:entry) { Person.new('Alice') } 20 | 21 | context 'when the entry is present' do 22 | let(:set) { DataMapper::SubjectSet.new([ entry ]) } 23 | 24 | it_should_behave_like 'DataMapper::SubjectSet#include? when the entry is present' 25 | end 26 | 27 | context 'when the entry is not present' do 28 | let(:set) { DataMapper::SubjectSet.new } 29 | 30 | it_should_behave_like 'DataMapper::SubjectSet#include? when the entry is not present' 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/ordered_set/shared/append_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | shared_examples_for 'DataMapper::OrderedSet#<< when appending a not yet included entry' do 4 | its(:size ) { should == 2 } 5 | its(:entries) { should include(entry1) } 6 | its(:entries) { should include(entry2) } 7 | 8 | it 'should not alter the position of the existing entry' do 9 | subject.entries.index(entry1).should == @old_index 10 | end 11 | 12 | it 'should append columns at the end of the set' do 13 | subject.entries.index(entry2).should == @old_index + 1 14 | end 15 | end 16 | 17 | shared_examples_for 'DataMapper::OrderedSet#<< when updating an already included entry' do 18 | its(:size ) { should == 1 } 19 | its(:entries) { should include(entry2) } 20 | 21 | it 'should not alter the position of the existing entry' do 22 | subject.entries.index(entry2).should == @old_index 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/semipublic/property/lookup_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'dm-core/property/lookup' 3 | 4 | describe DataMapper::Property::Lookup do 5 | supported_by :all do 6 | before :all do 7 | Object.send(:remove_const, :Foo) if defined?(Foo) 8 | @klass = Class.new { extend DataMapper::Model } 9 | 10 | module Foo 11 | class OtherProperty < DataMapper::Property::String; end 12 | end 13 | end 14 | 15 | it 'should provide access to Property classes' do 16 | @klass::Serial.should == DataMapper::Property::Serial 17 | end 18 | 19 | it 'should provide access to Property classes from outside of the Property namespace' do 20 | @klass::OtherProperty.should be(Foo::OtherProperty) 21 | end 22 | 23 | it 'should not provide access to unknown Property classes' do 24 | lambda { 25 | @klass::Bla 26 | }.should raise_error(NameError) 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/subject_set/shared/append_spec.rb: -------------------------------------------------------------------------------- 1 | require 'unit/data_mapper/ordered_set/shared/append_spec' 2 | 3 | shared_examples_for 'DataMapper::SubjectSet#<< when appending a not yet included entry' do 4 | it_should_behave_like 'DataMapper::OrderedSet#<< when appending a not yet included entry' 5 | end 6 | 7 | shared_examples_for 'DataMapper::SubjectSet#<< when updating an entry with the same cache key and the new entry is already included' do 8 | it_should_behave_like 'DataMapper::OrderedSet#<< when updating an already included entry' 9 | end 10 | 11 | shared_examples_for 'DataMapper::SubjectSet#<< when updating an entry with the same cache key and the new entry is not yet included' do 12 | its(:entries) { should_not include(entry1) } 13 | its(:entries) { should include(entry2) } 14 | 15 | it 'should insert the new entry at the old position' do 16 | subject.entries.index(entry2).should == @old_index 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/semipublic/property/class_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe DataMapper::Property::Class do 4 | before :all do 5 | Object.send(:remove_const, :Foo) if defined?(Foo) 6 | Object.send(:remove_const, :Bar) if defined?(Bar) 7 | 8 | class ::Foo; end 9 | class ::Bar; end 10 | 11 | @name = :type 12 | @type = described_class 13 | @value = Foo 14 | @other_value = Bar 15 | @invalid_value = 1 16 | end 17 | 18 | it_should_behave_like 'A semipublic Property' 19 | 20 | describe '#typecast' do 21 | it 'returns same value if a class' do 22 | @property.typecast(@model).should equal(@model) 23 | end 24 | 25 | it 'returns the class if found' do 26 | @property.typecast(@model.name).should eql(@model) 27 | end 28 | 29 | it 'does not typecast non-class values' do 30 | @property.typecast('NoClass').should eql('NoClass') 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/ordered_set/shared/initialize_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | shared_examples_for 'DataMapper::OrderedSet#initialize when no entries are given' do 4 | it { should be_empty } 5 | 6 | its(:entries) { should be_empty } 7 | its(:size) { should == 0 } 8 | end 9 | 10 | shared_examples_for 'DataMapper::OrderedSet#initialize when entries are given and they do not contain duplicates' do 11 | it { should_not be_empty } 12 | it { should include(entry1) } 13 | it { should include(entry2) } 14 | 15 | its(:size) { should == 2 } 16 | 17 | it 'should retain insertion order' do 18 | subject.index(entry1).should == 0 19 | subject.index(entry2).should == 1 20 | end 21 | end 22 | 23 | shared_examples_for 'DataMapper::OrderedSet#initialize when entries are given and they contain duplicates' do 24 | it { should_not be_empty } 25 | it { should include(entry1) } 26 | 27 | its(:size) { should == 1 } 28 | end 29 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/subject_set/clear_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'dm-core/support/subject_set' 3 | require 'unit/data_mapper/subject_set/shared/clear_spec' 4 | 5 | describe 'DataMapper::SubjectSet#clear' do 6 | before :all do 7 | 8 | class ::Person 9 | attr_reader :name 10 | def initialize(name) 11 | @name = name 12 | end 13 | end 14 | 15 | end 16 | 17 | subject { set.clear } 18 | 19 | let(:set) { DataMapper::SubjectSet.new(entries) } 20 | let(:entry1) { Person.new('Alice') } 21 | let(:entry2) { Person.new('Bob' ) } 22 | 23 | context 'when no entries are present' do 24 | let(:entries) { [] } 25 | 26 | it_should_behave_like 'DataMapper::SubjectSet#clear when no entries are present' 27 | end 28 | 29 | context 'when entries are present' do 30 | let(:entries) { [ entry1, entry2 ] } 31 | 32 | it_should_behave_like 'DataMapper::SubjectSet#clear when entries are present' 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/dm-core/query/operator.rb: -------------------------------------------------------------------------------- 1 | # TODO: rename this DM::Symbol::Operator 2 | 3 | # TODO: add a method to convert it into a DM::Query::AbstractComparison object, eg: 4 | # operator.comparison_for(repository, model) 5 | 6 | # TODO: rename #target to #property_name 7 | 8 | module DataMapper 9 | class Query 10 | class Operator 11 | include DataMapper::Assertions 12 | extend Equalizer 13 | 14 | equalize :target, :operator 15 | 16 | # @api private 17 | attr_reader :target 18 | 19 | # @api private 20 | attr_reader :operator 21 | 22 | # @api private 23 | def inspect 24 | "#<#{self.class.name} @target=#{target.inspect} @operator=#{operator.inspect}>" 25 | end 26 | 27 | private 28 | 29 | # @api private 30 | def initialize(target, operator) 31 | @target, @operator = target, operator.to_sym 32 | end 33 | end # class Operator 34 | end # class Query 35 | end # module DataMapper 36 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/ordered_set/shared/delete_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | shared_examples_for 'DataMapper::OrderedSet#delete when deleting an already included entry' do 4 | its(:entries) { should_not include(entry1) } 5 | its(:entries) { should include(entry2) } 6 | its(:entries) { should include(entry3) } 7 | 8 | it 'should correct the index' do 9 | ordered_set.index(entry1).should be_nil 10 | ordered_set.index(entry2).should == 0 11 | ordered_set.index(entry3).should == 1 12 | end 13 | end 14 | 15 | shared_examples_for 'DataMapper::OrderedSet#delete when deleting a not yet included entry' do 16 | its(:entries) { should include(entry1) } 17 | its(:entries) { should include(entry2) } 18 | its(:entries) { should include(entry3) } 19 | 20 | it 'should not alter the index' do 21 | ordered_set.index(entry1).should == 0 22 | ordered_set.index(entry2).should == 1 23 | ordered_set.index(entry3).should == 2 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/dm-core/query/direction.rb: -------------------------------------------------------------------------------- 1 | # TODO: rename this DM::Symbol::Direction 2 | 3 | # TODO: add a method to convert it into a DM::Query::Sort object, eg: 4 | # operator.sort_for(model) 5 | 6 | # TODO: rename #target to #property_name 7 | 8 | # TODO: make sure Query converts this into a DM::Query::Sort object 9 | # immediately and passes that down to the Adapter 10 | 11 | # TODO: remove #get method 12 | 13 | module DataMapper 14 | class Query 15 | class Direction < Operator 16 | 17 | # @api private 18 | def reverse! 19 | @operator = @operator == :asc ? :desc : :asc 20 | self 21 | end 22 | 23 | # @api private 24 | def get(resource) 25 | Sort.new(target.get(resource), @operator == :asc) 26 | end 27 | 28 | private 29 | 30 | # @api private 31 | def initialize(target, operator = :asc) 32 | super 33 | end 34 | end # class Direction 35 | end # class Query 36 | end # module DataMapper 37 | -------------------------------------------------------------------------------- /dm-core.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require File.expand_path('../lib/dm-core/version', __FILE__) 3 | 4 | Gem::Specification.new do |gem| 5 | gem.authors = [ "Dan Kubb" ] 6 | gem.email = [ "dan.kubb@gmail.com" ] 7 | gem.summary = "An Object/Relational Mapper for Ruby" 8 | gem.description = "Faster, Better, Simpler." 9 | gem.homepage = "http://datamapper.org" 10 | gem.date = "2011-10-11" 11 | 12 | gem.files = `git ls-files`.split("\n") 13 | gem.test_files = `git ls-files -- {spec}/*`.split("\n") 14 | gem.extra_rdoc_files = %w[LICENSE README.md] 15 | 16 | gem.name = "dm-core" 17 | gem.require_paths = [ "lib" ] 18 | gem.version = DataMapper::VERSION 19 | 20 | gem.add_runtime_dependency('addressable', '~> 2.2.6') 21 | gem.add_runtime_dependency('virtus', '~> 0.5') 22 | 23 | gem.add_development_dependency('rake', '~> 0.9.2') 24 | gem.add_development_dependency('rspec', '~> 1.3.2') 25 | end 26 | -------------------------------------------------------------------------------- /lib/dm-core/support/subject.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | module Subject 3 | # Returns a default value of the subject for given resource 4 | # 5 | # When default value is a callable object, it is called with resource 6 | # and subject passed as arguments. 7 | # 8 | # @param [Resource] resource 9 | # the model instance for which the default is to be set 10 | # 11 | # @return [Object] 12 | # the default value of this subject for +resource+ 13 | # 14 | # @api semipublic 15 | def default_for(resource) 16 | if @default.respond_to?(:call) 17 | @default.call(resource, self) 18 | else 19 | DataMapper::Ext.try_dup(@default) 20 | end 21 | end 22 | 23 | # Returns true if the subject has a default value 24 | # 25 | # @return [Boolean] 26 | # true if the subject has a default value 27 | # 28 | # @api semipublic 29 | def default? 30 | @options.key?(:default) 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/dm-core/query/sort.rb: -------------------------------------------------------------------------------- 1 | # TODO: add #reverse and #reverse! methods 2 | 3 | module DataMapper 4 | class Query 5 | class Sort 6 | # @api semipublic 7 | attr_reader :value 8 | 9 | # @api semipublic 10 | def direction 11 | @ascending ? :ascending : :descending 12 | end 13 | 14 | # @api private 15 | def <=>(other) 16 | other_value = other.value 17 | value_nil = @value.nil? 18 | other_nil = other_value.nil? 19 | 20 | cmp = case 21 | when value_nil then other_nil ? 0 : 1 22 | when other_nil then -1 23 | else 24 | @value <=> other_value 25 | end 26 | 27 | @ascending ? cmp : cmp * -1 28 | end 29 | 30 | private 31 | 32 | # @api private 33 | def initialize(value, ascending = true) 34 | @value = value 35 | @ascending = ascending 36 | end 37 | end # class Sort 38 | end # class Query 39 | end # module DataMapper 40 | -------------------------------------------------------------------------------- /lib/dm-core/resource/persistence_state/immutable.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | module Resource 3 | class PersistenceState 4 | 5 | # a not-persisted/unmodifiable resource 6 | class Immutable < PersistenceState 7 | def get(subject, *args) 8 | unless subject.loaded?(resource) || subject.kind_of?(Associations::Relationship) 9 | raise ImmutableError, 'Immutable resource cannot be lazy loaded' 10 | end 11 | 12 | super 13 | end 14 | 15 | def set(subject, value) 16 | raise ImmutableError, 'Immutable resource cannot be modified' 17 | end 18 | 19 | def delete 20 | raise ImmutableError, 'Immutable resource cannot be deleted' 21 | end 22 | 23 | def commit 24 | self 25 | end 26 | 27 | def rollback 28 | self 29 | end 30 | 31 | end # class Immutable 32 | end # class PersistenceState 33 | end # module Resource 34 | end # module DataMapper 35 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/subject_set/to_ary_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'dm-core/support/subject_set' 3 | require 'unit/data_mapper/subject_set/shared/to_ary_spec' 4 | 5 | describe 'DataMapper::SubjectSet#to_ary' do 6 | before :all do 7 | 8 | class ::Person 9 | attr_reader :name 10 | def initialize(name) 11 | @name = name 12 | end 13 | end 14 | 15 | end 16 | 17 | subject { set.to_ary } 18 | 19 | let(:set) { DataMapper::SubjectSet.new(entries) } 20 | let(:entry1) { Person.new('Alice') } 21 | let(:entry2) { Person.new('Bob' ) } 22 | 23 | context 'when no entries are present' do 24 | let(:entries) { [] } 25 | 26 | it_should_behave_like 'DataMapper::SubjectSet#to_ary when no entries are present' 27 | end 28 | 29 | context 'when entries are present' do 30 | let(:entries) { [ entry1, entry2 ] } 31 | 32 | it_should_behave_like 'DataMapper::SubjectSet#to_ary when entries are present' 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/dm-core/property/string.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | class Property 3 | class String < Object 4 | load_as ::String 5 | dump_as ::String 6 | coercion_method :to_string 7 | 8 | accept_options :length 9 | 10 | DEFAULT_LENGTH = 50 11 | length(DEFAULT_LENGTH) 12 | 13 | # Returns maximum property length (if applicable). 14 | # This usually only makes sense when property is of 15 | # type Range or custom 16 | # 17 | # @return [Integer, nil] 18 | # the maximum length of this property 19 | # 20 | # @api semipublic 21 | def length 22 | if @length.kind_of?(Range) 23 | @length.max 24 | else 25 | @length 26 | end 27 | end 28 | 29 | protected 30 | 31 | def initialize(model, name, options = {}) 32 | super 33 | @length = @options.fetch(:length) 34 | end 35 | 36 | end # class String 37 | end # class Property 38 | end # module DataMapper 39 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/subject_set/named_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'dm-core/support/subject_set' 3 | require 'unit/data_mapper/subject_set/shared/named_spec' 4 | 5 | describe 'DataMapper::SubjectSet#named?' do 6 | before :all do 7 | 8 | class ::Person 9 | attr_reader :name 10 | def initialize(name) 11 | @name = name 12 | end 13 | end 14 | 15 | end 16 | 17 | subject { set.named?(name) } 18 | 19 | let(:entry) { Person.new(name) } 20 | let(:name ) { 'Alice' } 21 | 22 | context 'when no entry with the given name is present' do 23 | let(:set) { DataMapper::SubjectSet.new([]) } 24 | 25 | it_should_behave_like 'DataMapper::SubjectSet#named? when no entry with the given name is present' 26 | end 27 | 28 | context 'when an entry with the given name is present' do 29 | let(:set) { DataMapper::SubjectSet.new([ entry ]) } 30 | 31 | it_should_behave_like 'DataMapper::SubjectSet#named? when an entry with the given name is present' 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/ordered_set/shared/merge_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | shared_examples_for 'DataMapper::OrderedSet#merge when merging two empty sets' do 4 | it { should be_instance_of(set.class) } 5 | it { should equal(set) } 6 | it { should == set } 7 | end 8 | 9 | shared_examples_for 'DataMapper::OrderedSet#merge when merging a set with already present entries' do 10 | it { should equal(set) } 11 | it { should == set } 12 | it { should include(entry) } 13 | 14 | it 'does not add an entry to the set' do 15 | expect { subject }.to_not change { set.size } 16 | end 17 | end 18 | 19 | shared_examples_for 'DataMapper::OrderedSet#merge when merging a set with not yet present entries' do 20 | it { should equal(set) } 21 | it { should != set } 22 | it { should include(entry1) } 23 | it { should include(entry2) } 24 | 25 | it 'adds an entry to the set' do 26 | expect { subject }.to change { set.size }.from(1).to(2) 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/semipublic/adapters/abstract_adapter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'AbstractAdapter' do 4 | before :all do 5 | @adapter = DataMapper::Adapters::AbstractAdapter.new(:abstract, :foo => 'bar') 6 | @adapter_class = @adapter.class 7 | @scheme = DataMapper::Inflector.underscore(DataMapper::Inflector.demodulize(@adapter_class).chomp('Adapter')) 8 | @adapter_name = "test_#{@scheme}".to_sym 9 | end 10 | 11 | describe 'initialization' do 12 | 13 | describe 'name' do 14 | it 'should have a name' do 15 | @adapter.name.should == :abstract 16 | end 17 | end 18 | 19 | it 'should set options' do 20 | @adapter.options.should == {:foo => 'bar'} 21 | end 22 | 23 | it 'should set naming conventions' do 24 | @adapter.resource_naming_convention.should == DataMapper::NamingConventions::Resource::UnderscoredAndPluralized 25 | @adapter.field_naming_convention.should == DataMapper::NamingConventions::Field::Underscored 26 | end 27 | 28 | end 29 | 30 | end 31 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/ordered_set/size_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'dm-core/support/ordered_set' 3 | require 'unit/data_mapper/ordered_set/shared/size_spec' 4 | 5 | describe 'DataMapper::OrderedSet#size' do 6 | subject { ordered_set.size } 7 | 8 | context 'when no entry is present' do 9 | let(:ordered_set) { DataMapper::OrderedSet.new } 10 | 11 | it_should_behave_like 'DataMapper::OrderedSet#size when no entry is present' 12 | end 13 | 14 | context 'when 1 entry is present' do 15 | let(:ordered_set) { DataMapper::OrderedSet.new([ 1 ]) } 16 | 17 | it_should_behave_like 'DataMapper::OrderedSet#size when 1 entry is present' 18 | end 19 | 20 | context 'when more than 1 entry is present' do 21 | let(:ordered_set) { DataMapper::OrderedSet.new(entries) } 22 | let(:entries) { [ 1, 2 ] } 23 | let(:expected_size) { entries.size } 24 | 25 | it_should_behave_like 'DataMapper::OrderedSet#size when more than 1 entry is present' 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/subject_set/get_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'dm-core/support/subject_set' 3 | require 'unit/data_mapper/subject_set/shared/get_spec' 4 | 5 | describe 'DataMapper::SubjectSet#[]' do 6 | before :all do 7 | 8 | class ::Person 9 | attr_reader :name 10 | def initialize(name) 11 | @name = name 12 | end 13 | end 14 | 15 | end 16 | 17 | subject { set[name] } 18 | 19 | let(:set ) { DataMapper::SubjectSet.new(entries) } 20 | let(:entry) { Person.new(name) } 21 | let(:name ) { 'Alice' } 22 | 23 | context 'when the entry with the given name is not present' do 24 | let(:entries) { [] } 25 | 26 | it_should_behave_like 'DataMapper::SubjectSet#[] when the entry with the given name is not present' 27 | end 28 | 29 | context 'when the entry with the given name is present' do 30 | let(:entries) { [ entry ] } 31 | 32 | it_should_behave_like 'DataMapper::SubjectSet#[] when the entry with the given name is present' 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /tasks/spec.rake: -------------------------------------------------------------------------------- 1 | spec_defaults = lambda do |spec| 2 | spec.pattern = 'spec/**/*_spec.rb' 3 | spec.libs << 'lib' << 'spec' 4 | spec.spec_opts << '--options' << 'spec/spec.opts' 5 | end 6 | 7 | begin 8 | require 'spec/rake/spectask' 9 | 10 | Spec::Rake::SpecTask.new(:spec, &spec_defaults) 11 | rescue LoadError 12 | task :spec do 13 | abort 'rspec is not available. In order to run spec, you must: gem install rspec' 14 | end 15 | end 16 | 17 | begin 18 | require 'rcov' 19 | require 'spec/rake/verify_rcov' 20 | 21 | Spec::Rake::SpecTask.new(:rcov) do |rcov| 22 | spec_defaults.call(rcov) 23 | rcov.rcov = true 24 | rcov.rcov_opts = File.read('spec/rcov.opts').split(/\s+/) 25 | end 26 | 27 | RCov::VerifyTask.new(:verify_rcov => :rcov) do |rcov| 28 | rcov.threshold = 100 29 | end 30 | rescue LoadError 31 | %w[ rcov verify_rcov ].each do |name| 32 | task name do 33 | abort "rcov is not available. In order to run #{name}, you must: gem install rcov" 34 | end 35 | end 36 | end 37 | 38 | task :default => :spec 39 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/ordered_set/delete_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'dm-core/support/ordered_set' 3 | require 'unit/data_mapper/ordered_set/shared/delete_spec' 4 | 5 | describe 'DataMapper::OrderedSet#delete' do 6 | subject { ordered_set } 7 | 8 | let(:ordered_set) { DataMapper::OrderedSet.new([ entry1, entry2, entry3 ]) } 9 | let(:entry1) { 1 } 10 | let(:entry2) { 2 } 11 | let(:entry3) { 3 } 12 | 13 | before do 14 | ordered_set.delete(entry) 15 | end 16 | 17 | context 'when deleting an already included entry' do 18 | let(:entry) { entry1 } 19 | 20 | it_should_behave_like 'DataMapper::OrderedSet#delete when deleting an already included entry' 21 | end 22 | 23 | context 'when deleting a not yet included entry' do 24 | let(:entry) { 4 } 25 | 26 | it_should_behave_like 'DataMapper::OrderedSet#delete when deleting a not yet included entry' 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /.autotest: -------------------------------------------------------------------------------- 1 | Autotest.add_hook :initialize do |at| 2 | %w[ .git log script tasks LICENSE README.rdoc ].each do |exception| 3 | at.add_exception(exception) 4 | end 5 | 6 | at.clear_mappings 7 | 8 | spec_folders = /(?:semi)?public/ 9 | 10 | # when a file is updated, make sure it's dependent public and semipublic specs pass 11 | at.add_mapping %r{\Alib/dm\-core/(.+)\.rb\z} do |_,match| 12 | at.files_matching %r{\Aspec/#{spec_folders}/#{match[1]}_spec\.rb\z} 13 | end 14 | 15 | # when the spec configuration changes make sure all specs pass 16 | at.add_mapping %r{\Aspec/spec_helper\.rb\z} do 17 | at.files_matching %r{\Aspec/.+_spec\.rb\z} 18 | end 19 | 20 | # when a spec is updated, make sure it passes 21 | at.add_mapping %r{\Aspec/#{spec_folders}/(.+)_spec\.rb\z} do |filename,_| 22 | filename 23 | end 24 | 25 | # when the collection shared spec is update, make sure all dependent specs pass 26 | at.add_mapping %r{\Aspec/lib/collection_shared_spec\.rb\z} do 27 | at.files_matching %r{\Aspec/#{spec_folders}/collection_spec\.rb\z} 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/dm-core/model/is.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | module Model 3 | # Module that provides a common way for plugin authors 4 | # to implement "is ... " traits (object behaviors that can be shared) 5 | module Is 6 | # A common interface to activate plugins for a resource. For instance: 7 | # 8 | # class Widget 9 | # include DataMapper::Resource 10 | # 11 | # is :list 12 | # end 13 | # 14 | # adds list item behavior to the model. Plugin that wants to conform 15 | # to "is API" of DataMapper must supply is_+behavior name+ method, 16 | # for example above it would be is_list. 17 | # 18 | # @api public 19 | def is(plugin, *args, &block) 20 | generator_method = "is_#{plugin}".to_sym 21 | 22 | if respond_to?(generator_method) 23 | send(generator_method, *args, &block) 24 | else 25 | raise PluginNotFoundError, "could not find plugin named #{plugin}" 26 | end 27 | end 28 | end # module Is 29 | 30 | include Is 31 | end # module Model 32 | end # module DataMapper 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Dan Kubb 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. 21 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/ordered_set/index_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'dm-core/support/ordered_set' 3 | require 'unit/data_mapper/ordered_set/shared/index_spec' 4 | 5 | describe 'DataMapper::OrderedSet#index' do 6 | subject { ordered_set.index(entry) } 7 | 8 | context 'when the entry is not present' do 9 | let(:ordered_set) { DataMapper::OrderedSet.new } 10 | let(:entry) { 1 } 11 | 12 | it_should_behave_like 'DataMapper::OrderedSet#index when the entry is not present' 13 | end 14 | 15 | context 'when 1 entry is present' do 16 | let(:ordered_set) { DataMapper::OrderedSet.new([ entry ]) } 17 | let(:entry) { 1 } 18 | 19 | it_should_behave_like 'DataMapper::OrderedSet#index when 1 entry is present' 20 | end 21 | 22 | context 'when 2 entries are present' do 23 | let(:ordered_set) { DataMapper::OrderedSet.new([ 2, entry ]) } 24 | let(:entry) { 1 } 25 | 26 | it_should_behave_like 'DataMapper::OrderedSet#index when 2 entries are present' 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/unit/try_dup_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'dm-core/support/ext/try_dup' 3 | 4 | describe "try_dup" do 5 | it "returns a duplicate version on regular objects" do 6 | obj = Object.new 7 | oth = DataMapper::Ext.try_dup(obj) 8 | obj.should_not === oth 9 | end 10 | 11 | it "returns self on Numerics" do 12 | obj = 12 13 | oth = DataMapper::Ext.try_dup(obj) 14 | obj.should === oth 15 | end 16 | 17 | it "returns self on Symbols" do 18 | obj = :test 19 | oth = DataMapper::Ext.try_dup(obj) 20 | obj.should === oth 21 | end 22 | 23 | it "returns self on true" do 24 | obj = true 25 | oth = DataMapper::Ext.try_dup(obj) 26 | obj.should === oth 27 | end 28 | 29 | it "returns self on false" do 30 | obj = false 31 | oth = DataMapper::Ext.try_dup(obj) 32 | obj.should === oth 33 | end 34 | 35 | it "returns self on nil" do 36 | obj = nil 37 | oth = DataMapper::Ext.try_dup(obj) 38 | obj.should === oth 39 | end 40 | 41 | it "returns self on modules" do 42 | obj = Module.new 43 | oth = DataMapper::Ext.try_dup(obj) 44 | obj.object_id.should == oth.object_id 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/dm-core/resource/persistence_state/clean.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | module Resource 3 | class PersistenceState 4 | 5 | # a persisted/unmodified resource 6 | class Clean < Persisted 7 | def set(subject, value) 8 | if not_modified?(subject, value) 9 | self 10 | else 11 | # assign to persistence_state so that if Dirty#set calls 12 | # a Relationship#set, which modifies a Property, the same 13 | # Dirty state instance will be reused. 14 | state = resource.persistence_state = Dirty.new(resource) 15 | state.set(subject, value) 16 | end 17 | end 18 | 19 | def delete 20 | Deleted.new(resource) 21 | end 22 | 23 | def commit 24 | self 25 | end 26 | 27 | def rollback 28 | self 29 | end 30 | 31 | private 32 | 33 | def not_modified?(subject, value) 34 | subject.loaded?(resource) && subject.get!(resource).eql?(value) 35 | end 36 | 37 | end # class Clean 38 | end # class PersistenceState 39 | end # module Resource 40 | end # module DataMapper 41 | -------------------------------------------------------------------------------- /lib/dm-core/property/decimal.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | class Property 3 | class Decimal < Numeric 4 | load_as BigDecimal 5 | dump_as BigDecimal 6 | coercion_method :to_decimal 7 | 8 | DEFAULT_PRECISION = 10 9 | DEFAULT_SCALE = 0 10 | 11 | precision(DEFAULT_PRECISION) 12 | scale(DEFAULT_SCALE) 13 | 14 | protected 15 | 16 | def initialize(model, name, options = {}) 17 | super 18 | 19 | [ :scale, :precision ].each do |key| 20 | unless @options.key?(key) 21 | warn "options[#{key.inspect}] should be set for #{self.class}, defaulting to #{send(key).inspect} (#{caller.first})" 22 | end 23 | end 24 | 25 | unless @scale >= 0 26 | raise ArgumentError, "scale must be equal to or greater than 0, but was #{@scale.inspect}" 27 | end 28 | 29 | unless @precision >= @scale 30 | raise ArgumentError, "precision must be equal to or greater than scale, but was #{@precision.inspect} and scale was #{@scale.inspect}" 31 | end 32 | end 33 | 34 | end # class Decimal 35 | end # class Property 36 | end # module DataMapper 37 | -------------------------------------------------------------------------------- /lib/dm-core/spec/lib/counter_adapter.rb: -------------------------------------------------------------------------------- 1 | class CounterAdapter < DataMapper::Adapters::AbstractAdapter 2 | instance_methods.each do |method| 3 | next if method =~ /\A__/ || 4 | %w[ send class dup object_id kind_of? instance_of? respond_to? equal? freeze frozen? should should_not instance_variables instance_variable_set instance_variable_get instance_variable_defined? remove_instance_variable extend inspect copy_object ].include?(method.to_s) 5 | undef_method method 6 | end 7 | 8 | attr_reader :counts 9 | 10 | def kind_of?(klass) 11 | super || @adapter.kind_of?(klass) 12 | end 13 | 14 | def instance_of?(klass) 15 | super || @adapter.instance_of?(klass) 16 | end 17 | 18 | def respond_to?(method, include_private = false) 19 | super || @adapter.respond_to?(method, include_private) 20 | end 21 | 22 | private 23 | 24 | def initialize(adapter) 25 | @counts = Hash.new { |hash, key| hash[key] = 0 } 26 | @adapter = adapter 27 | @count = 0 28 | end 29 | 30 | def increment_count_for(method) 31 | @counts[method] += 1 32 | end 33 | 34 | def method_missing(method, *args, &block) 35 | increment_count_for(method) 36 | @adapter.send(method, *args, &block) 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/ordered_set/initialize_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'dm-core/support/ordered_set' 3 | require 'unit/data_mapper/ordered_set/shared/initialize_spec' 4 | 5 | describe 'DataMapper::OrderedSet#initialize' do 6 | 7 | context 'when no entries are given' do 8 | subject { DataMapper::OrderedSet.new } 9 | 10 | it_should_behave_like 'DataMapper::OrderedSet#initialize when no entries are given' 11 | end 12 | 13 | context 'when entries are given' do 14 | subject { DataMapper::OrderedSet.new(entries) } 15 | 16 | context 'and they do not contain duplicates' do 17 | let(:entries) { [ entry1, entry2 ] } 18 | let(:entry1) { 1 } 19 | let(:entry2) { 2 } 20 | 21 | it_should_behave_like 'DataMapper::OrderedSet#initialize when entries are given and they do not contain duplicates' 22 | end 23 | 24 | context 'and they contain duplicates' do 25 | let(:entries) { [ entry1, entry2 ] } 26 | let(:entry1) { 1 } 27 | let(:entry2) { 1 } 28 | 29 | it_should_behave_like 'DataMapper::OrderedSet#initialize when entries are given and they contain duplicates' 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /spec/public/property/binary_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe DataMapper::Property::Binary do 4 | before :all do 5 | @name = :title 6 | @type = described_class 7 | @load_as = String 8 | @value = 'value' 9 | @other_value = 'return value' 10 | @invalid_value = 1 11 | end 12 | 13 | it_should_behave_like 'A public Property' 14 | 15 | describe '.options' do 16 | subject { described_class.options } 17 | 18 | it { should be_kind_of(Hash) } 19 | 20 | it { should eql(:load_as => @load_as, :dump_as => @load_as, :coercion_method => :to_string, :length => 50) } 21 | end 22 | 23 | if RUBY_VERSION >= "1.9" 24 | describe 'encoding' do 25 | let(:model) do 26 | Class.new do 27 | include ::DataMapper::Resource 28 | property :bin_data, ::DataMapper::Property::Binary 29 | end 30 | end 31 | 32 | it 'should always dump with BINARY' do 33 | model.bin_data.dump("foo".freeze).encoding.names.should include("BINARY") 34 | end 35 | 36 | it 'should always load with BINARY' do 37 | model.bin_data.load("foo".freeze).encoding.names.should include("BINARY") 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/public/associations/many_to_one_with_boolean_cpk_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | # TODO: combine this into many_to_one_spec.rb 4 | 5 | describe 'Many to One Associations when foreign key is part of a composite key, with an integer and a boolean making up the composite key' do 6 | before :all do 7 | class ::ManyModel 8 | include DataMapper::Resource 9 | 10 | property :integer_key, Integer, :key => true 11 | property :boolean_key, Boolean, :key => true 12 | 13 | belongs_to :one_model, :child_key => [ :integer_key ] 14 | end 15 | 16 | class ::OneModel 17 | include DataMapper::Resource 18 | 19 | property :integer_key, Integer, :key => true 20 | 21 | has n, :many_models, :child_key => [ :integer_key ] 22 | end 23 | DataMapper.finalize 24 | end 25 | 26 | supported_by :all do 27 | before :all do 28 | @one = OneModel.create(:integer_key => 1) 29 | @many = ManyModel.create(:integer_key => 1, :boolean_key => false) 30 | end 31 | 32 | it 'should be able to access parent' do 33 | @many.one_model.should == @one 34 | end 35 | 36 | it 'should be able to access the child' do 37 | @one.many_models.should == [ @many ] 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/public/associations/many_to_one_with_custom_fk_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | # TODO: combine this into many_to_one_spec.rb 4 | 5 | describe 'Many to One Associations when foreign key is a property subclass' do 6 | before :all do 7 | class ::CustomPK < DataMapper::Property::String 8 | key true 9 | end 10 | 11 | class ::Animal 12 | include DataMapper::Resource 13 | 14 | property :id, Serial 15 | property :name, String 16 | 17 | belongs_to :zoo 18 | end 19 | 20 | class ::Zoo 21 | include DataMapper::Resource 22 | 23 | property :id, ::CustomPK 24 | 25 | has n, :animals 26 | end 27 | 28 | DataMapper.finalize 29 | end 30 | 31 | supported_by :all do 32 | before :all do 33 | @zoo = Zoo.create(:id => 'foo') 34 | @animal = @zoo.animals.create(:name => 'marty') 35 | end 36 | 37 | it 'should have FK of the same property type as zoo PK' do 38 | Animal.properties[:zoo_id].class.should be(Zoo.properties[:id].class) 39 | end 40 | 41 | it 'should be able to access parent' do 42 | @animal.zoo.should == @zoo 43 | end 44 | 45 | it 'should be able to access the children' do 46 | @zoo.animals.should == [ @animal ] 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /spec/semipublic/property/date_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe DataMapper::Property::Date do 4 | before :all do 5 | @name = :created_on 6 | @type = described_class 7 | @value = Date.today 8 | @other_value = Date.today + 1 9 | @invalid_value = 1 10 | end 11 | 12 | it_should_behave_like 'A semipublic Property' 13 | 14 | describe '#typecast' do 15 | describe 'and value given as a hash with keys like :year, :month, etc' do 16 | it 'builds a Date instance from hash values' do 17 | result = @property.typecast( 18 | :year => '2007', 19 | :month => '3', 20 | :day => '25' 21 | ) 22 | 23 | result.should be_kind_of(Date) 24 | result.year.should eql(2007) 25 | result.month.should eql(3) 26 | result.day.should eql(25) 27 | end 28 | end 29 | 30 | describe 'and value is a string' do 31 | it 'parses the string' do 32 | result = @property.typecast('Dec 20th, 2006') 33 | result.month.should == 12 34 | result.day.should == 20 35 | result.year.should == 2006 36 | end 37 | end 38 | 39 | it 'does not typecast non-date values' do 40 | @property.typecast('not-date').should eql('not-date') 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/subject_set/delete_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'dm-core/support/subject_set' 3 | require 'unit/data_mapper/subject_set/shared/delete_spec' 4 | 5 | describe 'DataMapper::SubjectSet#delete' do 6 | before :all do 7 | 8 | class ::Person 9 | attr_reader :name 10 | def initialize(name) 11 | @name = name 12 | end 13 | end 14 | 15 | end 16 | 17 | subject { set } 18 | 19 | let(:set) { DataMapper::SubjectSet.new([ entry1, entry2, entry3 ]) } 20 | let(:ordered_set) { set.entries } 21 | let(:entry1) { Person.new('Alice') } 22 | let(:entry2) { Person.new('John' ) } 23 | let(:entry3) { Person.new('Jane' ) } 24 | 25 | before do 26 | set.delete(entry) 27 | end 28 | 29 | context 'when deleting an already included entry' do 30 | let(:entry) { entry1 } 31 | 32 | it_should_behave_like 'DataMapper::SubjectSet#delete when deleting an already included entry' 33 | end 34 | 35 | context 'when deleting a not yet included entry' do 36 | let(:entry) { Person.new('Bob') } 37 | 38 | it_should_behave_like 'DataMapper::SubjectSet#delete when deleting a not yet included entry' 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/unit/object_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'dm-core/support/ext/object' 3 | 4 | describe DataMapper::Ext::Object do 5 | before :all do 6 | Object.send(:remove_const, :HactiveSupport) if defined?(HactiveSupport) 7 | module ::HactiveSupport 8 | class MemoizeConsideredUseless; end 9 | end 10 | 11 | Object.send(:remove_const, :Foo) if defined?(Foo) 12 | module ::Foo 13 | class Bar; end 14 | end 15 | 16 | Object.send(:remove_const, :Oi) if defined?(Oi) 17 | class ::Oi 18 | attr_accessor :foo 19 | end 20 | end 21 | 22 | describe ".full_const_get" do 23 | it 'returns constant by FQ name in receiver namespace' do 24 | DataMapper::Ext::Object.full_const_get("Oi").should == Oi 25 | DataMapper::Ext::Object.full_const_get("Foo::Bar").should == Foo::Bar 26 | end 27 | end 28 | 29 | describe ".full_const_set" do 30 | it 'sets constant value by FQ name in receiver namespace' do 31 | DataMapper::Ext::Object.full_const_set("HactiveSupport::MCU", HactiveSupport::MemoizeConsideredUseless) 32 | 33 | DataMapper::Ext::Object.full_const_get("HactiveSupport::MCU").should == HactiveSupport::MemoizeConsideredUseless 34 | DataMapper::Ext::Object.full_const_get(HactiveSupport, "MCU").should == HactiveSupport::MemoizeConsideredUseless 35 | end 36 | end 37 | 38 | end 39 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/subject_set/size_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'dm-core/support/subject_set' 3 | require 'unit/data_mapper/subject_set/shared/size_spec' 4 | 5 | describe 'DataMapper::SubjectSet#size' do 6 | before :all do 7 | 8 | class ::Person 9 | attr_reader :name 10 | def initialize(name) 11 | @name = name 12 | end 13 | end 14 | 15 | end 16 | 17 | subject { set.size } 18 | 19 | let(:entry1) { Person.new('Alice') } 20 | let(:entry2) { Person.new('Bob' ) } 21 | 22 | context 'when no entry is present' do 23 | let(:set) { DataMapper::SubjectSet.new } 24 | 25 | it_should_behave_like 'DataMapper::SubjectSet#size when no entry is present' 26 | end 27 | 28 | context 'when 1 entry is present' do 29 | let(:set) { DataMapper::SubjectSet.new(entries) } 30 | let(:entries) { [ entry1 ] } 31 | 32 | it_should_behave_like 'DataMapper::SubjectSet#size when 1 entry is present' 33 | end 34 | 35 | context 'when more than 1 entry is present' do 36 | let(:set) { DataMapper::SubjectSet.new(entries) } 37 | let(:entries) { [ entry1, entry2 ] } 38 | let(:expected_size) { entries.size } 39 | 40 | it_should_behave_like 'DataMapper::SubjectSet#size when more than 1 entry is present' 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'pathname' 2 | require 'rubygems' 3 | require 'backports' 4 | require 'spec' 5 | require 'dm-core/spec/setup' 6 | 7 | ENV['ADAPTER'] ||= 'in_memory' 8 | 9 | SPEC_ROOT = Pathname(__FILE__).dirname.expand_path 10 | LIB_ROOT = SPEC_ROOT.parent + 'lib' 11 | 12 | Pathname.glob((LIB_ROOT + 'dm-core/spec/**/*.rb' ).to_s).each { |file| require file } 13 | Pathname.glob((SPEC_ROOT + '{lib,support,*/shared}/**/*.rb').to_s).each { |file| require file } 14 | 15 | Spec::Runner.configure do |config| 16 | 17 | config.extend( DataMapper::Spec::Adapters::Helpers) 18 | config.include(DataMapper::Spec::PendingHelpers) 19 | config.include(DataMapper::Spec::Helpers) 20 | 21 | config.after :all do 22 | DataMapper::Spec.cleanup_models 23 | end 24 | 25 | config.after :all do 26 | # global ivar cleanup 27 | DataMapper::Spec.remove_ivars(self, instance_variables.reject { |ivar| ivar[0, 2] == '@_' }) 28 | end 29 | 30 | config.after :all do 31 | # WTF: rspec holds a reference to the last match for some reason. 32 | # When the object ivars are explicitly removed, this causes weird 33 | # problems when rspec uses it (!). Why rspec does this I have no 34 | # idea because I cannot determine the intention from the code. 35 | DataMapper::Spec.remove_ivars(Spec::Matchers.last_matcher, %w[ @expected ]) 36 | end 37 | 38 | end 39 | -------------------------------------------------------------------------------- /lib/dm-core/property/numeric.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | class Property 3 | class Numeric < Object 4 | accept_options :precision, :scale, :min, :max 5 | 6 | attr_reader :precision, :scale, :min, :max 7 | 8 | DEFAULT_NUMERIC_MIN = 0 9 | DEFAULT_NUMERIC_MAX = 2**31-1 10 | 11 | protected 12 | 13 | def initialize(model, name, options = {}) 14 | super 15 | 16 | if kind_of?(Decimal) || kind_of?(Float) 17 | @precision = @options.fetch(:precision) 18 | @scale = @options.fetch(:scale) 19 | 20 | unless @precision > 0 21 | raise ArgumentError, "precision must be greater than 0, but was #{@precision.inspect}" 22 | end 23 | end 24 | 25 | if @options.key?(:min) || @options.key?(:max) 26 | @min = @options.fetch(:min, self.class::DEFAULT_NUMERIC_MIN) 27 | @max = @options.fetch(:max, self.class::DEFAULT_NUMERIC_MAX) 28 | 29 | if @max < DEFAULT_NUMERIC_MIN && !@options.key?(:min) 30 | raise ArgumentError, "min should be specified when the max is less than #{DEFAULT_NUMERIC_MIN}" 31 | elsif @max < @min 32 | raise ArgumentError, "max must be less than the min, but was #{@max} while the min was #{@min}" 33 | end 34 | end 35 | end 36 | end # class Numeric 37 | end # class Property 38 | end # module DataMapper 39 | -------------------------------------------------------------------------------- /spec/semipublic/property/date_time_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe DataMapper::Property::DateTime do 4 | before :all do 5 | @name = :created_at 6 | @type = described_class 7 | @value = DateTime.now 8 | @other_value = DateTime.now + 15 9 | @invalid_value = 1 10 | end 11 | 12 | it_should_behave_like 'A semipublic Property' 13 | 14 | describe '#typecast' do 15 | describe 'and value given as a hash with keys like :year, :month, etc' do 16 | it 'builds a DateTime instance from hash values' do 17 | result = @property.typecast( 18 | :year => '2006', 19 | :month => '11', 20 | :day => '23', 21 | :hour => '12', 22 | :min => '0', 23 | :sec => '0' 24 | ) 25 | 26 | result.should be_kind_of(DateTime) 27 | result.year.should eql(2006) 28 | result.month.should eql(11) 29 | result.day.should eql(23) 30 | result.hour.should eql(12) 31 | result.min.should eql(0) 32 | result.sec.should eql(0) 33 | end 34 | end 35 | 36 | describe 'and value is a string' do 37 | it 'parses the string' do 38 | @property.typecast('Dec, 2006').month.should == 12 39 | end 40 | end 41 | 42 | it 'does not typecast non-datetime values' do 43 | @property.typecast('not-datetime').should eql('not-datetime') 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/dm-core/property/discriminator.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | class Property 3 | class Discriminator < Class 4 | default lambda { |resource, property| resource.model } 5 | required true 6 | 7 | # @api private 8 | def bind 9 | model.extend Model unless model < Model 10 | end 11 | 12 | module Model 13 | def inherited(model) 14 | super # setup self.descendants 15 | set_discriminator_scope_for(model) 16 | end 17 | 18 | def new(*args, &block) 19 | if args.size == 1 && args.first.kind_of?(Hash) 20 | discriminator = properties(repository_name).discriminator 21 | 22 | if discriminator_value = args.first[discriminator.name] 23 | model = discriminator.typecast(discriminator_value) 24 | 25 | if model.kind_of?(Model) && !model.equal?(self) 26 | return model.new(*args, &block) 27 | end 28 | end 29 | end 30 | 31 | super 32 | end 33 | 34 | private 35 | 36 | def set_discriminator_scope_for(model) 37 | discriminator = self.properties.discriminator 38 | default_scope = model.default_scope(discriminator.repository_name) 39 | default_scope.update(discriminator.name => model.descendants.dup << model) 40 | end 41 | end 42 | end # class Discriminator 43 | end # module Property 44 | end # module DataMapper 45 | -------------------------------------------------------------------------------- /lib/dm-core/support/equalizer.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | module Equalizer 3 | def equalize(*methods) 4 | define_eql_method(methods) 5 | define_equivalent_method(methods) 6 | define_hash_method(methods) 7 | end 8 | 9 | private 10 | 11 | def define_eql_method(methods) 12 | class_eval <<-RUBY, __FILE__, __LINE__ + 1 13 | def eql?(other) 14 | return true if equal?(other) 15 | instance_of?(other.class) && 16 | #{methods.map { |method| "#{method}.eql?(other.#{method})" }.join(' && ')} 17 | end 18 | RUBY 19 | end 20 | 21 | def define_equivalent_method(methods) 22 | respond_to = [] 23 | equivalent = [] 24 | 25 | methods.each do |method| 26 | respond_to << "other.respond_to?(#{method.inspect})" 27 | equivalent << "#{method} == other.#{method}" 28 | end 29 | 30 | class_eval <<-RUBY, __FILE__, __LINE__ + 1 31 | def ==(other) 32 | return true if equal?(other) 33 | return false unless kind_of?(other.class) || other.kind_of?(self.class) 34 | #{respond_to.join(' && ')} && 35 | #{equivalent.join(' && ')} 36 | end 37 | RUBY 38 | end 39 | 40 | def define_hash_method(methods) 41 | class_eval <<-RUBY, __FILE__, __LINE__ + 1 42 | def hash 43 | self.class.hash ^ #{methods.map { |method| "#{method}.hash" }.join(' ^ ')} 44 | end 45 | RUBY 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /spec/support/core_ext/inheritable_attributes.rb: -------------------------------------------------------------------------------- 1 | class Class 2 | def class_inheritable_reader(*ivars) 3 | instance_reader = ivars.pop[:reader] if ivars.last.is_a?(Hash) 4 | 5 | ivars.each do |ivar| 6 | self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 7 | def self.#{ivar} 8 | return @#{ivar} if defined?(@#{ivar}) 9 | return nil if self.object_id == #{self.object_id} 10 | ivar = superclass.#{ivar} 11 | return nil if ivar.nil? 12 | @#{ivar} = DataMapper::Ext.try_dup(ivar) 13 | end 14 | RUBY 15 | 16 | unless instance_reader == false 17 | self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 18 | def #{ivar} 19 | self.class.#{ivar} 20 | end 21 | RUBY 22 | end 23 | end 24 | end 25 | 26 | def class_inheritable_writer(*ivars) 27 | instance_writer = ivars.pop[:instance_writer] if ivars.last.is_a?(Hash) 28 | ivars.each do |ivar| 29 | self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 30 | def self.#{ivar}=(obj) 31 | @#{ivar} = obj 32 | end 33 | RUBY 34 | unless instance_writer == false 35 | self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 36 | def #{ivar}=(obj) self.class.#{ivar} = obj end 37 | RUBY 38 | end 39 | end 40 | end 41 | 42 | def class_inheritable_accessor(*syms) 43 | class_inheritable_reader(*syms) 44 | class_inheritable_writer(*syms) 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/ordered_set/eql_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'dm-core/support/ordered_set' 3 | 4 | describe 'DataMapper::OrderedSet#eql?' do 5 | subject { ordered_set.eql?(other) } 6 | 7 | let(:original_entry) { 1 } 8 | let(:ordered_set) { DataMapper::OrderedSet.new([ original_entry ]) } 9 | 10 | context 'with the same ordered_set' do 11 | let(:other) { ordered_set } 12 | 13 | it { should be(true) } 14 | 15 | it 'is symmetric' do 16 | should == other.eql?(ordered_set) 17 | end 18 | end 19 | 20 | context 'with equivalent ordered_set' do 21 | let(:other) { ordered_set.dup } 22 | 23 | it { should be(true) } 24 | 25 | it 'is symmetric' do 26 | should == other.eql?(ordered_set) 27 | end 28 | end 29 | 30 | context 'with both containing no ordered_set' do 31 | let(:ordered_set) { DataMapper::OrderedSet.new } 32 | let(:other) { DataMapper::OrderedSet.new } 33 | 34 | it { should be(true) } 35 | 36 | it 'is symmetric' do 37 | should == other.eql?(ordered_set) 38 | end 39 | end 40 | 41 | context 'with different ordered_set' do 42 | let(:different_entry) { 2 } 43 | let(:other) { DataMapper::OrderedSet.new([ different_entry ]) } 44 | 45 | it { should be(false) } 46 | 47 | it 'is symmetric' do 48 | should == other.eql?(ordered_set) 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/ordered_set/merge_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'dm-core/support/ordered_set' 3 | require 'unit/data_mapper/ordered_set/shared/merge_spec' 4 | 5 | describe 'DataMapper::OrderedSet#merge' do 6 | 7 | context 'when merging two empty sets' do 8 | subject { ordered_set.merge([]) } 9 | 10 | let(:ordered_set) { set } 11 | let(:set) { DataMapper::OrderedSet.new } 12 | 13 | it_should_behave_like 'DataMapper::OrderedSet#merge when merging two empty sets' 14 | end 15 | 16 | context 'when merging a set with already present entries' do 17 | subject { ordered_set.merge([ entry ]) } 18 | 19 | let(:ordered_set) { set } 20 | let(:set) { DataMapper::OrderedSet.new([ entry ]) } 21 | let(:entry) { 1 } 22 | 23 | it_should_behave_like 'DataMapper::OrderedSet#merge when merging a set with already present entries' 24 | end 25 | 26 | context 'when merging a set with not yet present entries' do 27 | subject { ordered_set.merge([ entry2 ]) } 28 | 29 | let(:ordered_set) { set } 30 | let(:set) { DataMapper::OrderedSet.new([ entry1 ]) } 31 | let(:entry1) { 1 } 32 | let(:entry2) { 2 } 33 | 34 | it_should_behave_like 'DataMapper::OrderedSet#merge when merging a set with not yet present entries' 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/semipublic/property/boolean_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe DataMapper::Property::Boolean do 4 | before :all do 5 | @name = :active 6 | @type = described_class 7 | @value = true 8 | @other_value = false 9 | @invalid_value = 1 10 | end 11 | 12 | it_should_behave_like 'A semipublic Property' 13 | 14 | describe '#valid?' do 15 | [ true, false ].each do |value| 16 | it "returns true when value is #{value.inspect}" do 17 | @property.valid?(value).should be(true) 18 | end 19 | end 20 | 21 | [ 'true', 'TRUE', '1', 1, 't', 'T', 'false', 'FALSE', '0', 0, 'f', 'F' ].each do |value| 22 | it "returns false for #{value.inspect}" do 23 | @property.valid?(value).should be(false) 24 | end 25 | end 26 | end 27 | 28 | describe '#typecast' do 29 | [ true, 'true', 'TRUE', '1', 1, 't', 'T' ].each do |value| 30 | it "returns true when value is #{value.inspect}" do 31 | @property.typecast(value).should be(true) 32 | end 33 | end 34 | 35 | [ false, 'false', 'FALSE', '0', 0, 'f', 'F' ].each do |value| 36 | it "returns false when value is #{value.inspect}" do 37 | @property.typecast(value).should be(false) 38 | end 39 | end 40 | 41 | [ 'string', 2, 1.0, BigDecimal('1.0'), DateTime.now, Time.now, Date.today, Class, Object.new, ].each do |value| 42 | it "does not typecast value #{value.inspect}" do 43 | @property.typecast(value).should equal(value) 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/public/sel_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'SEL', 'with STI subclasses' do 4 | before :all do 5 | module ::Blog 6 | class Author 7 | include DataMapper::Resource 8 | 9 | property :id, Serial 10 | property :name, String 11 | 12 | has n, :messages 13 | end 14 | 15 | class Message 16 | include DataMapper::Resource 17 | 18 | property :id, Serial 19 | property :type, Discriminator 20 | property :title, String, :required => true 21 | 22 | belongs_to :author 23 | end 24 | 25 | class Article < Message; end 26 | class Comment < Message; end 27 | end 28 | 29 | DataMapper.finalize 30 | 31 | @author_model = Blog::Author 32 | @message_model = Blog::Message 33 | @article_model = Blog::Article 34 | @comment_model = Blog::Comment 35 | end 36 | 37 | supported_by :all do 38 | before :all do 39 | author1 = @author_model.create(:name => 'Dan Kubb') 40 | author2 = @author_model.create(:name => 'Sindre Aarsaether') 41 | 42 | @article_model.create(:title => 'SEL', :author => author1) 43 | @article_model.create(:title => 'STI', :author => author1) 44 | @comment_model.create(:title => 'SEL and STI error', :author => author2) 45 | end 46 | 47 | it 'should allow STI loading of mixed relationships' do 48 | lambda { 49 | @message_model.all.each { |message| message.author } 50 | }.should_not raise_error(ArgumentError) 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /spec/semipublic/property/time_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe DataMapper::Property::Time do 4 | before :all do 5 | @name = :deleted_at 6 | @type = described_class 7 | @value = Time.now 8 | @other_value = Time.now + 15 9 | @invalid_value = 1 10 | end 11 | 12 | it_should_behave_like 'A semipublic Property' 13 | 14 | describe '#typecast' do 15 | describe 'and value given as a hash with keys like :year, :month, etc' do 16 | it 'builds a Time instance from hash values' do 17 | result = @property.typecast( 18 | :year => '2006', 19 | :month => '11', 20 | :day => '23', 21 | :hour => '12', 22 | :min => '0', 23 | :sec => '0' 24 | ) 25 | 26 | result.should be_kind_of(Time) 27 | result.year.should eql(2006) 28 | result.month.should eql(11) 29 | result.day.should eql(23) 30 | result.hour.should eql(12) 31 | result.min.should eql(0) 32 | result.sec.should eql(0) 33 | end 34 | end 35 | 36 | describe 'and value is a string' do 37 | it 'parses the string' do 38 | result = @property.typecast('22:24') 39 | result.hour.should eql(22) 40 | result.min.should eql(24) 41 | end 42 | end 43 | 44 | it 'does not typecast non-time values' do 45 | pending_if 'Time#parse is too permissive', RUBY_VERSION <= '1.9.1' do 46 | @property.typecast('not-time').should eql('not-time') 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/subject_set/append_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'dm-core/support/subject_set' 3 | require 'unit/data_mapper/subject_set/shared/append_spec' 4 | 5 | describe 'DataMapper::SubjectSet#<<' do 6 | before :all do 7 | 8 | class ::Person 9 | attr_reader :name 10 | attr_reader :hobby 11 | def initialize(name, hobby) 12 | @name = name 13 | @hobby = hobby 14 | end 15 | end 16 | 17 | end 18 | 19 | before do 20 | @old_size = set.size 21 | @old_index = set.entries.index(entry1) 22 | end 23 | 24 | subject { set << entry2 } 25 | 26 | let(:set) { DataMapper::SubjectSet.new([ entry1 ]) } 27 | let(:entry1) { Person.new('snusnu', 'programming') } 28 | let(:entry2) { Person.new('snusnu', 'tabletennis') } 29 | 30 | context 'when appending a not yet included entry' do 31 | let(:entry2) { Person.new('Alice', 'cryptography') } 32 | 33 | it_should_behave_like 'DataMapper::SubjectSet#<< when appending a not yet included entry' 34 | end 35 | 36 | context 'when updating an entry with the same cache key' do 37 | context 'and the new entry is already included' do 38 | let(:entry2) { entry1 } 39 | 40 | it_should_behave_like 'DataMapper::SubjectSet#<< when updating an entry with the same cache key and the new entry is already included' 41 | end 42 | 43 | context 'and the new entry is not yet included' do 44 | it_should_behave_like 'DataMapper::SubjectSet#<< when updating an entry with the same cache key and the new entry is not yet included' 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/dm-core/spec/lib/pending_helpers.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | module Spec 3 | module PendingHelpers 4 | 5 | def pending_if(*args) 6 | message, boolean = parse_args(*args) 7 | 8 | if boolean 9 | pending(message) { yield } 10 | else 11 | yield 12 | end 13 | end 14 | 15 | def rescue_if(*args) 16 | message, boolean = parse_args(*args) 17 | 18 | if boolean 19 | raised = nil 20 | begin 21 | yield 22 | raised = false 23 | rescue Exception 24 | raised = true 25 | end 26 | 27 | raise "should have raised: #{message || 'TODO'}" if raised == false 28 | else 29 | yield 30 | end 31 | end 32 | 33 | private 34 | 35 | def parse_args(*args) 36 | case args.map { |arg| arg.class } 37 | when [ String, TrueClass ], [ String, FalseClass ] then args 38 | when [ String, NilClass ] then [ args.first, false ] 39 | when [ String ] then [ args.first, true ] 40 | when [ TrueClass ], [ FalseClass ] then [ '', args.first ] 41 | when [ NilClass ] then [ '', false ] 42 | when [] then [ '', true ] # defaults 43 | else 44 | raise ArgumentError, "Invalid arguments: #{args.inspect}" 45 | end 46 | end 47 | 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/dm-core/support/ext/module.rb: -------------------------------------------------------------------------------- 1 | module DataMapper; module Ext 2 | module Module 3 | 4 | # @api semipublic 5 | def self.find_const(mod, const_name) 6 | if const_name[0..1] == '::' 7 | DataMapper::Ext::Object.full_const_get(const_name[2..-1]) 8 | else 9 | nested_const_lookup(mod, const_name) 10 | end 11 | end 12 | 13 | private 14 | 15 | # Doesn't do any caching since constants can change with remove_const 16 | def self.nested_const_lookup(mod, const_name) 17 | unless mod.equal?(::Object) 18 | constants = [] 19 | 20 | mod.name.split('::').each do |part| 21 | const = constants.last || ::Object 22 | constants << const.const_get(part) 23 | end 24 | 25 | parts = const_name.split('::') 26 | 27 | # from most to least specific constant, use each as a base and try 28 | # to find a constant with the name const_name within them 29 | constants.reverse_each do |const| 30 | # return the nested constant if available 31 | return const if parts.all? do |part| 32 | const = if RUBY_VERSION >= '1.9.0' 33 | const.const_defined?(part, false) ? const.const_get(part, false) : nil 34 | else 35 | const.const_defined?(part) ? const.const_get(part) : nil 36 | end 37 | end 38 | end 39 | end 40 | 41 | # no relative constant found, fallback to an absolute lookup and 42 | # use const_missing if not found 43 | DataMapper::Ext::Object.full_const_get(const_name) 44 | end 45 | 46 | end 47 | end; end 48 | -------------------------------------------------------------------------------- /spec/public/associations/one_to_one_with_boolean_cpk_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | # TODO: combine this into one_to_one_spec.rb 4 | 5 | describe 'One to One Associations when foreign key is part of a composite key and contains a boolean, with an integer and a boolean making up the composite key' do 6 | before :all do 7 | class ::ParentModel 8 | include DataMapper::Resource 9 | 10 | property :integer_key, Integer, :key => true 11 | property :boolean_key, Boolean, :key => true 12 | 13 | has 1, :child_model, :child_key => [ :integer_key, :boolean_key ] 14 | end 15 | 16 | class ::ChildModel 17 | include DataMapper::Resource 18 | 19 | property :integer_key, Integer, :key => true 20 | property :other_integer_key, Integer, :key => true 21 | property :boolean_key, Boolean, :key => true 22 | 23 | belongs_to :parent_model, :child_key => [ :integer_key, :boolean_key ] 24 | end 25 | DataMapper.finalize 26 | end 27 | 28 | supported_by :all do 29 | before :all do 30 | @parent = ParentModel.create(:integer_key => 1, :boolean_key => false) 31 | @child = ChildModel.create(:integer_key => 1, :other_integer_key => 1, :boolean_key => false) 32 | end 33 | 34 | it 'should be able to access the child' do 35 | @parent.child_model.should == @child 36 | end 37 | 38 | it 'should be able to access the parent' do 39 | @child.parent_model.should == @parent 40 | end 41 | 42 | it 'should be able to access the parent_key' do 43 | @child.parent_model.key.should_not be_nil 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/subject_set/shared/values_at_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | shared_examples_for 'DataMapper::SubjectSet#values_at when one name is given and no entry with the given name is present' do 4 | its(:size) { should == given_names.size } 5 | 6 | it 'should contain nil values for the names not found' do 7 | subject.compact.should be_empty 8 | end 9 | end 10 | 11 | shared_examples_for 'DataMapper::SubjectSet#values_at when one name is given and an entry with the given name is present' do 12 | its(:size) { should == given_names.size } 13 | 14 | it { should include(entry1) } 15 | end 16 | 17 | shared_examples_for 'DataMapper::SubjectSet#values_at when more than one name is given and no entry with any of the given names is present' do 18 | its(:size) { should == given_names.size } 19 | 20 | it 'should contain nil values for the names not found' do 21 | subject.compact.should be_empty 22 | end 23 | end 24 | 25 | shared_examples_for 'DataMapper::SubjectSet#values_at when more than one name is given and one entry with one of the given names is present' do 26 | it { should include(entry1) } 27 | 28 | its(:size) { should == given_names.size } 29 | 30 | it 'should contain nil values for the names not found' do 31 | subject.compact.size.should == 1 32 | end 33 | end 34 | 35 | shared_examples_for 'DataMapper::SubjectSet#values_at when more than one name is given and an entry for every given name is present' do 36 | it { should include(entry1) } 37 | it { should include(entry2) } 38 | 39 | its(:size) { should == given_names.size } 40 | 41 | it 'should not contain any nil values' do 42 | subject.compact.size.should == given_names.size 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /spec/public/associations/many_to_many/read_multiple_join_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | describe 'Many to Many Associations read across multiple join associations' do 3 | before :all do 4 | class ::User 5 | include DataMapper::Resource 6 | 7 | property :id, Serial 8 | 9 | has n, :sales 10 | has n, :sale_items, :through => :sales 11 | has n, :items, :through => :sale_items 12 | end 13 | 14 | class ::Sale 15 | include DataMapper::Resource 16 | 17 | property :id, Serial 18 | 19 | belongs_to :user 20 | has n, :sale_items 21 | has n, :items, :through => :sale_items 22 | end 23 | 24 | class ::SaleItem 25 | include DataMapper::Resource 26 | 27 | property :id, Serial 28 | 29 | belongs_to :sale 30 | belongs_to :item 31 | end 32 | 33 | class ::Item 34 | include DataMapper::Resource 35 | 36 | property :id, Serial 37 | 38 | has n, :sale_items 39 | end 40 | 41 | DataMapper.finalize 42 | end 43 | 44 | supported_by :all do 45 | before :all do 46 | @user = User.create 47 | @sale = @user.sales.create 48 | 49 | 5.times { @sale.items.create } 50 | end 51 | 52 | before :all do 53 | @no_join = defined?(DataMapper::Adapters::InMemoryAdapter) && @adapter.kind_of?(DataMapper::Adapters::InMemoryAdapter) || 54 | defined?(DataMapper::Adapters::YamlAdapter) && @adapter.kind_of?(DataMapper::Adapters::YamlAdapter) 55 | 56 | @skip = @no_join 57 | end 58 | 59 | before do 60 | pending if @skip 61 | end 62 | 63 | it 'should return all the created entries' do 64 | @user.items.to_a.should == Item.all.to_a 65 | @sale.items.to_a.should == Item.all.to_a 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /spec/lib/rspec_immediate_feedback_formatter.rb: -------------------------------------------------------------------------------- 1 | require 'spec/runner/formatter/base_text_formatter' 2 | 3 | # Code is based on standard SpecdocFormatter, but will print full error details as soon as they are found. 4 | # Successful or pending examples are written only as a dot in the output. Header is only printed if errors occur. 5 | # 6 | # To use it, add the following to your spec/spec.opts: 7 | # --require 8 | # lib/rspec_immediate_feedback_formatter.rb 9 | # --format 10 | # Spec::Runner::Formatter::ImmediateFeedbackFormatter 11 | 12 | module Spec 13 | module Runner 14 | module Formatter 15 | class ImmediateFeedbackFormatter < BaseTextFormatter 16 | 17 | def add_example_group(example_group) 18 | super 19 | @current_group = example_group.description 20 | end 21 | 22 | def example_failed(example, counter, failure) 23 | if @current_group 24 | output.puts 25 | output.puts @current_group 26 | @current_group = nil # only print the group name once 27 | end 28 | 29 | message = if failure.expectation_not_met? 30 | "- #{example.description} (FAILED - #{counter})" 31 | else 32 | "- #{example.description} (ERROR - #{counter})" 33 | end 34 | 35 | output.puts(red(message)) 36 | # output.puts(failure.expectation_not_met? ? red(message) : message) 37 | dump_failure(counter, failure) # dump stacktrace immediately 38 | output.flush 39 | end 40 | 41 | def example_passed(*) 42 | output.print green('.') 43 | output.flush 44 | end 45 | 46 | def example_pending(*) 47 | super 48 | output.print yellow('*') 49 | output.flush 50 | end 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/dm-core/resource/persistence_state.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | module Resource 3 | 4 | # the state of the resource (abstract) 5 | class PersistenceState 6 | extend Equalizer 7 | 8 | equalize :resource 9 | 10 | attr_reader :resource 11 | 12 | def initialize(resource) 13 | @resource = resource 14 | @model = resource.model 15 | end 16 | 17 | def get(subject, *args) 18 | subject.get(resource, *args) 19 | end 20 | 21 | def set(subject, value) 22 | subject.set(resource, value) 23 | self 24 | end 25 | 26 | def delete 27 | raise NotImplementedError, "#{self.class}#delete should be implemented" 28 | end 29 | 30 | def commit 31 | raise NotImplementedError, "#{self.class}#commit should be implemented" 32 | end 33 | 34 | def rollback 35 | raise NotImplementedError, "#{self.class}#rollback should be implemented" 36 | end 37 | 38 | private 39 | 40 | attr_reader :model 41 | 42 | def properties 43 | @properties ||= model.properties(repository.name) 44 | end 45 | 46 | def relationships 47 | @relationships ||= model.relationships(repository.name) 48 | end 49 | 50 | def identity_map 51 | @identity_map ||= repository.identity_map(model) 52 | end 53 | 54 | def remove_from_identity_map 55 | identity_map.delete(resource.key) 56 | end 57 | 58 | def add_to_identity_map 59 | identity_map[resource.key] = resource 60 | end 61 | 62 | def set_child_keys 63 | relationships.each do |relationship| 64 | set_child_key(relationship) 65 | end 66 | end 67 | 68 | def set_child_key(relationship) 69 | return unless relationship.loaded?(resource) && relationship.respond_to?(:resource_for) 70 | set(relationship, get(relationship)) 71 | end 72 | 73 | end # class PersistenceState 74 | end # module Resource 75 | end # module DataMapper 76 | -------------------------------------------------------------------------------- /spec/public/property/text_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe DataMapper::Property::Text do 4 | before :all do 5 | @name = :title 6 | @type = described_class 7 | @load_as = String 8 | @value = 'value' 9 | @other_value = 'return value' 10 | @invalid_value = 1 11 | end 12 | 13 | it_should_behave_like 'A public Property' 14 | 15 | describe '.options' do 16 | subject { described_class.options } 17 | 18 | it { should be_kind_of(Hash) } 19 | 20 | it { should eql(:load_as => @load_as, :dump_as => @load_as, :coercion_method => :to_string, :length => 65535, :lazy => true) } 21 | end 22 | 23 | describe 'migration with an index' do 24 | supported_by :all do 25 | before do 26 | Object.send(:remove_const, :Foo) if Object.const_defined?(:Foo) 27 | @model = DataMapper::Model.new('Foo') do 28 | storage_names[:default] = 'anonymous' 29 | 30 | property :id, DataMapper::Property::Serial 31 | property :body, DataMapper::Property::Text, :index => true 32 | end 33 | end 34 | 35 | it 'should allow a migration' do 36 | lambda { 37 | @model.auto_migrate! 38 | }.should_not raise_error(DataObjects::SyntaxError) 39 | end 40 | end 41 | end if defined?(DataObjects::SyntaxError) 42 | 43 | describe 'migration with a unique index' do 44 | supported_by :all do 45 | before do 46 | 47 | Object.send(:remove_const, :Foo) if Object.const_defined?(:Foo) 48 | @model = DataMapper::Model.new('Foo') do 49 | storage_names[:default] = 'anonymous' 50 | 51 | property :id, DataMapper::Property::Serial 52 | property :body, DataMapper::Property::Text, :unique_index => true 53 | end 54 | end 55 | 56 | it 'should allow a migration' do 57 | lambda { 58 | @model.auto_migrate! 59 | }.should_not raise_error(DataObjects::SyntaxError) 60 | end 61 | end 62 | end if defined?(DataObjects::SyntaxError) 63 | end 64 | -------------------------------------------------------------------------------- /spec/semipublic/associations/one_to_one_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'One to One Associations' do 4 | before :all do 5 | module ::Blog 6 | class Article 7 | include DataMapper::Resource 8 | 9 | property :title, String, :key => true 10 | property :body, Text, :required => true 11 | end 12 | 13 | class Author 14 | include DataMapper::Resource 15 | 16 | property :name, String, :key => true 17 | 18 | belongs_to :without_default, 'Article', :child_key => [ :without_default_id ], :required => false 19 | belongs_to :with_default, 'Article', :child_key => [ :with_default_id ], :required => false 20 | belongs_to :with_default_callable, 'Article', :child_key => [ :with_default_callable_id ], :required => false 21 | end 22 | end 23 | 24 | @article_model = Blog::Article 25 | @author_model = Blog::Author 26 | 27 | DataMapper.finalize 28 | 29 | @default_value = @author_model.new(:name => 'Dan Kubb') 30 | @default_value_callable = @author_model.new(:name => 'John Doe') 31 | 32 | @subject_without_default = @article_model.has(1, :without_default, @author_model, :child_key => [ :without_default_id ]) 33 | @subject_with_default = @article_model.has(1, :with_default, @author_model, :child_key => [ :with_default_id ], :default => @default_value) 34 | @subject_with_default_callable = @article_model.has(1, :with_default_callable, @author_model, :child_key => [ :with_default_callable_id ], :default => lambda { |resource, relationship| @default_value_callable }) 35 | 36 | DataMapper.finalize 37 | end 38 | 39 | supported_by :all do 40 | before :all do 41 | @default_value.save 42 | @default_value_callable.save 43 | end 44 | 45 | describe 'acts like a subject' do 46 | before do 47 | @resource = @article_model.new(:title => 'DataMapper Rocks!', :body => 'TSIA') 48 | end 49 | 50 | it_should_behave_like 'A semipublic Subject' 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | require File.expand_path('../lib/dm-core/version', __FILE__) 2 | 3 | require 'pathname' 4 | 5 | source :rubygems 6 | 7 | gemspec 8 | 9 | gem 'virtus', '~> 0.5', :git => 'https://github.com/solnic/virtus' 10 | 11 | SOURCE = ENV.fetch('SOURCE', :git).to_sym 12 | REPO_POSTFIX = SOURCE == :path ? '' : '.git' 13 | DATAMAPPER = SOURCE == :path ? Pathname(__FILE__).dirname.parent : 'https://github.com/datamapper' 14 | DM_VERSION = "~> #{DataMapper::VERSION}" 15 | DO_VERSION = '~> 0.10.6' 16 | DM_DO_ADAPTERS = %w[ sqlite postgres mysql oracle sqlserver ] 17 | CURRENT_BRANCH = ENV.fetch('GIT_BRANCH', 'master') 18 | 19 | platforms :mri_18 do 20 | group :quality do 21 | 22 | gem 'rcov', '~> 0.9.10' 23 | gem 'yard', '~> 0.7.2' 24 | gem 'yardstick', '~> 0.4' 25 | 26 | end 27 | end 28 | 29 | group :datamapper do 30 | 31 | adapters = ENV['ADAPTERS'] || ENV['ADAPTER'] 32 | adapters = adapters.to_s.tr(',', ' ').split.uniq - %w[ in_memory ] 33 | 34 | if (do_adapters = DM_DO_ADAPTERS & adapters).any? 35 | do_options = {} 36 | do_options[:git] = "#{DATAMAPPER}/do#{REPO_POSTFIX}" if ENV['DO_GIT'] == 'true' 37 | 38 | gem 'data_objects', DO_VERSION, do_options.dup 39 | 40 | do_adapters.each do |adapter| 41 | adapter = 'sqlite3' if adapter == 'sqlite' 42 | gem "do_#{adapter}", DO_VERSION, do_options.dup 43 | end 44 | 45 | gem 'dm-do-adapter', DM_VERSION, 46 | SOURCE => "#{DATAMAPPER}/dm-do-adapter#{REPO_POSTFIX}", 47 | :branch => CURRENT_BRANCH 48 | end 49 | 50 | adapters.each do |adapter| 51 | gem "dm-#{adapter}-adapter", ENV.fetch('ADAPTER_VERSION', DM_VERSION), 52 | SOURCE => "#{DATAMAPPER}/dm-#{adapter}-adapter#{REPO_POSTFIX}", 53 | :branch => CURRENT_BRANCH 54 | end 55 | 56 | plugins = ENV['PLUGINS'] || ENV['PLUGIN'] 57 | plugins = plugins.to_s.tr(',', ' ').split.push('dm-migrations').uniq 58 | 59 | plugins.each do |plugin| 60 | gem plugin, DM_VERSION, 61 | SOURCE => "#{DATAMAPPER}/#{plugin}#{REPO_POSTFIX}", 62 | :branch => CURRENT_BRANCH 63 | end 64 | 65 | end 66 | -------------------------------------------------------------------------------- /lib/dm-core/support/descendant_set.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | class DescendantSet 3 | include Enumerable 4 | 5 | # Initialize a DescendantSet instance 6 | # 7 | # @param [#to_ary] descendants 8 | # initialize with the descendants 9 | # 10 | # @api private 11 | def initialize(descendants = []) 12 | @descendants = SubjectSet.new(descendants) 13 | end 14 | 15 | # Copy a DescendantSet instance 16 | # 17 | # @param [DescendantSet] original 18 | # the original descendants 19 | # 20 | # @api private 21 | def initialize_copy(original) 22 | @descendants = @descendants.dup 23 | end 24 | 25 | # Add a descendant 26 | # 27 | # @param [Module] descendant 28 | # 29 | # @return [DescendantSet] 30 | # self 31 | # 32 | # @api private 33 | def <<(descendant) 34 | @descendants << descendant 35 | self 36 | end 37 | 38 | # Remove a descendant 39 | # 40 | # Also removes from all descendants 41 | # 42 | # @param [Module] descendant 43 | # 44 | # @return [DescendantSet] 45 | # self 46 | # 47 | # @api private 48 | def delete(descendant) 49 | @descendants.delete(descendant) 50 | each { |d| d.descendants.delete(descendant) } 51 | end 52 | 53 | # Iterate over each descendant 54 | # 55 | # @yield [descendant] 56 | # @yieldparam [Module] descendant 57 | # 58 | # @return [DescendantSet] 59 | # self 60 | # 61 | # @api private 62 | def each 63 | @descendants.each do |descendant| 64 | yield descendant 65 | descendant.descendants.each { |dd| yield dd } 66 | end 67 | self 68 | end 69 | 70 | # Test if there are any descendants 71 | # 72 | # @return [Boolean] 73 | # 74 | # @api private 75 | def empty? 76 | @descendants.empty? 77 | end 78 | 79 | # Removes all entries and returns self 80 | # 81 | # @return [DescendantSet] self 82 | # 83 | # @api private 84 | def clear 85 | @descendants.clear 86 | end 87 | 88 | end # class DescendantSet 89 | end # module DataMapper 90 | -------------------------------------------------------------------------------- /lib/dm-core/resource/persistence_state/transient.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | module Resource 3 | class PersistenceState 4 | 5 | # a not-persisted/modifiable resource 6 | class Transient < PersistenceState 7 | def get(subject, *args) 8 | set_default_value(subject) 9 | super 10 | end 11 | 12 | def set(subject, value) 13 | track(subject) 14 | super 15 | end 16 | 17 | def delete 18 | self 19 | end 20 | 21 | def commit 22 | set_child_keys 23 | set_default_values 24 | assert_valid_attributes 25 | create_resource 26 | set_repository 27 | add_to_identity_map 28 | Clean.new(resource) 29 | end 30 | 31 | def rollback 32 | self 33 | end 34 | 35 | def original_attributes 36 | @original_attributes ||= {} 37 | end 38 | 39 | private 40 | 41 | def repository 42 | @repository ||= model.repository 43 | end 44 | 45 | def set_default_values 46 | (properties | relationships).each do |subject| 47 | set_default_value(subject) 48 | end 49 | end 50 | 51 | def set_default_value(subject) 52 | return if subject.loaded?(resource) || !subject.default? 53 | set(subject, subject.default_for(resource)) 54 | end 55 | 56 | def track(subject) 57 | original_attributes[subject] = nil 58 | end 59 | 60 | def create_resource 61 | repository.create([ resource ]) 62 | end 63 | 64 | def set_repository 65 | resource.instance_variable_set(:@_repository, repository) 66 | end 67 | 68 | def assert_valid_attributes 69 | properties.each do |property| 70 | value = get(property) 71 | unless property.serial? && value.nil? 72 | property.assert_valid_value(value) 73 | end 74 | end 75 | end 76 | 77 | end # class Transient 78 | end # class PersistenceState 79 | end # module Resource 80 | end # module DataMapper 81 | -------------------------------------------------------------------------------- /lib/dm-core/support/ext/hash.rb: -------------------------------------------------------------------------------- 1 | module DataMapper; module Ext 2 | module Hash 3 | # Creates a hash with *only* the specified key/value pairs from +hash+. 4 | # 5 | # @param [Hash] hash The hash from which to pick the key/value pairs. 6 | # @param [Array] *keys The hash keys to include. 7 | # 8 | # @return [Hash] A new hash with only the selected keys. 9 | # 10 | # @example 11 | # hash = { :one => 1, :two => 2, :three => 3 } 12 | # Ext::Hash.only(hash, :one, :two) # => { :one => 1, :two => 2 } 13 | # 14 | # @api semipublic 15 | def self.only(hash, *keys) 16 | h = {} 17 | keys.each {|k| h[k] = hash[k] if hash.has_key?(k) } 18 | h 19 | end 20 | 21 | # Returns a hash that includes everything but the given +keys+. 22 | # 23 | # @param [Hash] hash The hash from which to pick the key/value pairs. 24 | # @param [Array] *keys The hash keys to exclude. 25 | # 26 | # @return [Hash] A new hash without the specified keys. 27 | # 28 | # @example 29 | # hash = { :one => 1, :two => 2, :three => 3 } 30 | # Ext::Hash.except(hash, :one, :two) # => { :three => 3 } 31 | # 32 | # @api semipublic 33 | def self.except(hash, *keys) 34 | self.except!(hash.dup, *keys) 35 | end 36 | 37 | # Removes the specified +keys+ from the given +hash+. 38 | # 39 | # @param [Hash] hash The hash to modify. 40 | # @param [Array] *keys The hash keys to exclude. 41 | # 42 | # @return [Hash] +hash+ 43 | # 44 | # @example 45 | # hash = { :one => 1, :two => 2, :three => 3 } 46 | # Ext::Hash.except!(hash, :one, :two) 47 | # hash # => { :three => 3 } 48 | # 49 | # @api semipublic 50 | def self.except!(hash, *keys) 51 | keys.each { |key| hash.delete(key) } 52 | hash 53 | end 54 | 55 | # Converts the specified +hash+ to a {Mash}. 56 | # 57 | # @param [Hash] hash The hash to convert. 58 | # @return [Mash] The {Mash} for the specified +hash+. 59 | # 60 | # @api semipublic 61 | def self.to_mash(hash) 62 | h = Mash.new(hash) 63 | h.default = hash.default 64 | h 65 | end 66 | end 67 | end; end 68 | -------------------------------------------------------------------------------- /spec/semipublic/associations/one_to_many_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'One to Many Associations' do 4 | before :all do 5 | module ::Blog 6 | class Article 7 | include DataMapper::Resource 8 | 9 | property :title, String, :key => true 10 | property :body, Text, :required => true 11 | end 12 | 13 | class Author 14 | include DataMapper::Resource 15 | 16 | property :name, String, :key => true 17 | 18 | belongs_to :without_default, 'Article', :child_key => [ :without_default_id ], :required => false 19 | belongs_to :with_default, 'Article', :child_key => [ :with_default_id ], :required => false 20 | belongs_to :with_default_callable, 'Article', :child_key => [ :with_default_callable_id ], :required => false 21 | end 22 | end 23 | 24 | @article_model = Blog::Article 25 | @author_model = Blog::Author 26 | 27 | DataMapper.finalize 28 | 29 | n = @article_model.n 30 | 31 | @default_value = [ @author_model.new(:name => 'Dan Kubb') ] 32 | @default_value_callable = [ @author_model.new(:name => 'John Doe') ] 33 | 34 | @subject_without_default = @article_model.has(n, :without_default, @author_model, :child_key => [ :without_default_id ]) 35 | @subject_with_default = @article_model.has(n, :with_default, @author_model, :child_key => [ :with_default_id ], :default => @default_value) 36 | @subject_with_default_callable = @article_model.has(n, :with_default_callable, @author_model, :child_key => [ :with_default_callable_id ], :default => lambda { |resource, relationship| @default_value_callable }) 37 | 38 | DataMapper.finalize 39 | end 40 | 41 | supported_by :all do 42 | before :all do 43 | @default_value.each { |resource| resource.save } 44 | @default_value_callable.each { |resource| resource.save } 45 | end 46 | 47 | describe 'acts like a subject' do 48 | before do 49 | @resource = @article_model.new(:title => 'DataMapper Rocks!', :body => 'TSIA') 50 | end 51 | 52 | it_should_behave_like 'A semipublic Subject' 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /spec/semipublic/associations/many_to_one_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'Many to One Associations' do 4 | before :all do 5 | class ::User 6 | include DataMapper::Resource 7 | 8 | property :name, String, :key => true 9 | property :age, Integer 10 | property :description, Text 11 | 12 | has n, :comments 13 | end 14 | 15 | class ::Comment 16 | include DataMapper::Resource 17 | 18 | property :id, Serial 19 | 20 | belongs_to :user 21 | end 22 | 23 | @user_model = User 24 | @comment_model = Comment 25 | 26 | DataMapper.finalize 27 | 28 | @default_value = @user_model.new(:name => 'dkubb', :age => 34, :description => 'Test') 29 | @default_value_callable = @user_model.new(:name => 'jdoe', :age => 21, :description => 'Test') 30 | 31 | @subject_without_default = @user_model.belongs_to(:without_default, @user_model, :required => false, :child_key => [ :without_default_id ]) 32 | @subject_with_default = @user_model.belongs_to(:with_default, @user_model, :required => false, :child_key => [ :with_default_id ], :default => @default_value) 33 | @subject_with_default_callable = @user_model.belongs_to(:with_default_callable, @user_model, :required => false, :child_key => [ :with_default_callable_id ], :default => lambda { |resource, relationship| @default_value_callable }) 34 | 35 | @default_value.with_default = nil 36 | @default_value.with_default_callable = nil 37 | 38 | DataMapper.finalize 39 | end 40 | 41 | supported_by :all do 42 | before :all do 43 | @default_value.save 44 | @default_value_callable.save 45 | end 46 | 47 | before :all do 48 | comment = @comment_model.create(:user => { :name => 'dbussink', :age => 25, :description => 'Test' }) 49 | 50 | @user = @comment_model.get(*comment.key).user 51 | end 52 | 53 | it_should_behave_like 'A semipublic Resource' 54 | 55 | describe 'acts like a subject' do 56 | before do 57 | @resource = @user_model.new(:name => 'A subject') 58 | end 59 | 60 | it_should_behave_like 'A semipublic Subject' 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /lib/dm-core/spec/lib/adapter_helpers.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | module Spec 3 | module Adapters 4 | 5 | module Helpers 6 | 7 | def supported_by(*adapters, &block) 8 | adapters = adapters.map { |adapter| adapter.to_sym } 9 | adapter = DataMapper::Spec.adapter_name.to_sym 10 | if adapters.include?(:all) || adapters.include?(adapter) 11 | describe_adapter(:default, &block) 12 | end 13 | end 14 | 15 | def with_alternate_adapter(&block) 16 | describe_adapter(:alternate, &block) 17 | end 18 | 19 | def describe_adapter(kind, &block) 20 | describe("with #{kind} adapter") do 21 | 22 | before :all do 23 | # store these in instance vars for the shared adapter specs 24 | @adapter = DataMapper::Spec.adapter(kind) 25 | @repository = DataMapper.repository(@adapter.name) 26 | 27 | @repository.scope { DataMapper.finalize } 28 | 29 | # create all tables and constraints before each spec 30 | DataMapper::Model.descendants.each do |model| 31 | next unless model.respond_to?(:auto_migrate!) 32 | begin 33 | model.auto_migrate!(@repository.name) 34 | rescue IncompleteModelError 35 | # skip incomplete models 36 | end 37 | end 38 | end 39 | 40 | after :all do 41 | # remove all tables and constraints after each spec 42 | DataMapper::Model.descendants.each do |model| 43 | next unless model.respond_to?(:auto_migrate_down!) 44 | begin 45 | model.auto_migrate_down!(@repository.name) 46 | rescue IncompleteModelError 47 | # skip incomplete models 48 | end 49 | end 50 | # TODO consider proper automigrate functionality 51 | if @adapter.respond_to?(:reset) 52 | @adapter.reset 53 | end 54 | end 55 | 56 | instance_eval(&block) 57 | end 58 | end 59 | 60 | end 61 | 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /spec/unit/blank_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'dm-core/support/ext/blank' 3 | 4 | describe 'DataMapper::Ext.blank?', Object do 5 | it 'should be blank if it is nil' do 6 | object = Object.new 7 | class << object 8 | def nil?; true end 9 | end 10 | DataMapper::Ext.blank?(object).should == true 11 | end 12 | 13 | it 'should be blank if it is empty' do 14 | DataMapper::Ext.blank?({}).should == true 15 | DataMapper::Ext.blank?([]).should == true 16 | end 17 | 18 | it 'should not be blank if not nil or empty' do 19 | DataMapper::Ext.blank?(Object.new).should == false 20 | DataMapper::Ext.blank?([nil]).should == false 21 | DataMapper::Ext.blank?({ nil => 0 }).should == false 22 | end 23 | end 24 | 25 | describe 'DataMapper::Ext.blank?', Numeric do 26 | it 'should never be blank' do 27 | DataMapper::Ext.blank?(1).should == false 28 | end 29 | end 30 | 31 | describe 'DataMapper::Ext.blank?', NilClass do 32 | it 'should always be blank' do 33 | DataMapper::Ext.blank?(nil).should == true 34 | end 35 | end 36 | 37 | describe 'DataMapper::Ext.blank?', TrueClass do 38 | it 'should never be blank' do 39 | DataMapper::Ext.blank?(true).should == false 40 | end 41 | end 42 | 43 | describe 'DataMapper::Ext.blank?', FalseClass do 44 | it 'should always be blank' do 45 | DataMapper::Ext.blank?(false).should == true 46 | end 47 | end 48 | 49 | describe 'DataMapper::Ext.blank?', String do 50 | it 'should be blank if empty' do 51 | DataMapper::Ext.blank?('').should == true 52 | end 53 | 54 | it 'should be blank if it only contains whitespace' do 55 | DataMapper::Ext.blank?(' ').should == true 56 | DataMapper::Ext.blank?(" \r \n \t ").should == true 57 | end 58 | 59 | it 'should not be blank if it contains non-whitespace' do 60 | DataMapper::Ext.blank?(' a ').should == false 61 | end 62 | end 63 | 64 | describe 'DataMapper::Ext.blank?', 'object with #blank?' do 65 | subject { DataMapper::Ext.blank?(object) } 66 | 67 | let(:return_value) { mock('Return Value') } 68 | let(:object) { mock('Object', :blank? => return_value) } 69 | 70 | it 'returns the object#blank? result if supported' do 71 | should equal(return_value) 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /spec/semipublic/resource/state/deleted_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | describe DataMapper::Resource::PersistenceState::Deleted do 3 | before :all do 4 | class ::Author 5 | include DataMapper::Resource 6 | 7 | property :id, HugeInteger, :key => true, :default => 1 8 | property :name, String 9 | property :active, Boolean, :default => true 10 | property :coding, Boolean, :default => true 11 | 12 | belongs_to :parent, self, :required => false 13 | has n, :children, self, :inverse => :parent 14 | end 15 | 16 | DataMapper.finalize 17 | @model = Author 18 | end 19 | 20 | before do 21 | @resource = @model.create(:name => 'Dan Kubb') 22 | 23 | @state = DataMapper::Resource::PersistenceState::Deleted.new(@resource) 24 | end 25 | 26 | after do 27 | @model.destroy! 28 | end 29 | 30 | describe '#commit' do 31 | subject { @state.commit } 32 | 33 | supported_by :all do 34 | it 'should return an Immutable state' do 35 | should eql(DataMapper::Resource::PersistenceState::Immutable.new(@resource)) 36 | end 37 | 38 | it 'should delete the resource' do 39 | subject 40 | @model.get(*@resource.key).should be_nil 41 | end 42 | 43 | it 'should remove the resource from the identity map' do 44 | identity_map = @resource.repository.identity_map(@model) 45 | method(:subject).should change { identity_map.dup }.from(@resource.key => @resource).to({}) 46 | end 47 | end 48 | end 49 | 50 | describe '#delete' do 51 | subject { @state.delete } 52 | 53 | supported_by :all do 54 | it 'should be a no-op' do 55 | should equal(@state) 56 | end 57 | end 58 | end 59 | 60 | describe '#get' do 61 | it_should_behave_like 'Resource::PersistenceState::Persisted#get' 62 | end 63 | 64 | describe '#set' do 65 | subject { @state.set(@key, @value) } 66 | 67 | supported_by :all do 68 | before do 69 | @key = @model.properties[:name] 70 | @value = @key.get!(@resource) 71 | end 72 | 73 | it 'should raise an exception' do 74 | method(:subject).should raise_error(DataMapper::ImmutableDeletedError, 'Deleted resource cannot be modified') 75 | end 76 | end 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/subject_set/values_at_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'dm-core/support/subject_set' 3 | require 'unit/data_mapper/subject_set/shared/values_at_spec' 4 | 5 | describe 'DataMapper::SubjectSet#values_at' do 6 | before :all do 7 | 8 | class ::Person 9 | attr_reader :name 10 | def initialize(name) 11 | @name = name 12 | end 13 | end 14 | 15 | end 16 | 17 | subject { set.values_at(*given_names) } 18 | 19 | let(:set) { DataMapper::SubjectSet.new(entries) } 20 | let(:entry1) { Person.new('Alice') } 21 | let(:entry2) { Person.new('Bob' ) } 22 | 23 | context 'when one name is given and no entry with the given name is present' do 24 | let(:given_names) { [ 'Alice' ] } 25 | let(:entries) { [] } 26 | 27 | it_should_behave_like 'DataMapper::SubjectSet#values_at when one name is given and no entry with the given name is present' 28 | end 29 | 30 | context 'when one name is given and an entry with the given name is present' do 31 | let(:given_names) { [ 'Alice' ] } 32 | let(:entries) { [ entry1 ] } 33 | 34 | it_should_behave_like 'DataMapper::SubjectSet#values_at when one name is given and an entry with the given name is present' 35 | end 36 | 37 | context 'when more than one name is given and no entry with any of the given names is present' do 38 | let(:given_names) { [ 'Alice', 'Bob' ] } 39 | let(:entries) { [] } 40 | 41 | it_should_behave_like 'DataMapper::SubjectSet#values_at when more than one name is given and no entry with any of the given names is present' 42 | end 43 | 44 | context 'when more than one name is given and one entry with one of the given names is present' do 45 | let(:given_names) { [ 'Alice', 'Bob' ] } 46 | let(:entries) { [ entry1 ] } 47 | 48 | it_should_behave_like 'DataMapper::SubjectSet#values_at when more than one name is given and one entry with one of the given names is present' 49 | end 50 | 51 | context 'when more than one name is given and an entry for every given name is present' do 52 | let(:given_names) { [ 'Alice', 'Bob' ] } 53 | let(:entries) { [ entry1, entry2 ] } 54 | 55 | it_should_behave_like 'DataMapper::SubjectSet#values_at when more than one name is given and an entry for every given name is present' 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /spec/unit/data_mapper/ordered_set/equal_value_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'dm-core/support/ordered_set' 3 | 4 | module DataMapper::Specs 5 | 6 | # Used to test duck typing behavior 7 | class OrderedSetDuck 8 | attr_reader :entries 9 | 10 | def initialize(columns = []) 11 | @entries = DataMapper::OrderedSet.new 12 | end 13 | end 14 | end 15 | 16 | describe 'DataMapper::OrderedSet#==' do 17 | subject { ordered_set == other } 18 | 19 | let(:original_entry) { 1 } 20 | let(:ordered_set) { DataMapper::OrderedSet.new([ original_entry ]) } 21 | 22 | context 'with the same ordered_set' do 23 | let(:other) { ordered_set } 24 | 25 | it { should be(true) } 26 | 27 | it 'is symmetric' do 28 | should == (other == ordered_set) 29 | end 30 | end 31 | 32 | context 'with equivalent ordered_set' do 33 | let(:other) { ordered_set.dup } 34 | 35 | it { should be(true) } 36 | 37 | it 'is symmetric' do 38 | should == (other == ordered_set) 39 | end 40 | end 41 | 42 | # TODO This probably needs more thought 43 | context 'with a class that quacks like OrderedSet and is equivalent otherwise' do 44 | let(:other) { DataMapper::Specs::OrderedSetDuck.new([ original_entry ]) } 45 | 46 | it { should be(false) } 47 | 48 | it 'is symmetric' do 49 | should == (other == ordered_set) 50 | end 51 | end 52 | 53 | context 'with a subclass that is equivalent otherwise' do 54 | let(:other) { Class.new(DataMapper::OrderedSet).new([ original_entry ]) } 55 | 56 | it { should be(true) } 57 | 58 | it 'is symmetric' do 59 | should == (other == ordered_set) 60 | end 61 | end 62 | 63 | context 'with both containing no ordered_set' do 64 | let(:ordered_set) { DataMapper::OrderedSet.new } 65 | let(:other) { DataMapper::OrderedSet.new } 66 | 67 | it { should be(true) } 68 | 69 | it 'is symmetric' do 70 | should == (other == ordered_set) 71 | end 72 | end 73 | 74 | context 'with different ordered_set' do 75 | let(:different_entry) { 2 } 76 | let(:other) { DataMapper::OrderedSet.new([ different_entry ]) } 77 | 78 | it { should be(false) } 79 | 80 | it 'is symmetric' do 81 | should == (other == ordered_set) 82 | end 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /spec/public/associations/many_to_one_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'Many to One Associations' do 4 | before :all do 5 | module ::Blog 6 | class User 7 | include DataMapper::Resource 8 | 9 | property :name, String, :key => true 10 | property :age, Integer 11 | property :summary, Text 12 | property :description, Text 13 | property :admin, Boolean, :accessor => :private 14 | 15 | belongs_to :parent, self, :required => false 16 | has n, :children, self, :inverse => :parent 17 | 18 | belongs_to :referrer, self, :required => false 19 | has n, :comments 20 | 21 | # FIXME: figure out a different approach than stubbing things out 22 | def comment=(*) 23 | # do nothing with comment 24 | end 25 | end 26 | 27 | class Author < User; end 28 | 29 | class Comment 30 | include DataMapper::Resource 31 | 32 | property :id, Serial 33 | property :body, Text 34 | 35 | belongs_to :user 36 | end 37 | 38 | class Article 39 | include DataMapper::Resource 40 | 41 | property :id, Serial 42 | property :body, Text 43 | 44 | has n, :paragraphs 45 | end 46 | 47 | class Paragraph 48 | include DataMapper::Resource 49 | 50 | property :id, Serial 51 | property :text, String 52 | 53 | belongs_to :article 54 | end 55 | end 56 | 57 | class ::Default 58 | include DataMapper::Resource 59 | 60 | property :name, String, :key => true, :default => 'a default value' 61 | end 62 | DataMapper.finalize 63 | 64 | @user_model = Blog::User 65 | @author_model = Blog::Author 66 | @comment_model = Blog::Comment 67 | @article_model = Blog::Article 68 | @paragraph_model = Blog::Paragraph 69 | end 70 | 71 | supported_by :all do 72 | before :all do 73 | user = @user_model.create(:name => 'dbussink', :age => 25, :description => 'Test') 74 | comment = @comment_model.create(:body => 'Cool spec', :user => user) 75 | 76 | @comment = @comment_model.get(*comment.key) 77 | @user = @comment.user 78 | end 79 | 80 | it_should_behave_like 'A public Resource' 81 | it_should_behave_like 'A Resource supporting Strategic Eager Loading' 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /lib/dm-core/support/ext/object.rb: -------------------------------------------------------------------------------- 1 | module DataMapper; module Ext 2 | module Object 3 | # Returns the value of the specified constant. 4 | # 5 | # @overload full_const_get(obj, name) 6 | # Returns the value of the specified constant in +obj+. 7 | # @param [Object] obj The root object used as origin. 8 | # @param [String] name The name of the constant to get, e.g. "Merb::Router". 9 | # 10 | # @overload full_const_get(name) 11 | # Returns the value of the fully-qualified constant. 12 | # @param [String] name The name of the constant to get, e.g. "Merb::Router". 13 | # 14 | # @return [Object] The constant corresponding to +name+. 15 | # 16 | # @api semipublic 17 | def self.full_const_get(obj, name = nil) 18 | obj, name = ::Object, obj if name.nil? 19 | 20 | list = name.split("::") 21 | list.shift if DataMapper::Ext.blank?(list.first) 22 | list.each do |x| 23 | # This is required because const_get tries to look for constants in the 24 | # ancestor chain, but we only want constants that are HERE 25 | obj = obj.const_defined?(x) ? obj.const_get(x) : obj.const_missing(x) 26 | end 27 | obj 28 | end 29 | 30 | # Sets the specified constant to the given +value+. 31 | # 32 | # @overload full_const_set(obj, name) 33 | # Sets the specified constant in +obj+ to the given +value+. 34 | # @param [Object] obj The root object used as origin. 35 | # @param [String] name The name of the constant to set, e.g. "Merb::Router". 36 | # @param [Object] value The value to assign to the constant. 37 | # 38 | # @overload full_const_set(name) 39 | # Sets the fully-qualified constant to the given +value+. 40 | # @param [String] name The name of the constant to set, e.g. "Merb::Router". 41 | # @param [Object] value The value to assign to the constant. 42 | # 43 | # @return [Object] The constant corresponding to +name+. 44 | # 45 | # @api semipublic 46 | def self.full_const_set(obj, name, value = nil) 47 | obj, name, value = ::Object, obj, name if value.nil? 48 | 49 | list = name.split("::") 50 | toplevel = DataMapper::Ext.blank?(list.first) 51 | list.shift if toplevel 52 | last = list.pop 53 | obj = list.empty? ? ::Object : DataMapper::Ext::Object.full_const_get(list.join("::")) 54 | obj.const_set(last, value) if obj && !obj.const_defined?(last) 55 | end 56 | end 57 | end; end 58 | -------------------------------------------------------------------------------- /lib/dm-core/relationship_set.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | 3 | # A {SubjectSet} that keeps track of relationships defined in a {Model} 4 | # 5 | class RelationshipSet < SubjectSet 6 | 7 | # A list of all relationships in this set 8 | # 9 | # @deprecated use DataMapper::RelationshipSet#each or DataMapper::RelationshipSet#to_a instead 10 | # 11 | # @return [Array] 12 | # a list of all relationships in the set 13 | # 14 | # @api semipublic 15 | def values 16 | warn "#{self.class}#values is deprecated. Use #{self.class}#each or #{self.class}#to_a instead: #{caller.first}" 17 | to_a 18 | end 19 | 20 | # A list of all relationships in this set 21 | # 22 | # @deprecated use DataMapper::RelationshipSet#each instead 23 | # 24 | # @yield [DataMapper::Associations::Relationship] 25 | # all relationships in the set 26 | # 27 | # @yieldparam [DataMapper::Associations::Relationship] relationship 28 | # a relationship in the set 29 | # 30 | # @return [RelationshipSet] self 31 | # 32 | # @api semipublic 33 | def each_value 34 | warn "#{self.class}#each_value is deprecated. Use #{self.class}#each instead: #{caller.first}" 35 | each { |relationship| yield(relationship) } 36 | self 37 | end 38 | 39 | # Check wether this RelationshipSet includes an entry with the given name 40 | # 41 | # @deprecated use DataMapper::RelationshipSet#named? instead 42 | # 43 | # @param [#to_s] name 44 | # the name of the entry to look for 45 | # 46 | # @return [Boolean] 47 | # true if the set contains a relationship with the given name 48 | # 49 | # @api semipublic 50 | def key?(name) 51 | warn "#{self.class}#key? is deprecated. Use #{self.class}#named? instead: #{caller.first}" 52 | named?(name) 53 | end 54 | 55 | # Check wether this RelationshipSet includes an entry with the given name 56 | # 57 | # @deprecated use DataMapper::RelationshipSet#named? instead 58 | # 59 | # @param [#to_s] name 60 | # the name of the entry to look for 61 | # 62 | # @return [Boolean] 63 | # true if the set contains a relationship with the given name 64 | # 65 | # @api semipublic 66 | def has_key?(name) 67 | warn "#{self.class}#has_key? is deprecated. Use #{self.class}#named? instead: #{caller.first}" 68 | named?(name) 69 | end 70 | 71 | end # class RelationshipSet 72 | end # module DataMapper 73 | -------------------------------------------------------------------------------- /spec/semipublic/model_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe DataMapper::Model do 4 | 5 | it { should respond_to(:append_inclusions) } 6 | 7 | describe '.append_inclusions' do 8 | module ::Inclusions 9 | def new_method 10 | end 11 | end 12 | 13 | describe 'before the model is defined' do 14 | before :all do 15 | DataMapper::Model.append_inclusions(Inclusions) 16 | 17 | class ::User 18 | include DataMapper::Resource 19 | property :id, Serial 20 | end 21 | end 22 | 23 | it 'should respond to :new_method' do 24 | User.new.should respond_to(:new_method) 25 | end 26 | 27 | after :all do 28 | DataMapper::Model.extra_inclusions.delete(Inclusions) 29 | end 30 | end 31 | 32 | describe 'after the model is defined' do 33 | before :all do 34 | class ::User 35 | include DataMapper::Resource 36 | property :id, Serial 37 | end 38 | DataMapper::Model.append_inclusions(Inclusions) 39 | end 40 | 41 | it 'should respond to :new_method' do 42 | User.new.should respond_to(:new_method) 43 | end 44 | 45 | after :all do 46 | DataMapper::Model.extra_inclusions.delete(Inclusions) 47 | end 48 | end 49 | end 50 | 51 | it { should respond_to(:append_extensions) } 52 | 53 | describe '.append_extensions' do 54 | module ::Extensions 55 | def new_method 56 | end 57 | end 58 | 59 | describe 'before the model is defined' do 60 | before :all do 61 | DataMapper::Model.append_extensions(Extensions) 62 | 63 | class ::User 64 | include DataMapper::Resource 65 | property :id, Serial 66 | end 67 | end 68 | 69 | it 'should respond to :new_method' do 70 | User.should respond_to(:new_method) 71 | end 72 | 73 | after :all do 74 | DataMapper::Model.extra_extensions.delete(Extensions) 75 | end 76 | end 77 | 78 | describe 'after the model is defined' do 79 | before :all do 80 | class ::User 81 | include DataMapper::Resource 82 | property :id, Serial 83 | end 84 | DataMapper::Model.append_extensions(Extensions) 85 | end 86 | 87 | it 'should respond to :new_method' do 88 | User.should respond_to(:new_method) 89 | end 90 | 91 | after :all do 92 | DataMapper::Model.extra_extensions.delete(Extensions) 93 | end 94 | end 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /spec/semipublic/resource/state/clean_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | describe DataMapper::Resource::PersistenceState::Clean do 3 | before :all do 4 | class ::Author 5 | include DataMapper::Resource 6 | 7 | property :id, HugeInteger, :key => true, :default => 1 8 | property :name, String 9 | property :active, Boolean, :default => true 10 | property :coding, Boolean, :default => true 11 | 12 | belongs_to :parent, self, :required => false 13 | has n, :children, self, :inverse => :parent 14 | end 15 | DataMapper.finalize 16 | 17 | @model = Author 18 | end 19 | 20 | before do 21 | @resource = @model.create(:name => 'Dan Kubb') 22 | 23 | @state = @resource.persistence_state 24 | @state.should be_kind_of(DataMapper::Resource::PersistenceState::Clean) 25 | end 26 | 27 | after do 28 | @model.destroy! 29 | end 30 | 31 | [ :commit, :rollback ].each do |method| 32 | describe "##{method}" do 33 | subject { @state.send(method) } 34 | 35 | supported_by :all do 36 | it 'should be a no-op' do 37 | should equal(@state) 38 | end 39 | end 40 | end 41 | end 42 | 43 | describe '#delete' do 44 | subject { @state.delete } 45 | 46 | supported_by :all do 47 | it 'should return a Deleted state' do 48 | should eql(DataMapper::Resource::PersistenceState::Deleted.new(@resource)) 49 | end 50 | end 51 | end 52 | 53 | describe '#get' do 54 | it_should_behave_like 'Resource::PersistenceState::Persisted#get' 55 | end 56 | 57 | describe '#set' do 58 | subject { @state.set(@key, @value) } 59 | 60 | supported_by :all do 61 | describe 'with attributes that make the resource dirty' do 62 | before do 63 | @key = @model.properties[:name] 64 | @value = nil 65 | end 66 | 67 | it_should_behave_like 'A method that delegates to the superclass #set' 68 | 69 | it 'should return a Dirty state' do 70 | should eql(DataMapper::Resource::PersistenceState::Dirty.new(@resource)) 71 | end 72 | end 73 | 74 | describe 'with attributes that keep the resource clean' do 75 | before do 76 | @key = @model.properties[:name] 77 | @value = 'Dan Kubb' 78 | end 79 | 80 | it_should_behave_like 'A method that does not delegate to the superclass #set' 81 | 82 | it 'should return a Clean state' do 83 | should equal(@state) 84 | end 85 | end 86 | end 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /spec/unit/module_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'dm-core/support/ext/module' 3 | 4 | describe DataMapper::Ext::Module do 5 | 6 | before :all do 7 | Object.send(:remove_const, :Foo) if defined?(Foo) 8 | Object.send(:remove_const, :Baz) if defined?(Baz) 9 | Object.send(:remove_const, :Bar) if defined?(Bar) 10 | 11 | module ::Foo 12 | module ModBar 13 | module Noo 14 | module Too 15 | module Boo; end 16 | end 17 | end 18 | end 19 | 20 | class Zed; end 21 | end 22 | 23 | class ::Baz; end 24 | 25 | class ::Bar; end 26 | end 27 | 28 | it "should raise NameError for a missing constant" do 29 | lambda { DataMapper::Ext::Module.find_const(Foo, 'Moo') }.should raise_error(NameError) 30 | lambda { DataMapper::Ext::Module.find_const(Object, 'MissingConstant') }.should raise_error(NameError) 31 | end 32 | 33 | it "should be able to get a recursive constant" do 34 | DataMapper::Ext::Module.find_const(Object, 'Foo::ModBar').should == Foo::ModBar 35 | end 36 | 37 | it "should ignore get Constants from the Kernel namespace correctly" do 38 | DataMapper::Ext::Module.find_const(Object, '::Foo::ModBar').should == ::Foo::ModBar 39 | end 40 | 41 | it "should find relative constants" do 42 | DataMapper::Ext::Module.find_const(Foo, 'ModBar').should == Foo::ModBar 43 | DataMapper::Ext::Module.find_const(Foo, 'Baz').should == Baz 44 | end 45 | 46 | it "should find sibling constants" do 47 | DataMapper::Ext::Module.find_const(Foo::ModBar, "Zed").should == Foo::Zed 48 | end 49 | 50 | it "should find nested constants on nested constants" do 51 | DataMapper::Ext::Module.find_const(Foo::ModBar, 'Noo::Too').should == Foo::ModBar::Noo::Too 52 | end 53 | 54 | it "should find constants outside of nested constants" do 55 | DataMapper::Ext::Module.find_const(Foo::ModBar::Noo::Too, "Zed").should == Foo::Zed 56 | end 57 | 58 | it 'should be able to find past the second nested level' do 59 | DataMapper::Ext::Module.find_const(Foo::ModBar::Noo, 'Too').should == Foo::ModBar::Noo::Too 60 | DataMapper::Ext::Module.find_const(Foo::ModBar::Noo::Too, 'Boo').should == Foo::ModBar::Noo::Too::Boo 61 | end 62 | 63 | 64 | it "should be able to deal with constants being added and removed" do 65 | DataMapper::Ext::Module.find_const(Object, 'Bar') # First we load Bar with find_const 66 | Object.module_eval { remove_const('Bar') } # Now we delete it 67 | module ::Bar; end; # Now we redefine it 68 | DataMapper::Ext::Module.find_const(Object, 'Bar').should == Bar 69 | end 70 | 71 | end 72 | -------------------------------------------------------------------------------- /spec/public/finalize_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe DataMapper do 4 | describe '.finalize' do 5 | subject { DataMapper.finalize } 6 | 7 | it 'should not raise with valid models' do 8 | class ::ValidObject 9 | include DataMapper::Resource 10 | property :id, Integer, :key => true 11 | end 12 | begin 13 | method(:subject).should_not raise_error 14 | ensure 15 | DataMapper::Model.descendants.delete(ValidObject) 16 | Object.send(:remove_const, :ValidObject) 17 | end 18 | end 19 | 20 | it "should not raise on valid child model" do 21 | class ::ValidChild 22 | include DataMapper::Resource 23 | belongs_to :valid_object, :key => true 24 | end 25 | class ::ValidObject 26 | include DataMapper::Resource 27 | property :id, Integer, :key => true 28 | end 29 | begin 30 | method(:subject).should_not raise_error 31 | ensure 32 | DataMapper::Model.descendants.delete(ValidChild) 33 | DataMapper::Model.descendants.delete(ValidObject) 34 | Object.send(:remove_const, :ValidChild) 35 | Object.send(:remove_const, :ValidObject) 36 | end 37 | end 38 | 39 | it 'should raise on an anonymous model' do 40 | model = Class.new do 41 | include DataMapper::Resource 42 | property :id, Integer, :key => true 43 | end 44 | begin 45 | method(:subject).should raise_error(DataMapper::IncompleteModelError, "#{model.inspect} must have a name") 46 | ensure 47 | DataMapper::Model.descendants.delete(model) 48 | end 49 | end 50 | 51 | it 'should raise on an empty model' do 52 | class ::EmptyObject 53 | include DataMapper::Resource 54 | end 55 | begin 56 | method(:subject).should raise_error(DataMapper::IncompleteModelError, 'EmptyObject must have at least one property or many to one relationship to be valid') 57 | ensure 58 | DataMapper::Model.descendants.delete(EmptyObject) 59 | Object.send(:remove_const, :EmptyObject) 60 | end 61 | end 62 | 63 | it 'should raise on a keyless model' do 64 | class ::KeylessObject 65 | include DataMapper::Resource 66 | property :name, String 67 | end 68 | begin 69 | method(:subject).should raise_error(DataMapper::IncompleteModelError, 'KeylessObject must have a key to be valid') 70 | ensure 71 | DataMapper::Model.descendants.delete(KeylessObject) 72 | Object.send(:remove_const, :KeylessObject) 73 | end 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /spec/public/collection_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | # run the specs once with a loaded collection and once not 4 | [ false, true ].each do |loaded| 5 | describe DataMapper::Collection do 6 | extend DataMapper::Spec::CollectionHelpers::GroupMethods 7 | 8 | self.loaded = loaded 9 | 10 | before :all do 11 | module ::Blog 12 | class Article 13 | include DataMapper::Resource 14 | 15 | property :id, Serial 16 | property :title, String, :required => true 17 | property :content, Text 18 | property :subtitle, String 19 | property :author, String, :required => true 20 | property :attachment, Object 21 | 22 | belongs_to :original, self, :required => false 23 | has n, :revisions, self, :child_key => [ :original_id ] 24 | has 1, :previous, self, :child_key => [ :original_id ], :order => [ :id.desc ] 25 | has n, :publications, :through => Resource 26 | end 27 | 28 | class Publication 29 | include DataMapper::Resource 30 | 31 | property :id, Serial 32 | property :name, String 33 | 34 | has n, :articles, :through => Resource 35 | end 36 | end 37 | 38 | DataMapper.finalize 39 | 40 | @article_model = Blog::Article 41 | @publication_model = Blog::Publication 42 | end 43 | 44 | supported_by :all do 45 | before :all do 46 | @author = 'Dan Kubb' 47 | 48 | @original = @article_model.create(:title => 'Original Article', :author => @author) 49 | @article = @article_model.create(:title => 'Sample Article', :content => 'Sample', :original => @original, :author => @author) 50 | @other = @article_model.create(:title => 'Other Article', :content => 'Other', :author => @author) 51 | 52 | # load the targets without references to a single source 53 | load_collection = lambda do |query| 54 | @article_model.all(query) 55 | end 56 | 57 | @articles = load_collection.call(:title => 'Sample Article', :author => @author) 58 | @other_articles = load_collection.call(:title => 'Other Article', :author => @author) 59 | 60 | @articles.entries if loaded 61 | end 62 | 63 | it_should_behave_like 'A public Collection' 64 | it_should_behave_like 'A Collection supporting Strategic Eager Loading' 65 | it_should_behave_like 'Finder Interface' 66 | it_should_behave_like 'Collection Finder Interface' 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /lib/dm-core/spec/lib/spec_helper.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | module Spec 3 | 4 | module Helpers 5 | def reset_raise_on_save_failure(object) 6 | object.instance_eval do 7 | if defined?(@raise_on_save_failure) 8 | remove_instance_variable(:@raise_on_save_failure) 9 | end 10 | end 11 | end 12 | end 13 | 14 | # global model cleanup 15 | def self.cleanup_models 16 | descendants = DataMapper::Model.descendants.to_a 17 | 18 | while model = descendants.shift 19 | model_name = model.name.to_s.strip 20 | 21 | unless model_name.empty? || model_name[0] == ?# 22 | parts = model_name.split('::') 23 | constant_name = parts.pop.to_sym 24 | base = parts.empty? ? Object : DataMapper::Ext::Object.full_const_get(parts.join('::')) 25 | 26 | base.class_eval { remove_const(constant_name) if const_defined?(constant_name) } 27 | end 28 | 29 | remove_ivars(model) 30 | model.instance_methods(false).each { |method| model.send(:undef_method, method) } 31 | 32 | end 33 | 34 | DataMapper::Model.descendants.clear 35 | end 36 | 37 | def self.remove_ivars(object, instance_variables = object.instance_variables) 38 | seen = {} 39 | stack = instance_variables.map { |var| [ object, var ] } 40 | 41 | while node = stack.pop 42 | object, ivar = node 43 | 44 | # skip "global" and non-DM objects 45 | next if object.kind_of?(DataMapper::Logger) || 46 | object.kind_of?(DataMapper::DescendantSet) || 47 | object.kind_of?(DataMapper::Adapters::AbstractAdapter) || 48 | object.class.name[0, 13] == 'DataObjects::' 49 | 50 | # skip classes and modules in the DataMapper namespace 51 | next if object.kind_of?(Module) && 52 | !object.name.nil? && 53 | object.name[0, 12] == 'DataMapper::' 54 | 55 | # skip when the ivar is no longer defined in the object 56 | next unless object.instance_variable_defined?(ivar) 57 | 58 | value = object.instance_variable_get(ivar) 59 | 60 | # skip descendant sets 61 | next if value.kind_of?(DataMapper::DescendantSet) 62 | 63 | object.__send__(:remove_instance_variable, ivar) unless object.frozen? 64 | 65 | # skip when the value was seen 66 | next if seen.key?(value.object_id) 67 | seen[value.object_id] = true 68 | 69 | stack.concat value.instance_variables.map { |ivar| [ value, ivar ] } 70 | end 71 | end 72 | 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /lib/dm-core/support/inflections.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | Inflector.inflections do |inflect| 3 | inflect.plural(/$/, 's') 4 | inflect.plural(/s$/i, 's') 5 | inflect.plural(/(ax|test)is$/i, '\1es') 6 | inflect.plural(/(octop|vir)us$/i, '\1i') 7 | inflect.plural(/(octop|vir)i$/i, '\1i') 8 | inflect.plural(/(alias|status)$/i, '\1es') 9 | inflect.plural(/(bu)s$/i, '\1ses') 10 | inflect.plural(/(buffal|tomat)o$/i, '\1oes') 11 | inflect.plural(/([ti])um$/i, '\1a') 12 | inflect.plural(/([ti])a$/i, '\1a') 13 | inflect.plural(/sis$/i, 'ses') 14 | inflect.plural(/(?:([^f])fe|([lr])f)$/i, '\1\2ves') 15 | inflect.plural(/(hive)$/i, '\1s') 16 | inflect.plural(/([^aeiouy]|qu)y$/i, '\1ies') 17 | inflect.plural(/(x|ch|ss|sh)$/i, '\1es') 18 | inflect.plural(/(matr|vert|ind)(?:ix|ex)$/i, '\1ices') 19 | inflect.plural(/([m|l])ouse$/i, '\1ice') 20 | inflect.plural(/([m|l])ice$/i, '\1ice') 21 | inflect.plural(/^(ox)$/i, '\1en') 22 | inflect.plural(/^(oxen)$/i, '\1') 23 | inflect.plural(/(quiz)$/i, '\1zes') 24 | 25 | inflect.singular(/s$/i, '') 26 | inflect.singular(/(n)ews$/i, '\1ews') 27 | inflect.singular(/([ti])a$/i, '\1um') 28 | inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i, '\1\2sis') 29 | inflect.singular(/(^analy)ses$/i, '\1sis') 30 | inflect.singular(/([^f])ves$/i, '\1fe') 31 | inflect.singular(/(hive)s$/i, '\1') 32 | inflect.singular(/(tive)s$/i, '\1') 33 | inflect.singular(/([lr])ves$/i, '\1f') 34 | inflect.singular(/([^aeiouy]|qu)ies$/i, '\1y') 35 | inflect.singular(/(s)eries$/i, '\1eries') 36 | inflect.singular(/(m)ovies$/i, '\1ovie') 37 | inflect.singular(/(x|ch|ss|sh)es$/i, '\1') 38 | inflect.singular(/([m|l])ice$/i, '\1ouse') 39 | inflect.singular(/(bus)es$/i, '\1') 40 | inflect.singular(/(o)es$/i, '\1') 41 | inflect.singular(/(shoe)s$/i, '\1') 42 | inflect.singular(/(cris|ax|test)es$/i, '\1is') 43 | inflect.singular(/(octop|vir)i$/i, '\1us') 44 | inflect.singular(/(alias|status)(es)?$/i, '\1') 45 | inflect.singular(/^(ox)en/i, '\1') 46 | inflect.singular(/(vert|ind)ices$/i, '\1ex') 47 | inflect.singular(/(matr)ices$/i, '\1ix') 48 | inflect.singular(/(quiz)zes$/i, '\1') 49 | inflect.singular(/(database)s$/i, '\1') 50 | 51 | inflect.irregular('person', 'people') 52 | inflect.irregular('man', 'men') 53 | inflect.irregular('child', 'children') 54 | inflect.irregular('sex', 'sexes') 55 | inflect.irregular('move', 'moves') 56 | inflect.irregular('cow', 'kine') 57 | 58 | inflect.uncountable(%w(equipment information rice money species series fish sheep jeans)) 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /spec/semipublic/shared/subject_shared_spec.rb: -------------------------------------------------------------------------------- 1 | share_examples_for 'A semipublic Subject' do 2 | describe '#default?' do 3 | describe 'with a default' do 4 | subject { @subject_with_default.default? } 5 | 6 | it { should be(true) } 7 | end 8 | 9 | describe 'without a default' do 10 | subject { @subject_without_default.default? } 11 | 12 | it { should be(false) } 13 | end 14 | end 15 | 16 | describe '#default_for' do 17 | describe 'without a default' do 18 | subject { @subject_without_default.default_for(@resource) } 19 | 20 | it 'should match the default value' do 21 | DataMapper::Ext.blank?(subject).should == true 22 | end 23 | 24 | it 'should be used as a default for the subject accessor' do 25 | should == @resource.__send__(@subject_without_default.name) 26 | end 27 | 28 | it 'should persist the value' do 29 | @resource.save.should be(true) 30 | @resource = @resource.model.get!(*@resource.key) 31 | @resource.without_default.should == subject 32 | end 33 | end 34 | 35 | describe 'with a default value' do 36 | subject { @subject_with_default.default_for(@resource) } 37 | 38 | it 'should match the default value' do 39 | if @default_value.kind_of?(DataMapper::Resource) 40 | subject.key.should == @default_value.key 41 | else 42 | should == @default_value 43 | end 44 | end 45 | 46 | it 'should be used as a default for the subject accessor' do 47 | should == @resource.__send__(@subject_with_default.name) 48 | end 49 | 50 | it 'should persist the value' do 51 | @resource.save.should be(true) 52 | @resource = @resource.model.get!(*@resource.key) 53 | @resource.with_default.should == subject 54 | end 55 | end 56 | 57 | describe 'with a default value responding to #call' do 58 | subject { @subject_with_default_callable.default_for(@resource) } 59 | 60 | it 'should match the default value' do 61 | if @default_value.kind_of?(DataMapper::Resource) 62 | subject.key.should == @default_value_callable.key 63 | else 64 | should == @default_value_callable 65 | end 66 | end 67 | 68 | it 'should be used as a default for the subject accessor' do 69 | should == @resource.__send__(@subject_with_default_callable.name) 70 | end 71 | 72 | it 'should persist the value' do 73 | @resource.save.should be(true) 74 | @resource = @resource.model.get!(*@resource.key) 75 | @resource.with_default_callable.should == subject 76 | end 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /spec/public/property/object_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe DataMapper::Property, 'Object type' do 4 | before :all do 5 | module ::Blog 6 | class Article 7 | include DataMapper::Resource 8 | 9 | property :id, Serial 10 | property :title, String 11 | property :meta, Object, :required => true 12 | end 13 | end 14 | 15 | DataMapper.finalize 16 | @model = Blog::Article 17 | @property = @model.properties[:meta] 18 | end 19 | 20 | subject { @property } 21 | 22 | describe '.options' do 23 | subject { described_class.options } 24 | 25 | it { should be_kind_of(Hash) } 26 | 27 | it { should be_empty } 28 | end 29 | 30 | it { should respond_to(:typecast) } 31 | 32 | describe '#typecast' do 33 | before do 34 | @value = { 'lang' => 'en_CA' } 35 | end 36 | 37 | subject { @property.typecast(@value) } 38 | 39 | it { should equal(@value) } 40 | end 41 | 42 | it { should respond_to(:dump) } 43 | 44 | describe '#dump' do 45 | describe 'with a value' do 46 | before do 47 | @value = { 'lang' => 'en_CA' } 48 | end 49 | 50 | subject { @property.dump(@value) } 51 | 52 | it { @property.load(subject).should == @value } 53 | end 54 | 55 | describe 'with nil' do 56 | subject { @property.dump(nil) } 57 | 58 | it { should be_nil } 59 | end 60 | end 61 | 62 | it { should respond_to(:valid?) } 63 | 64 | describe '#valid?' do 65 | describe 'with a valid load_as' do 66 | subject { @property.valid?('lang' => 'en_CA') } 67 | 68 | it { should be(true) } 69 | end 70 | 71 | describe 'with nil and property is not required' do 72 | before do 73 | @property = @model.property(:meta, Object, :required => false) 74 | end 75 | 76 | subject { @property.valid?(nil) } 77 | 78 | it { should be(true) } 79 | end 80 | 81 | describe 'with nil and property is required' do 82 | subject { @property.valid?(nil) } 83 | 84 | it { should be(false) } 85 | end 86 | 87 | describe 'with nil and property is required, but validity is negated' do 88 | subject { @property.valid?(nil, true) } 89 | 90 | it { should be(true) } 91 | end 92 | end 93 | 94 | describe 'persistable' do 95 | supported_by :all do 96 | before :all do 97 | @resource = @model.create(:title => 'Test', :meta => { 'lang' => 'en_CA' }) 98 | end 99 | 100 | subject { @resource.reload.meta } 101 | 102 | it 'should load the correct value' do 103 | should == { 'lang' => 'en_CA' } 104 | end 105 | end 106 | end 107 | end 108 | -------------------------------------------------------------------------------- /lib/dm-core/model/scope.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | module Model 3 | # Module with query scoping functionality. 4 | # 5 | # Scopes are implemented using simple array based 6 | # stack that is thread local. Default scope can be set 7 | # on a per repository basis. 8 | # 9 | # Scopes are merged as new queries are nested. 10 | # It is also possible to get exclusive scope access 11 | # using +with_exclusive_scope+ 12 | module Scope 13 | # @api private 14 | def default_scope(repository_name = default_repository_name) 15 | @default_scope ||= {} 16 | 17 | default_repository_name = self.default_repository_name 18 | 19 | @default_scope[repository_name] ||= if repository_name == default_repository_name 20 | {} 21 | else 22 | default_scope(default_repository_name).dup 23 | end 24 | end 25 | 26 | # Returns query on top of scope stack 27 | # 28 | # @api private 29 | def query 30 | repository.new_query(self, current_scope).freeze 31 | end 32 | 33 | # @api private 34 | def current_scope 35 | scope_stack.last || default_scope(repository.name) 36 | end 37 | 38 | protected 39 | 40 | # Pushes given query on top of the stack 41 | # 42 | # @param [Hash, Query] Query to add to current scope nesting 43 | # 44 | # @api private 45 | def with_scope(query) 46 | options = if query.kind_of?(Hash) 47 | query 48 | else 49 | query.options 50 | end 51 | 52 | # merge the current scope with the passed in query 53 | with_exclusive_scope(self.query.merge(options)) { |*block_args| yield(*block_args) } 54 | end 55 | 56 | # Pushes given query on top of scope stack and yields 57 | # given block, then pops the stack. During block execution 58 | # queries previously pushed onto the stack 59 | # have no effect. 60 | # 61 | # @api private 62 | def with_exclusive_scope(query) 63 | query = if query.kind_of?(Hash) 64 | repository.new_query(self, query) 65 | else 66 | query.dup 67 | end 68 | 69 | scope_stack = self.scope_stack 70 | scope_stack << query.options 71 | 72 | begin 73 | yield query.freeze 74 | ensure 75 | scope_stack.pop 76 | end 77 | end 78 | 79 | # Initializes (if necessary) and returns current scope stack 80 | # @api private 81 | def scope_stack 82 | scope_stack_for = Thread.current[:dm_scope_stack] ||= {} 83 | scope_stack_for[object_id] ||= [] 84 | end 85 | end # module Scope 86 | 87 | include Scope 88 | end # module Model 89 | end # module DataMapper 90 | -------------------------------------------------------------------------------- /spec/semipublic/property/integer_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe DataMapper::Property::Integer do 4 | before :all do 5 | @name = :age 6 | @type = described_class 7 | @value = 1 8 | @other_value = 2 9 | @invalid_value = '1' 10 | end 11 | 12 | it_should_behave_like 'A semipublic Property' 13 | 14 | describe '#typecast' do 15 | it 'returns same value if an integer' do 16 | @value = 24 17 | @property.typecast(@value).should equal(@value) 18 | end 19 | 20 | it 'returns integer representation of a zero string integer' do 21 | @property.typecast('0').should eql(0) 22 | end 23 | 24 | it 'returns integer representation of a positive string integer' do 25 | @property.typecast('24').should eql(24) 26 | end 27 | 28 | it 'returns integer representation of a negative string integer' do 29 | @property.typecast('-24').should eql(-24) 30 | end 31 | 32 | it 'returns integer representation of a zero string float' do 33 | @property.typecast('0.0').should eql(0) 34 | end 35 | 36 | it 'returns integer representation of a positive string float' do 37 | @property.typecast('24.35').should eql(24) 38 | end 39 | 40 | it 'returns integer representation of a negative string float' do 41 | @property.typecast('-24.35').should eql(-24) 42 | end 43 | 44 | it 'returns integer representation of a zero string float, with no leading digits' do 45 | @property.typecast('.0').should eql(0) 46 | end 47 | 48 | it 'returns integer representation of a positive string float, with no leading digits' do 49 | @property.typecast('.41').should eql(0) 50 | end 51 | 52 | it 'returns integer representation of a zero float' do 53 | @property.typecast(0.0).should eql(0) 54 | end 55 | 56 | it 'returns integer representation of a positive float' do 57 | @property.typecast(24.35).should eql(24) 58 | end 59 | 60 | it 'returns integer representation of a negative float' do 61 | @property.typecast(-24.35).should eql(-24) 62 | end 63 | 64 | it 'returns integer representation of a zero decimal' do 65 | @property.typecast(BigDecimal('0.0')).should eql(0) 66 | end 67 | 68 | it 'returns integer representation of a positive decimal' do 69 | @property.typecast(BigDecimal('24.35')).should eql(24) 70 | end 71 | 72 | it 'returns integer representation of a negative decimal' do 73 | @property.typecast(BigDecimal('-24.35')).should eql(-24) 74 | end 75 | 76 | [ Object.new, true, '00.0', '0.', '-.0', 'string' ].each do |value| 77 | it "does not typecast non-numeric value #{value.inspect}" do 78 | @property.typecast(value).should equal(value) 79 | end 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /spec/semipublic/property/float_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe DataMapper::Property::Float do 4 | before :all do 5 | @name = :rating 6 | @type = described_class 7 | @value = 0.1 8 | @other_value = 0.2 9 | @invalid_value = '1' 10 | end 11 | 12 | it_should_behave_like 'A semipublic Property' 13 | 14 | describe '#typecast' do 15 | it 'returns same value if a float' do 16 | @value = 24.0 17 | @property.typecast(@value).should equal(@value) 18 | end 19 | 20 | it 'returns float representation of a zero string integer' do 21 | @property.typecast('0').should eql(0.0) 22 | end 23 | 24 | it 'returns float representation of a positive string integer' do 25 | @property.typecast('24').should eql(24.0) 26 | end 27 | 28 | it 'returns float representation of a negative string integer' do 29 | @property.typecast('-24').should eql(-24.0) 30 | end 31 | 32 | it 'returns float representation of a zero string float' do 33 | @property.typecast('0.0').should eql(0.0) 34 | end 35 | 36 | it 'returns float representation of a positive string float' do 37 | @property.typecast('24.35').should eql(24.35) 38 | end 39 | 40 | it 'returns float representation of a negative string float' do 41 | @property.typecast('-24.35').should eql(-24.35) 42 | end 43 | 44 | it 'returns float representation of a zero string float, with no leading digits' do 45 | @property.typecast('.0').should eql(0.0) 46 | end 47 | 48 | it 'returns float representation of a positive string float, with no leading digits' do 49 | @property.typecast('.41').should eql(0.41) 50 | end 51 | 52 | it 'returns float representation of a zero integer' do 53 | @property.typecast(0).should eql(0.0) 54 | end 55 | 56 | it 'returns float representation of a positive integer' do 57 | @property.typecast(24).should eql(24.0) 58 | end 59 | 60 | it 'returns float representation of a negative integer' do 61 | @property.typecast(-24).should eql(-24.0) 62 | end 63 | 64 | it 'returns float representation of a zero decimal' do 65 | @property.typecast(BigDecimal('0.0')).should eql(0.0) 66 | end 67 | 68 | it 'returns float representation of a positive decimal' do 69 | @property.typecast(BigDecimal('24.35')).should eql(24.35) 70 | end 71 | 72 | it 'returns float representation of a negative decimal' do 73 | @property.typecast(BigDecimal('-24.35')).should eql(-24.35) 74 | end 75 | 76 | [ Object.new, true, '00.0', '0.', '-.0', 'string' ].each do |value| 77 | it "does not typecast non-numeric value #{value.inspect}" do 78 | @property.typecast(value).should equal(value) 79 | end 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /spec/public/associations/one_to_many_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | # run the specs once with a loaded association and once not 4 | [ false, true ].each do |loaded| 5 | describe 'One to Many Associations' do 6 | extend DataMapper::Spec::CollectionHelpers::GroupMethods 7 | 8 | self.loaded = loaded 9 | 10 | # define the model prior to supported_by 11 | before :all do 12 | module ::Blog 13 | class Author 14 | include DataMapper::Resource 15 | 16 | property :id, Serial 17 | property :name, String 18 | 19 | has n, :articles 20 | end 21 | 22 | class Article 23 | include DataMapper::Resource 24 | 25 | property :id, Serial 26 | property :title, String, :required => true 27 | property :content, Text 28 | property :subtitle, String 29 | property :attachment, Object 30 | 31 | belongs_to :author, :required => false 32 | belongs_to :original, self, :required => false 33 | has n, :revisions, self, :child_key => [ :original_id ] 34 | has 1, :previous, self, :child_key => [ :original_id ], :order => [ :id.desc ] 35 | has n, :publications, :through => Resource 36 | end 37 | 38 | class Publication 39 | include DataMapper::Resource 40 | 41 | property :id, Serial 42 | property :name, String 43 | 44 | has n, :articles, :through => Resource 45 | end 46 | end 47 | 48 | DataMapper.finalize 49 | 50 | @author_model = Blog::Author 51 | @article_model = Blog::Article 52 | @publication_model = Blog::Publication 53 | end 54 | 55 | supported_by :all do 56 | before :all do 57 | @author = @author_model.create(:name => 'Dan Kubb') 58 | 59 | @original = @author.articles.create(:title => 'Original Article') 60 | @article = @author.articles.create(:title => 'Sample Article', :content => 'Sample', :original => @original) 61 | @other = @author.articles.create(:title => 'Other Article', :content => 'Other') 62 | 63 | # load the targets without references to a single source 64 | load_collection = lambda do |query| 65 | @author_model.get(*@author.key).articles(query) 66 | end 67 | 68 | @articles = load_collection.call(:title => 'Sample Article') 69 | @other_articles = load_collection.call(:title => 'Other Article') 70 | 71 | @articles.entries if loaded 72 | end 73 | 74 | it_should_behave_like 'A public Collection' 75 | it_should_behave_like 'A public Association Collection' 76 | it_should_behave_like 'A Collection supporting Strategic Eager Loading' 77 | it_should_behave_like 'Finder Interface' 78 | it_should_behave_like 'Collection Finder Interface' 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /lib/dm-core/associations/one_to_one.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | module Associations 3 | module OneToOne #:nodoc: 4 | class Relationship < Associations::Relationship 5 | %w[ public protected private ].map do |visibility| 6 | methods = superclass.send("#{visibility}_instance_methods", false) | 7 | DataMapper::Subject.send("#{visibility}_instance_methods", false) 8 | 9 | methods.each do |method| 10 | undef_method method.to_sym unless method.to_s == 'initialize' 11 | end 12 | end 13 | 14 | # Loads (if necessary) and returns association target 15 | # for given source 16 | # 17 | # @api semipublic 18 | def get(source, query = nil) 19 | relationship.get(source, query).first 20 | end 21 | 22 | # Get the resource directly 23 | # 24 | # @api semipublic 25 | def get!(source) 26 | collection = relationship.get!(source) 27 | collection.first if collection 28 | end 29 | 30 | # Sets and returns association target 31 | # for given source 32 | # 33 | # @api semipublic 34 | def set(source, target) 35 | relationship.set(source, [ target ].compact).first 36 | end 37 | 38 | # Sets the resource directly 39 | # 40 | # @api semipublic 41 | def set!(source, target) 42 | set(source, target) 43 | end 44 | 45 | # @api semipublic 46 | def default_for(source) 47 | relationship.default_for(source).first 48 | end 49 | 50 | # @api public 51 | def kind_of?(klass) 52 | super || relationship.kind_of?(klass) 53 | end 54 | 55 | # @api public 56 | def instance_of?(klass) 57 | super || relationship.instance_of?(klass) 58 | end 59 | 60 | # @api public 61 | def respond_to?(method, include_private = false) 62 | super || relationship.respond_to?(method, include_private) 63 | end 64 | 65 | private 66 | 67 | attr_reader :relationship 68 | 69 | # Initializes the relationship. Always assumes target model class is 70 | # a camel cased association name. 71 | # 72 | # @api semipublic 73 | def initialize(name, target_model, source_model, options = {}) 74 | klass = options.key?(:through) ? ManyToMany::Relationship : OneToMany::Relationship 75 | target_model ||= DataMapper::Inflector.camelize(name).freeze 76 | @relationship = klass.new(name, target_model, source_model, options) 77 | end 78 | 79 | # @api private 80 | def method_missing(method, *args, &block) 81 | relationship.send(method, *args, &block) 82 | end 83 | end # class Relationship 84 | end # module HasOne 85 | end # module Associations 86 | end # module DataMapper 87 | -------------------------------------------------------------------------------- /lib/dm-core/model/hook.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | module Model 3 | module Hook 4 | Model.append_inclusions self 5 | 6 | extend Chainable 7 | 8 | def self.included(model) 9 | model.send(:include, DataMapper::Hook) 10 | model.extend Methods 11 | super 12 | end 13 | 14 | module Methods 15 | def inherited(model) 16 | copy_hooks(model) 17 | super 18 | end 19 | 20 | # @api public 21 | def before(target_method, method_sym = nil, &block) 22 | setup_hook(:before, target_method, method_sym, block) { super } 23 | end 24 | 25 | # @api public 26 | def after(target_method, method_sym = nil, &block) 27 | setup_hook(:after, target_method, method_sym, block) { super } 28 | end 29 | 30 | # @api private 31 | def hooks 32 | @hooks ||= { 33 | :save => { :before => [], :after => [] }, 34 | :create => { :before => [], :after => [] }, 35 | :update => { :before => [], :after => [] }, 36 | :destroy => { :before => [], :after => [] }, 37 | } 38 | end 39 | 40 | private 41 | 42 | def setup_hook(type, name, method, proc) 43 | types = hooks[name] 44 | if types && types[type] 45 | types[type] << if proc 46 | ProcCommand.new(proc) 47 | else 48 | MethodCommand.new(self, method) 49 | end 50 | else 51 | yield 52 | end 53 | end 54 | 55 | # deep copy hooks from the parent model 56 | def copy_hooks(model) 57 | hooks = Hash.new do |hooks, name| 58 | hooks[name] = Hash.new do |types, type| 59 | if self.hooks[name] 60 | types[type] = self.hooks[name][type].map do |command| 61 | command.copy(model) 62 | end 63 | end 64 | end 65 | end 66 | 67 | model.instance_variable_set(:@hooks, hooks) 68 | end 69 | 70 | end 71 | 72 | class ProcCommand 73 | def initialize(proc) 74 | @proc = proc.to_proc 75 | end 76 | 77 | def call(resource) 78 | resource.instance_eval(&@proc) 79 | end 80 | 81 | def copy(model) 82 | self 83 | end 84 | end 85 | 86 | class MethodCommand 87 | def initialize(model, method) 88 | @model, @method = model, method.to_sym 89 | end 90 | 91 | def call(resource) 92 | resource.__send__(@method) 93 | end 94 | 95 | def copy(model) 96 | self.class.new(model, @method) 97 | end 98 | 99 | end 100 | 101 | end # module Hook 102 | end # module Model 103 | end # module DataMapper 104 | -------------------------------------------------------------------------------- /spec/semipublic/resource/state/immutable_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | describe DataMapper::Resource::PersistenceState::Immutable do 3 | before :all do 4 | class ::Author 5 | include DataMapper::Resource 6 | 7 | property :id, Serial 8 | property :name, String 9 | property :active, Boolean, :default => true 10 | property :coding, Boolean, :default => true 11 | 12 | belongs_to :parent, self, :required => false 13 | end 14 | 15 | DataMapper.finalize 16 | 17 | @model = Author 18 | end 19 | 20 | before do 21 | @parent = @model.create(:name => 'John Doe') 22 | 23 | @resource = @model.create(:name => 'Dan Kubb', :parent => @parent) 24 | attributes = Hash[ @model.key.zip(@resource.key) ] 25 | @resource = @model.first(attributes.merge(:fields => [ :name, :parent_id ])) 26 | 27 | @state = @resource.persistence_state 28 | @state.should be_kind_of(DataMapper::Resource::PersistenceState::Immutable) 29 | end 30 | 31 | after do 32 | @model.destroy! 33 | end 34 | 35 | describe '#commit' do 36 | subject { @state.commit } 37 | 38 | supported_by :all do 39 | it 'should be a no-op' do 40 | should equal(@state) 41 | end 42 | end 43 | end 44 | 45 | describe '#delete' do 46 | subject { @state.delete } 47 | 48 | supported_by :all do 49 | it 'should raise an exception' do 50 | method(:subject).should raise_error(DataMapper::ImmutableError, 'Immutable resource cannot be deleted') 51 | end 52 | end 53 | end 54 | 55 | describe '#get' do 56 | subject { @state.get(@key) } 57 | 58 | supported_by :all do 59 | describe 'with an unloaded property' do 60 | before do 61 | @key = @model.properties[:id] 62 | end 63 | 64 | it 'should raise an exception' do 65 | method(:subject).should raise_error(DataMapper::ImmutableError, 'Immutable resource cannot be lazy loaded') 66 | end 67 | end 68 | 69 | describe 'with an unloaded relationship' do 70 | before do 71 | @key = @model.relationships[:parent] 72 | end 73 | 74 | it 'should return value' do 75 | should == @parent 76 | end 77 | end 78 | 79 | describe 'with a loaded subject' do 80 | before do 81 | @key = @model.properties[:name] 82 | end 83 | 84 | it 'should return value' do 85 | should == 'Dan Kubb' 86 | end 87 | end 88 | end 89 | end 90 | 91 | describe '#set' do 92 | before do 93 | @key = @model.properties[:name] 94 | @value = @key.get!(@resource) 95 | end 96 | 97 | subject { @state.set(@key, @value) } 98 | 99 | supported_by :all do 100 | it 'should raise an exception' do 101 | method(:subject).should raise_error(DataMapper::ImmutableError, 'Immutable resource cannot be modified') 102 | end 103 | end 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /lib/dm-core/resource/persistence_state/dirty.rb: -------------------------------------------------------------------------------- 1 | module DataMapper 2 | module Resource 3 | class PersistenceState 4 | 5 | # a persisted/dirty resource 6 | class Dirty < Persisted 7 | def set(subject, value) 8 | track(subject, value) 9 | super 10 | original_attributes.empty? ? Clean.new(resource) : self 11 | end 12 | 13 | def delete 14 | reset_resource 15 | Deleted.new(resource) 16 | end 17 | 18 | def commit 19 | remove_from_identity_map 20 | set_child_keys 21 | assert_valid_attributes 22 | update_resource 23 | reset_original_attributes 24 | reset_resource_key 25 | Clean.new(resource) 26 | ensure 27 | add_to_identity_map 28 | end 29 | 30 | def rollback 31 | reset_resource 32 | Clean.new(resource) 33 | end 34 | 35 | def original_attributes 36 | @original_attributes ||= {} 37 | end 38 | 39 | private 40 | 41 | def track(subject, value) 42 | if original_attributes.key?(subject) 43 | # stop tracking if the new value is the same as the original 44 | if original_attributes[subject].eql?(value) 45 | original_attributes.delete(subject) 46 | end 47 | elsif !value.eql?(original = get(subject)) 48 | # track the original value 49 | original_attributes[subject] = original 50 | end 51 | end 52 | 53 | def update_resource 54 | repository.update(resource.dirty_attributes, collection_for_self) 55 | end 56 | 57 | def reset_resource 58 | reset_resource_properties 59 | reset_resource_relationships 60 | end 61 | 62 | def reset_resource_key 63 | resource.instance_eval { remove_instance_variable(:@_key) } 64 | end 65 | 66 | def reset_resource_properties 67 | # delete every original attribute after resetting the resource 68 | original_attributes.delete_if do |property, value| 69 | property.set!(resource, value) 70 | true 71 | end 72 | end 73 | 74 | def reset_resource_relationships 75 | relationships.each do |relationship| 76 | next unless relationship.loaded?(resource) 77 | # TODO: consider a method in Relationship that can reset the relationship 78 | resource.instance_eval { remove_instance_variable(relationship.instance_variable_name) } 79 | end 80 | end 81 | 82 | def reset_original_attributes 83 | original_attributes.clear 84 | end 85 | 86 | def assert_valid_attributes 87 | properties.each do |property| 88 | value = property.get! resource 89 | property.assert_valid_value(value) 90 | end 91 | end 92 | 93 | end # class Dirty 94 | end # class PersistenceState 95 | end # module Resource 96 | end # module DataMapper 97 | -------------------------------------------------------------------------------- /spec/semipublic/property/decimal_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe DataMapper::Property::Decimal do 4 | before :all do 5 | @name = :rate 6 | @type = described_class 7 | @options = { :precision => 5, :scale => 2 } 8 | @value = BigDecimal('1.0') 9 | @other_value = BigDecimal('2.0') 10 | @invalid_value = true 11 | end 12 | 13 | it_should_behave_like 'A semipublic Property' 14 | 15 | describe '#typecast' do 16 | it 'returns same value if a decimal' do 17 | @value = BigDecimal('24.0') 18 | @property.typecast(@value).should equal(@value) 19 | end 20 | 21 | it 'returns decimal representation of a zero string integer' do 22 | @property.typecast('0').should eql(BigDecimal('0.0')) 23 | end 24 | 25 | it 'returns decimal representation of a positive string integer' do 26 | @property.typecast('24').should eql(BigDecimal('24.0')) 27 | end 28 | 29 | it 'returns decimal representation of a negative string integer' do 30 | @property.typecast('-24').should eql(BigDecimal('-24.0')) 31 | end 32 | 33 | it 'returns decimal representation of a zero string float' do 34 | @property.typecast('0.0').should eql(BigDecimal('0.0')) 35 | end 36 | 37 | it 'returns decimal representation of a positive string float' do 38 | @property.typecast('24.35').should eql(BigDecimal('24.35')) 39 | end 40 | 41 | it 'returns decimal representation of a negative string float' do 42 | @property.typecast('-24.35').should eql(BigDecimal('-24.35')) 43 | end 44 | 45 | it 'returns decimal representation of a zero string float, with no leading digits' do 46 | @property.typecast('.0').should eql(BigDecimal('0.0')) 47 | end 48 | 49 | it 'returns decimal representation of a positive string float, with no leading digits' do 50 | @property.typecast('.41').should eql(BigDecimal('0.41')) 51 | end 52 | 53 | it 'returns decimal representation of a zero integer' do 54 | @property.typecast(0).should eql(BigDecimal('0.0')) 55 | end 56 | 57 | it 'returns decimal representation of a positive integer' do 58 | @property.typecast(24).should eql(BigDecimal('24.0')) 59 | end 60 | 61 | it 'returns decimal representation of a negative integer' do 62 | @property.typecast(-24).should eql(BigDecimal('-24.0')) 63 | end 64 | 65 | it 'returns decimal representation of a zero float' do 66 | @property.typecast(0.0).should eql(BigDecimal('0.0')) 67 | end 68 | 69 | it 'returns decimal representation of a positive float' do 70 | @property.typecast(24.35).should eql(BigDecimal('24.35')) 71 | end 72 | 73 | it 'returns decimal representation of a negative float' do 74 | @property.typecast(-24.35).should eql(BigDecimal('-24.35')) 75 | end 76 | 77 | [ Object.new, true, '00.0', '0.', '-.0', 'string' ].each do |value| 78 | it "does not typecast non-numeric value #{value.inspect}" do 79 | @property.typecast(value).should equal(value) 80 | end 81 | end 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /spec/semipublic/shared/resource_state_shared_spec.rb: -------------------------------------------------------------------------------- 1 | share_examples_for 'A method that delegates to the superclass #set' do 2 | it 'should delegate to the superclass' do 3 | # this is the only way I could think of to test if the 4 | # superclass method is being called 5 | DataMapper::Resource::PersistenceState.class_eval { alias_method :original_set, :set; undef_method(:set) } 6 | method(:subject).should raise_error(NoMethodError) 7 | DataMapper::Resource::PersistenceState.class_eval { alias_method :set, :original_set; undef_method(:original_set) } 8 | end 9 | end 10 | 11 | share_examples_for 'A method that does not delegate to the superclass #set' do 12 | it 'should delegate to the superclass' do 13 | # this is the only way I could think of to test if the 14 | # superclass method is not being called 15 | DataMapper::Resource::PersistenceState.class_eval { alias_method :original_set, :set; undef_method(:set) } 16 | method(:subject).should_not raise_error(NoMethodError) 17 | DataMapper::Resource::PersistenceState.class_eval { alias_method :set, :original_set; undef_method(:original_set) } 18 | end 19 | end 20 | 21 | share_examples_for 'It resets resource state' do 22 | it 'should reset the dirty property' do 23 | method(:subject).should change(@resource, :name).from('John Doe').to('Dan Kubb') 24 | end 25 | 26 | it 'should reset the dirty m:1 relationship' do 27 | method(:subject).should change(@resource, :parent).from(@resource).to(nil) 28 | end 29 | 30 | it 'should reset the dirty 1:m relationship' do 31 | method(:subject).should change(@resource, :children).from([ @resource ]).to([]) 32 | end 33 | 34 | it 'should clear original attributes' do 35 | method(:subject).should change { @resource.original_attributes.dup }.to({}) 36 | end 37 | end 38 | 39 | share_examples_for 'Resource::PersistenceState::Persisted#get' do 40 | subject { @state.get(@key) } 41 | 42 | supported_by :all do 43 | describe 'with an unloaded subject' do 44 | before do 45 | @key = @model.relationships[:parent] 46 | 47 | # set the parent relationship 48 | @resource.attributes = { @key => @resource } 49 | @resource.should be_dirty 50 | @resource.save.should be(true) 51 | 52 | attributes = Hash[ @model.key.zip(@resource.key) ] 53 | @resource = @model.first(attributes.merge(:fields => @model.key)) 54 | @state = @state.class.new(@resource) 55 | 56 | # make sure the subject is not loaded 57 | @key.should_not be_loaded(@resource) 58 | end 59 | 60 | it 'should lazy load the value' do 61 | subject.key.should == @resource.key 62 | end 63 | end 64 | 65 | describe 'with a loaded subject' do 66 | before do 67 | @key = @model.properties[:name] 68 | @loaded_value ||= 'Dan Kubb' 69 | 70 | # make sure the subject is loaded 71 | @key.should be_loaded(@resource) 72 | end 73 | 74 | it 'should return value' do 75 | should == @loaded_value 76 | end 77 | end 78 | end 79 | end 80 | --------------------------------------------------------------------------------