dm-core 0.9.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +144 -0
- data/FAQ +74 -0
- data/MIT-LICENSE +22 -0
- data/QUICKLINKS +12 -0
- data/README +143 -0
- data/lib/dm-core.rb +213 -0
- data/lib/dm-core/adapters.rb +4 -0
- data/lib/dm-core/adapters/abstract_adapter.rb +202 -0
- data/lib/dm-core/adapters/data_objects_adapter.rb +701 -0
- data/lib/dm-core/adapters/mysql_adapter.rb +132 -0
- data/lib/dm-core/adapters/postgres_adapter.rb +179 -0
- data/lib/dm-core/adapters/sqlite3_adapter.rb +105 -0
- data/lib/dm-core/associations.rb +172 -0
- data/lib/dm-core/associations/many_to_many.rb +138 -0
- data/lib/dm-core/associations/many_to_one.rb +101 -0
- data/lib/dm-core/associations/one_to_many.rb +275 -0
- data/lib/dm-core/associations/one_to_one.rb +61 -0
- data/lib/dm-core/associations/relationship.rb +116 -0
- data/lib/dm-core/associations/relationship_chain.rb +74 -0
- data/lib/dm-core/auto_migrations.rb +64 -0
- data/lib/dm-core/collection.rb +604 -0
- data/lib/dm-core/hook.rb +11 -0
- data/lib/dm-core/identity_map.rb +45 -0
- data/lib/dm-core/is.rb +16 -0
- data/lib/dm-core/logger.rb +233 -0
- data/lib/dm-core/migrations/destructive_migrations.rb +17 -0
- data/lib/dm-core/migrator.rb +29 -0
- data/lib/dm-core/model.rb +399 -0
- data/lib/dm-core/naming_conventions.rb +52 -0
- data/lib/dm-core/property.rb +611 -0
- data/lib/dm-core/property_set.rb +158 -0
- data/lib/dm-core/query.rb +590 -0
- data/lib/dm-core/repository.rb +159 -0
- data/lib/dm-core/resource.rb +618 -0
- data/lib/dm-core/scope.rb +35 -0
- data/lib/dm-core/support.rb +7 -0
- data/lib/dm-core/support/array.rb +13 -0
- data/lib/dm-core/support/assertions.rb +8 -0
- data/lib/dm-core/support/errors.rb +23 -0
- data/lib/dm-core/support/kernel.rb +7 -0
- data/lib/dm-core/support/symbol.rb +41 -0
- data/lib/dm-core/transaction.rb +267 -0
- data/lib/dm-core/type.rb +160 -0
- data/lib/dm-core/type_map.rb +80 -0
- data/lib/dm-core/types.rb +19 -0
- data/lib/dm-core/types/boolean.rb +7 -0
- data/lib/dm-core/types/discriminator.rb +32 -0
- data/lib/dm-core/types/object.rb +20 -0
- data/lib/dm-core/types/paranoid_boolean.rb +23 -0
- data/lib/dm-core/types/paranoid_datetime.rb +22 -0
- data/lib/dm-core/types/serial.rb +9 -0
- data/lib/dm-core/types/text.rb +10 -0
- data/spec/integration/association_spec.rb +1215 -0
- data/spec/integration/association_through_spec.rb +150 -0
- data/spec/integration/associations/many_to_many_spec.rb +171 -0
- data/spec/integration/associations/many_to_one_spec.rb +123 -0
- data/spec/integration/associations/one_to_many_spec.rb +66 -0
- data/spec/integration/auto_migrations_spec.rb +398 -0
- data/spec/integration/collection_spec.rb +1015 -0
- data/spec/integration/data_objects_adapter_spec.rb +32 -0
- data/spec/integration/model_spec.rb +68 -0
- data/spec/integration/mysql_adapter_spec.rb +85 -0
- data/spec/integration/postgres_adapter_spec.rb +732 -0
- data/spec/integration/property_spec.rb +224 -0
- data/spec/integration/query_spec.rb +376 -0
- data/spec/integration/repository_spec.rb +57 -0
- data/spec/integration/resource_spec.rb +324 -0
- data/spec/integration/sqlite3_adapter_spec.rb +352 -0
- data/spec/integration/sti_spec.rb +185 -0
- data/spec/integration/transaction_spec.rb +75 -0
- data/spec/integration/type_spec.rb +149 -0
- data/spec/lib/mock_adapter.rb +27 -0
- data/spec/spec_helper.rb +112 -0
- data/spec/unit/adapters/abstract_adapter_spec.rb +133 -0
- data/spec/unit/adapters/adapter_shared_spec.rb +15 -0
- data/spec/unit/adapters/data_objects_adapter_spec.rb +627 -0
- data/spec/unit/adapters/postgres_adapter_spec.rb +125 -0
- data/spec/unit/associations/many_to_many_spec.rb +14 -0
- data/spec/unit/associations/many_to_one_spec.rb +138 -0
- data/spec/unit/associations/one_to_many_spec.rb +385 -0
- data/spec/unit/associations/one_to_one_spec.rb +7 -0
- data/spec/unit/associations/relationship_spec.rb +67 -0
- data/spec/unit/associations_spec.rb +205 -0
- data/spec/unit/auto_migrations_spec.rb +110 -0
- data/spec/unit/collection_spec.rb +174 -0
- data/spec/unit/data_mapper_spec.rb +21 -0
- data/spec/unit/identity_map_spec.rb +126 -0
- data/spec/unit/is_spec.rb +80 -0
- data/spec/unit/migrator_spec.rb +33 -0
- data/spec/unit/model_spec.rb +339 -0
- data/spec/unit/naming_conventions_spec.rb +28 -0
- data/spec/unit/property_set_spec.rb +96 -0
- data/spec/unit/property_spec.rb +447 -0
- data/spec/unit/query_spec.rb +485 -0
- data/spec/unit/repository_spec.rb +93 -0
- data/spec/unit/resource_spec.rb +557 -0
- data/spec/unit/scope_spec.rb +131 -0
- data/spec/unit/transaction_spec.rb +493 -0
- data/spec/unit/type_map_spec.rb +114 -0
- data/spec/unit/type_spec.rb +119 -0
- metadata +187 -0
data/CHANGELOG
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
-- 0.1.0
|
2
|
+
* Initial Public Release
|
3
|
+
|
4
|
+
-- 0.1.1
|
5
|
+
* Removed /lib/data_mapper/extensions
|
6
|
+
* Moved ActiveRecordImpersonation into DataMapper::Support module
|
7
|
+
* Moved CallbackHelper methods into DataMapper::Base class
|
8
|
+
* Moved ValidationHelper into DataMapper::Validations module
|
9
|
+
* Removed LoadedSet since it's not necessary for it to reference the Database, so it's nothing more than an array now; Replaced with Array
|
10
|
+
* Modified data_mapper.rb to load DataMapper::Support::Enumerable
|
11
|
+
* Modified example.rb and performance.rb to require 'lib/data_mapper' instead of modifying $LOADPATH
|
12
|
+
* Created SqlAdapter base-class
|
13
|
+
* Refactored MysqlAdapter to use SqlAdapter superclass
|
14
|
+
* Refactored Sqlite3Adapter to use SqlAdapter superclass
|
15
|
+
* Moved /lib/data_mapper/queries to /lib/data_mapper/adapters/sql/queries
|
16
|
+
* Moved Connection, Result and Reader classes along with Coersion and Quoting modules to DataMapper::Adapters::Sql module
|
17
|
+
* Moved DataMapper::Adapters::Sql::Queries to ::Commands
|
18
|
+
* Moved Mappings to SqlAdapter
|
19
|
+
* Added monolithic DeleteCommand
|
20
|
+
* Added monolithic SaveCommand
|
21
|
+
* Added TableExistsCommand
|
22
|
+
* Moved save/delete logic out of Session
|
23
|
+
* Added create-table functionality to SaveCommand
|
24
|
+
* Cleaned up Session; #find no longer supported, use #all or #first
|
25
|
+
* Moved object materialization into LoadCommand
|
26
|
+
* Migrated Sqlite3Adapter::Commands
|
27
|
+
* Added Session#query support back in
|
28
|
+
* Removed Connection/Reader/Result classes
|
29
|
+
* Set DataMapper::Base#key on load to avoid double-hit against Schema
|
30
|
+
* Added DataMapper::Support::Struct for increased Session#query performance
|
31
|
+
* Added AdvancedHasManyAssociation (preview status)
|
32
|
+
* Added benchmarks comparing ActiveRecord::Base::find_by_sql with Session#query
|
33
|
+
|
34
|
+
-- 0.2.0
|
35
|
+
* AdvancedHasManyAssociation now functional for fetches
|
36
|
+
* AdvancedHasManyAssociation renamed to HasNAssociation
|
37
|
+
* HasManyAssociation refactored to use HasNAssociation superclass
|
38
|
+
* Slight spec tweaks to accomodate the updates
|
39
|
+
* HasOneAssociation refactored to use HasNAssociation superclass
|
40
|
+
* Added HasAndBelongsToManyAssociation, using HasNAssociation as a basis; Need to add corresponding SQL generation code in AdvancedLoadCommand
|
41
|
+
* Added spec for habtm query generation
|
42
|
+
* HasNAssociation#foreign_key returns a DataMapper::Adapters::Sql::Mappings::Column instance instead of a raw String now
|
43
|
+
* Added table, association, association_table and to_sql methods to HasNAssociation
|
44
|
+
* Added associations_spec.rb
|
45
|
+
* Added a forced table-recreation to spec_helper.rb so the tests could run with a clean version of the database, including any new columns added to the models
|
46
|
+
* Added HasAndBelongsToManyAssociation#to_sql (all current specs pass now!)
|
47
|
+
* Minor tweaks to Callbacks
|
48
|
+
* Added CallbacksHelper to declare class-method ::callbacks on DataMapper::Base
|
49
|
+
* Implemented before_validate and after_validate hooks in ValidationHelper
|
50
|
+
* Minor documentation additions in callbacks.rb
|
51
|
+
* Added callbacks_spec
|
52
|
+
* Moved class-method declarations for built-in callbacks to the callbacks helper instead of DataMapper::Base
|
53
|
+
* Renamed :before/after_validate callback to :before/after_validation to match ActiveRecord
|
54
|
+
* Callbacks#add now accepts a Symbol which maps a callback to a method call on the targetted instance, also added a spec to verify this behavior
|
55
|
+
* Documented callbacks.rb
|
56
|
+
* Added DataMapper::Associations::Reference class
|
57
|
+
* Documented DataMapper::Associations::Reference class
|
58
|
+
* Upgraded BelongsToAssociation to new style
|
59
|
+
* Added AssociationsSet to handle simple "last-in" for association bindings
|
60
|
+
* Fixed extra spec loading
|
61
|
+
* Added *Association#columns
|
62
|
+
* Some refactoring in AdvancedLoadCommand regarding :include options
|
63
|
+
* Added support for class-less Mappings::Table instances, with just a string name
|
64
|
+
* HasAndBelongsToManyAssociation#join_table #left_foreign_key and #right_foreign_key reference actual Table or Column objects now
|
65
|
+
* Added :shallow_include option for HABTM joins in AdvancedLoadCommand and corresponding spec
|
66
|
+
* Added Commands::AdvancedConditions
|
67
|
+
* Added ORDER, LIMIT, OFFSET and WHERE support to AdvancedLoadCommand
|
68
|
+
* Renamed spec/has_many.rb to spec/has_many_spec.rb
|
69
|
+
* Tweaked the loading of has_many relationships; big performance boost; got rid of an extra query
|
70
|
+
* Added EmbeddedValue support, and accompanying spec
|
71
|
+
* Fleshed out AdvancedConditions a bit; added conditions_spec.rb
|
72
|
+
* Added more AdvancedConditions specs
|
73
|
+
* Added Loader to handle multi-instanced rows
|
74
|
+
* AdvancedLoadCommand replaced LoadCommand; down to 3 failing specs
|
75
|
+
* All specs pass
|
76
|
+
* Added :intercept_load finder option and accompanying spec
|
77
|
+
* Modified :intercept_load block signature to |instance,columns,row|
|
78
|
+
* HasAndBelongsToMany works, all specs pass
|
79
|
+
* Fixed a couple bugs with keys; Added DataMapper::Base#key= method
|
80
|
+
* Made DataMapper::Base#lazy_load! a little more flexible
|
81
|
+
* Removed LoadCommand overwrites from MysqlAdapter
|
82
|
+
* Default Database#single_threaded mode is true now
|
83
|
+
* Removed MysqlAdapter#initialize, which only served to setup the connections, moved to SqlAdapter
|
84
|
+
* Added SqlAdapter#create_connection and SqlAdapter#close_connection abstract methods
|
85
|
+
* Added MysqlAdapter#create_connection and MysqlAdapter#close_connection concrete methods
|
86
|
+
* Made SqlAdapter#connection a concrete method (instead of abstract), with support for single_threaded operation
|
87
|
+
* Database#setup now takes a Hash of options instead of a block-initializer
|
88
|
+
* Validation chaining should work for all association types
|
89
|
+
* Save chaining should work for has_many associations
|
90
|
+
* Added benchmarks for in-session performance to performance.rb
|
91
|
+
* Removed block conditions; They're slower and don't offer any real advantages
|
92
|
+
* Removed DeleteCommand
|
93
|
+
* Removed SaveCommand
|
94
|
+
* Removed TableExistsCommand
|
95
|
+
* Session renamed to Context
|
96
|
+
* Most command implementations moved to methods in SqlAdapter
|
97
|
+
* Removed UnitOfWork module, instead moving a slightly refactored implementation into Base
|
98
|
+
|
99
|
+
-- 0.2.1
|
100
|
+
* Added :float column support
|
101
|
+
* Added association proxies: ie: Zoo.first.exhibits.animals
|
102
|
+
* Columns stored in SortedSet
|
103
|
+
* Swig files are no longer RDOCed
|
104
|
+
* Added :date column support
|
105
|
+
* BUG: Fixed UTC issues with datetimes
|
106
|
+
* Added #to_yaml method
|
107
|
+
* Added #to_xml method
|
108
|
+
* Added #to_json method
|
109
|
+
* BUG: Fixed HasManyAssociation::Set#inspect
|
110
|
+
* BUG: Fixed #reload!
|
111
|
+
* BUG: Column copy for STI moved into Table#initialize to better handle STI with multiple mapped databases
|
112
|
+
* BUG: before_create callbacks moved in the execution flow since they weren't guaranteed to fire before
|
113
|
+
* Threading enhancements: Removed single_threaded_mode, #database block form adjusted for thread-safety
|
114
|
+
* BUG: Fixed String#blank? when a multi-line string contained a blank line (thanks zapnap!)
|
115
|
+
* Performance enhancements: (thanks wycats!)
|
116
|
+
|
117
|
+
-- 0.2.2
|
118
|
+
* Removed C extension bundles and log files from package
|
119
|
+
|
120
|
+
-- 0.2.3
|
121
|
+
* Added String#t for translation and overrides for default validation messages
|
122
|
+
* Give credit where it's due: zapnap, not pimpmaster, submitted the String#blank? patch. My bad. :-(
|
123
|
+
* MAJOR: Resolve issue with non-unique-hash values and #dirty?; now frozen original values are stored instead
|
124
|
+
* Added Base#update_attributes
|
125
|
+
* MAJOR: Queries are now passed to the database drivers in a parameterized fashion
|
126
|
+
* Updated PostgreSQL driver and adapter to current
|
127
|
+
|
128
|
+
-- 0.2.4
|
129
|
+
* Bug fixes
|
130
|
+
* Added paranoia
|
131
|
+
|
132
|
+
-- 0.2.5
|
133
|
+
* has_one bugfixes
|
134
|
+
* Added syntax for setting CHECK-constraints directly in your properties (Postgres)
|
135
|
+
* You can now set indexes with :index => true and :index => :unique
|
136
|
+
* Support for composite indexes (thanks to Jeffrey Gelens)
|
137
|
+
* Add composite scope to validates_uniqueness
|
138
|
+
* Added private/protected properties
|
139
|
+
* Remove HasOneAssociation, Make HasManyAssociation impersonate has_one relationships
|
140
|
+
* Added #get method
|
141
|
+
* Persistence module added, inheriting from DataMapper::Base no longer necessary
|
142
|
+
|
143
|
+
-- 0.3.0
|
144
|
+
* HasManyAssociation::Set now has a nil? method, so we can do stuff like cage.animal.nil?
|
data/FAQ
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
:include:QUICKLINKS
|
2
|
+
|
3
|
+
= FAQ
|
4
|
+
|
5
|
+
=== So where's my :id column?
|
6
|
+
|
7
|
+
DataMapper will NOT create an auto-incrementing <tt>:id</tt> key for you
|
8
|
+
automatically, so you'll need to either explicitly create one with
|
9
|
+
|
10
|
+
property :id, Serial
|
11
|
+
|
12
|
+
You can choose to use a natural key by doing
|
13
|
+
|
14
|
+
property :slug, String, :key => true
|
15
|
+
|
16
|
+
Remember, DataMapper supports multiple keys ("composite keys"), so if your
|
17
|
+
model has two or more keys, no big deal
|
18
|
+
|
19
|
+
property :store_id, Integer, :key => true
|
20
|
+
property :invoice_id, Integer, :key => true
|
21
|
+
|
22
|
+
=== How do I make a model paranoid?
|
23
|
+
|
24
|
+
Create a property and make it a ParanoidDateTime or ParanoidBoolean type.
|
25
|
+
|
26
|
+
property :deleted_at, ParanoidDateTime
|
27
|
+
property :deleted, ParanoidBoolean
|
28
|
+
|
29
|
+
All of your calls to <tt>##all()</tt>, <tt>##first()</tt> will be scoped
|
30
|
+
with <tt>:deleted_at => nil</tt> or <tt>:deleted => false</tt>. Plus,
|
31
|
+
you won't see deleted objects in your associations.
|
32
|
+
|
33
|
+
=== Does DataMapper do Single Table Inheritance?
|
34
|
+
|
35
|
+
This is what the Discriminator data-type is for:
|
36
|
+
|
37
|
+
class Person
|
38
|
+
include DataMapper::Resource
|
39
|
+
property :id, Serial
|
40
|
+
property :type, Discriminator ## other shared properties here
|
41
|
+
end
|
42
|
+
|
43
|
+
class Salesperson < Person; end
|
44
|
+
|
45
|
+
You can claim a column to have the type <tt>Discriminator</tt> and DataMapper will
|
46
|
+
automatically drop the class name of the inherited classes into that field of
|
47
|
+
the data-store.
|
48
|
+
|
49
|
+
=== How do I run my own commands?
|
50
|
+
|
51
|
+
repository.adapter.query("select * from users where clue > 0")
|
52
|
+
repository(:integration).adapter.query("select * from users where clue > 0")
|
53
|
+
|
54
|
+
This does not return any Users (har har), but rather Struct's that will quack
|
55
|
+
like Users. They'll be read-only as well.
|
56
|
+
|
57
|
+
<tt>repository.adapter.query</tt> shouldn't be used if you aren't expecting a result set
|
58
|
+
back. If you want to just execute something against the database, use
|
59
|
+
<tt>repository.adapter.execute</tt> instead.
|
60
|
+
|
61
|
+
|
62
|
+
=== Can I get an query log of what DataMapper is issuing?
|
63
|
+
|
64
|
+
Yup, to set this up, do:
|
65
|
+
|
66
|
+
DataMapper::Logger.new(STDOUT, 0)
|
67
|
+
|
68
|
+
Incidentally, if you'd like to send a message into the DataMapper logger, do:
|
69
|
+
|
70
|
+
DataMapper.logger.debug { "something" }
|
71
|
+
DataMapper.logger.info { "something" }
|
72
|
+
DataMapper.logger.warn { "something" }
|
73
|
+
DataMapper.logger.error { "something" }
|
74
|
+
DataMapper.logger.fatal { "something" }
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2007 Sam Smoot
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/QUICKLINKS
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
= Quick Links
|
2
|
+
|
3
|
+
* Setup and Configuration - DataMapper
|
4
|
+
* Finders and CRUD -
|
5
|
+
* Properties - DataMapper::Property
|
6
|
+
* FAQ[link:/files/FAQ.html]
|
7
|
+
* Contact Us
|
8
|
+
* Website - http://www.datamapper.org
|
9
|
+
* Bug Reports - http://wm.lighthouseapp.com/projects/4819-datamapper/overview
|
10
|
+
* IRC Channel - <tt>##datamapper</tt> on irc.freenode.net
|
11
|
+
* Mailing List - http://groups.google.com/group/datamapper/
|
12
|
+
|
data/README
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
|
2
|
+
:include:QUICKLINKS
|
3
|
+
|
4
|
+
= Why DataMapper?
|
5
|
+
|
6
|
+
== Open Development
|
7
|
+
|
8
|
+
DataMapper sports a very accessible code-base and a welcoming community.
|
9
|
+
Outside contributions and feedback are welcome and encouraged, especially
|
10
|
+
constructive criticism. Make your voice heard! Submit a
|
11
|
+
ticket[http://wm.lighthouseapp.com/projects/4819-datamapper/overview] or
|
12
|
+
patch[http://wm.lighthouseapp.com/projects/4819-datamapper/overview], speak up
|
13
|
+
on our mailing-list[http://groups.google.com/group/datamapper/], chat with us
|
14
|
+
on irc[irc://irc.freenode.net/#datamapper], write a spec, get it reviewed, ask
|
15
|
+
for commit rights. It's as easy as that to become a contributor.
|
16
|
+
|
17
|
+
== Identity Map
|
18
|
+
|
19
|
+
One row in the data-store should equal one object reference. Pretty simple idea.
|
20
|
+
Pretty profound impact. If you run the following code in ActiveRecord you'll
|
21
|
+
see all <tt>false</tt> results. Do the same in DataMapper and it's
|
22
|
+
<tt>true</tt> all the way down.
|
23
|
+
|
24
|
+
@parent = Tree.find(:first, :conditions => ['name = ?', 'bob'])
|
25
|
+
|
26
|
+
@parent.children.each do |child|
|
27
|
+
puts @parent.object_id == child.parent.object_id
|
28
|
+
end
|
29
|
+
|
30
|
+
This makes DataMapper faster and allocate less resources to get things done.
|
31
|
+
|
32
|
+
== Dirty Tracking
|
33
|
+
|
34
|
+
When you save a model back to your data-store, DataMapper will only write
|
35
|
+
the fields that actually changed. So it plays well with others. You can
|
36
|
+
use it in an Integration data-store without worrying that your application will
|
37
|
+
be a bad actor causing trouble for all of your other processes.
|
38
|
+
|
39
|
+
You can also configure which strategy you'd like to use to track dirtiness.
|
40
|
+
|
41
|
+
== Eager Loading
|
42
|
+
|
43
|
+
Ready for something amazing? The following example executes only two queries.
|
44
|
+
|
45
|
+
zoos = Zoo.all
|
46
|
+
first = zoos.first
|
47
|
+
first.exhibits # Loads the exhibits for all the Zoo objects in the zoos variable.
|
48
|
+
|
49
|
+
Pretty impressive huh? The idea is that you aren't going to load a set of
|
50
|
+
objects and use only an association in just one of them. This should hold up
|
51
|
+
pretty well against a 99% rule. When you don't want it to work like this, just
|
52
|
+
load the item you want in it's own set. So the DataMapper thinks ahead. We
|
53
|
+
like to call it "performant by default". This feature single-handedly wipes
|
54
|
+
out the "N+1 Query Problem". No need to specify an <tt>include</tt> option in
|
55
|
+
your finders.
|
56
|
+
|
57
|
+
== Laziness Can Be A Virtue
|
58
|
+
|
59
|
+
Text fields are expensive in data-stores. They're generally stored in a
|
60
|
+
different place than the rest of your data. So instead of a fast sequential
|
61
|
+
read from your hard-drive, your data-store server has to hop around all over the
|
62
|
+
place to get what it needs. Since ActiveRecord returns everything by default,
|
63
|
+
adding a text field to a table slows everything down drastically, across the
|
64
|
+
board.
|
65
|
+
|
66
|
+
Not so with the DataMapper. Text fields are treated like in-row associations
|
67
|
+
by default, meaning they only load when you need them. If you want more
|
68
|
+
control you can enable or disable this feature for any field (not just
|
69
|
+
text-fields) by passing a @lazy@ option to your field mapping with a value of
|
70
|
+
<tt>true</tt> or <tt>false</tt>.
|
71
|
+
|
72
|
+
class Animal
|
73
|
+
include DataMapper::Resource
|
74
|
+
property :name, String
|
75
|
+
property :notes, Text, :lazy => false
|
76
|
+
end
|
77
|
+
|
78
|
+
Plus, lazy-loading of text fields happens automatically and intelligently when
|
79
|
+
working with associations. The following only issues 2 queries to load up all
|
80
|
+
of the notes fields on each animal:
|
81
|
+
|
82
|
+
animals = Animal.all
|
83
|
+
animals.each do |pet|
|
84
|
+
pet.notes
|
85
|
+
end
|
86
|
+
|
87
|
+
== Plays Well With Others
|
88
|
+
|
89
|
+
In ActiveRecord, all your fields are mapped, whether you want them or not.
|
90
|
+
This slows things down. In the DataMapper you define your mappings in your
|
91
|
+
model. So instead of an _ALTER TABLE ADD field_ in your data-store, you simply
|
92
|
+
add a <tt>property :name, :string</tt> to your model. DRY. No schema.rb. No
|
93
|
+
migration files to conflict or die without reverting changes. Your model
|
94
|
+
drives the data-store, not the other way around.
|
95
|
+
|
96
|
+
Unless of course you want to map to a legacy data-store. Raise your hand if you
|
97
|
+
like seeing a method called <tt>col2Name</tt> on your model just because
|
98
|
+
that's what it's called in an old data-store you can't afford to change right
|
99
|
+
now? In DataMapper you control the mappings:
|
100
|
+
|
101
|
+
class Fruit
|
102
|
+
include DataMapper::Resource
|
103
|
+
storage_names[:repo] = 'frt'
|
104
|
+
property :name, String, :field => 'col2Name'
|
105
|
+
end
|
106
|
+
|
107
|
+
== All Ruby, All The Time
|
108
|
+
|
109
|
+
It's great that ActiveRecord allows you to write SQL when you need to, but
|
110
|
+
should we have to so often?
|
111
|
+
|
112
|
+
DataMapper supports issuing your own query, but it also provides more helpers
|
113
|
+
and a unique hash-based condition syntax to cover more of the use-cases where
|
114
|
+
issuing your own SQL would have been the only way to go. For example, any
|
115
|
+
finder option that's non-standard is considered a condition. So you can write
|
116
|
+
<tt>Zoo.all(:name => 'Dallas')</tt> and DataMapper will look for zoos with the
|
117
|
+
name of 'Dallas'.
|
118
|
+
|
119
|
+
It's just a little thing, but it's so much nicer than writing
|
120
|
+
<tt>Zoo.find(:all, :conditions => ['name = ?', 'Dallas'])</tt>. What if you
|
121
|
+
need other comparisons though? Try these:
|
122
|
+
|
123
|
+
Zoo.first(:name => 'Galveston')
|
124
|
+
|
125
|
+
# 'gt' means greater-than. We also do 'lt'.
|
126
|
+
Person.all(:age.gt => 30)
|
127
|
+
|
128
|
+
# 'gte' means greather-than-or-equal-to. We also do 'lte'.
|
129
|
+
Person.all(:age.gte => 30)
|
130
|
+
|
131
|
+
Person.all(:name.not => 'bob')
|
132
|
+
|
133
|
+
# If the value of a pair is an Array, we do an IN-clause for you.
|
134
|
+
Person.all(:name.like => 'S%', :id => [1, 2, 3, 4, 5])
|
135
|
+
|
136
|
+
# An alias for Zoo.find(11)
|
137
|
+
Zoo[11]
|
138
|
+
|
139
|
+
# Does a NOT IN () clause for you.
|
140
|
+
Person.all(:name.not => ['bob','rick','steve'])
|
141
|
+
|
142
|
+
See? Fewer SQL fragments dirtying your Ruby code. And that's just a few of the
|
143
|
+
nice syntax tweaks DataMapper delivers out of the box...
|
data/lib/dm-core.rb
ADDED
@@ -0,0 +1,213 @@
|
|
1
|
+
# This file begins the loading sequence.
|
2
|
+
#
|
3
|
+
# Quick Overview:
|
4
|
+
# * Requires fastthread, support libs, and base.
|
5
|
+
# * Sets the application root and environment for compatibility with frameworks
|
6
|
+
# such as Rails or Merb.
|
7
|
+
# * Checks for the database.yml and loads it if it exists.
|
8
|
+
# * Sets up the database using the config from the Yaml file or from the
|
9
|
+
# environment.
|
10
|
+
#
|
11
|
+
|
12
|
+
require 'date'
|
13
|
+
require 'pathname'
|
14
|
+
require 'set'
|
15
|
+
require 'time'
|
16
|
+
require 'yaml'
|
17
|
+
|
18
|
+
require 'rubygems'
|
19
|
+
|
20
|
+
gem 'addressable', '>=1.0.4'
|
21
|
+
require 'addressable/uri'
|
22
|
+
|
23
|
+
gem 'extlib', '=0.9.2'
|
24
|
+
require 'extlib'
|
25
|
+
|
26
|
+
begin
|
27
|
+
require 'fastthread'
|
28
|
+
rescue LoadError
|
29
|
+
# fastthread not installed
|
30
|
+
end
|
31
|
+
|
32
|
+
dir = Pathname(__FILE__).dirname.expand_path / 'dm-core'
|
33
|
+
|
34
|
+
require dir / 'support'
|
35
|
+
require dir / 'resource'
|
36
|
+
require dir / 'model'
|
37
|
+
|
38
|
+
require dir / 'type'
|
39
|
+
require dir / 'type_map'
|
40
|
+
require dir / 'types'
|
41
|
+
require dir / 'hook'
|
42
|
+
require dir / 'associations'
|
43
|
+
require dir / 'auto_migrations'
|
44
|
+
require dir / 'identity_map'
|
45
|
+
require dir / 'logger'
|
46
|
+
require dir / 'migrator'
|
47
|
+
require dir / 'naming_conventions'
|
48
|
+
require dir / 'property_set'
|
49
|
+
require dir / 'query'
|
50
|
+
require dir / 'transaction'
|
51
|
+
require dir / 'repository'
|
52
|
+
require dir / 'scope'
|
53
|
+
require dir / 'property'
|
54
|
+
require dir / 'adapters'
|
55
|
+
require dir / 'collection'
|
56
|
+
require dir / 'is'
|
57
|
+
|
58
|
+
# == Setup and Configuration
|
59
|
+
# DataMapper uses URIs or a connection hash to connect to your data-store.
|
60
|
+
# URI connections takes the form of:
|
61
|
+
# DataMapper.setup(:default, 'protocol://username:password@localhost:port/path/to/repo')
|
62
|
+
#
|
63
|
+
# Breaking this down, the first argument is the name you wish to give this
|
64
|
+
# connection. If you do not specify one, it will be assigned :default. If you
|
65
|
+
# would like to connect to more than one data-store, simply issue this command
|
66
|
+
# again, but with a different name specified.
|
67
|
+
#
|
68
|
+
# In order to issue ORM commands without specifying the repository context, you
|
69
|
+
# must define the :default database. Otherwise, you'll need to wrap your ORM
|
70
|
+
# calls in <tt>repository(:name) { }</tt>.
|
71
|
+
#
|
72
|
+
# Second, the URI breaks down into the access protocol, the username, the
|
73
|
+
# server, the password, and whatever path information is needed to properly
|
74
|
+
# address the data-store on the server.
|
75
|
+
#
|
76
|
+
# Here's some examples
|
77
|
+
# DataMapper.setup(:default, "sqlite3://path/to/your/project/db/development.db")
|
78
|
+
# DataMapper.setup(:default, "mysql://localhost/dm_core_test")
|
79
|
+
# # no auth-info
|
80
|
+
# DataMapper.setup(:default, "postgres://root:supahsekret@127.0.0.1/dm_core_test")
|
81
|
+
# # with auth-info
|
82
|
+
#
|
83
|
+
#
|
84
|
+
# Alternatively, you can supply a hash as the second parameter, which would
|
85
|
+
# take the form:
|
86
|
+
#
|
87
|
+
# DataMapper.setup(:default, {
|
88
|
+
# :adapter => 'adapter_name_here',
|
89
|
+
# :database => "path/to/repo",
|
90
|
+
# :username => 'username',
|
91
|
+
# :password => 'password',
|
92
|
+
# :host => 'hostname'
|
93
|
+
# })
|
94
|
+
#
|
95
|
+
# === Logging
|
96
|
+
# To turn on error logging to STDOUT, issue:
|
97
|
+
#
|
98
|
+
# DataMapper::Logger.new(STDOUT, 0)
|
99
|
+
#
|
100
|
+
# You can pass a file location ("/path/to/log/file.log") in place of STDOUT.
|
101
|
+
# see DataMapper::Logger for more information.
|
102
|
+
#
|
103
|
+
module DataMapper
|
104
|
+
extend Assertions
|
105
|
+
|
106
|
+
def self.root
|
107
|
+
@root ||= Pathname(__FILE__).dirname.parent.expand_path
|
108
|
+
end
|
109
|
+
|
110
|
+
##
|
111
|
+
# Setups up a connection to a data-store
|
112
|
+
#
|
113
|
+
# @param Symbol name a name for the context, defaults to :default
|
114
|
+
# @param [Hash{Symbol => String}, Addressable::URI, String] uri_or_options
|
115
|
+
# connection information
|
116
|
+
#
|
117
|
+
# @return Repository the resulting setup repository
|
118
|
+
#
|
119
|
+
# @raise ArgumentError "+name+ must be a Symbol, but was..." indicates that
|
120
|
+
# an invalid argument was passed for name[Symbol]
|
121
|
+
# @raise [ArgumentError] "+uri_or_options+ must be a Hash, URI or String,
|
122
|
+
# but was..." indicates that connection information could not be gleaned
|
123
|
+
# from the given uri_or_options<Hash, Addressable::URI, String>
|
124
|
+
#
|
125
|
+
# -
|
126
|
+
# @api public
|
127
|
+
def self.setup(name, uri_or_options)
|
128
|
+
assert_kind_of 'name', name, Symbol
|
129
|
+
assert_kind_of 'uri_or_options', uri_or_options, Addressable::URI, Hash, String
|
130
|
+
|
131
|
+
case uri_or_options
|
132
|
+
when Hash
|
133
|
+
adapter_name = uri_or_options[:adapter].to_s
|
134
|
+
when String, Addressable::URI
|
135
|
+
uri_or_options = Addressable::URI.parse(uri_or_options) if uri_or_options.kind_of?(String)
|
136
|
+
adapter_name = uri_or_options.scheme
|
137
|
+
end
|
138
|
+
|
139
|
+
class_name = Extlib::Inflection.classify(adapter_name) + 'Adapter'
|
140
|
+
|
141
|
+
unless Adapters::const_defined?(class_name)
|
142
|
+
lib_name = "#{Extlib::Inflection.underscore(adapter_name)}_adapter"
|
143
|
+
begin
|
144
|
+
require root / 'lib' / 'dm-core' / 'adapters' / lib_name
|
145
|
+
rescue LoadError
|
146
|
+
require lib_name
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
Repository.adapters[name] = Adapters::const_get(class_name).new(name, uri_or_options)
|
151
|
+
end
|
152
|
+
|
153
|
+
##
|
154
|
+
# Block Syntax
|
155
|
+
# Pushes the named repository onto the context-stack,
|
156
|
+
# yields a new session, and pops the context-stack.
|
157
|
+
#
|
158
|
+
# Non-Block Syntax
|
159
|
+
# Returns the current session, or if there is none,
|
160
|
+
# a new Session.
|
161
|
+
#
|
162
|
+
# @param [Symbol] args the name of a repository to act within or return, :default is default
|
163
|
+
# @yield [Proc] (optional) block to execute within the context of the named repository
|
164
|
+
# @demo spec/integration/repository_spec.rb
|
165
|
+
def self.repository(*args, &block) # :yields: current_context
|
166
|
+
if args.size > 1
|
167
|
+
raise ArgumentError, "Can only pass in one optional argument, but passed in #{args.size} arguments", caller
|
168
|
+
end
|
169
|
+
|
170
|
+
if args.any? && !args.first.kind_of?(Symbol)
|
171
|
+
raise ArgumentError, "First optional argument must be a Symbol, but was #{args.first.inspect}", caller
|
172
|
+
end
|
173
|
+
|
174
|
+
name = args.first
|
175
|
+
|
176
|
+
current_repository = if name
|
177
|
+
Repository.context.detect { |r| r.name == name } || Repository.new(name)
|
178
|
+
else
|
179
|
+
Repository.context.last || Repository.new(Repository.default_name)
|
180
|
+
end
|
181
|
+
|
182
|
+
return current_repository unless block_given?
|
183
|
+
|
184
|
+
current_repository.scope(&block)
|
185
|
+
end
|
186
|
+
|
187
|
+
# A logger should always be present. Lets be consistent with DO
|
188
|
+
Logger.new(nil, :off)
|
189
|
+
|
190
|
+
##
|
191
|
+
# destructively migrates the repository upwards to match model definitions
|
192
|
+
#
|
193
|
+
# @param [Symbol] name repository to act on, :default is the default
|
194
|
+
def self.migrate!(name = Repository.default_name)
|
195
|
+
repository(name).migrate!
|
196
|
+
end
|
197
|
+
|
198
|
+
##
|
199
|
+
# drops and recreates the repository upwards to match model definitions
|
200
|
+
#
|
201
|
+
# @param [Symbol] name repository to act on, :default is the default
|
202
|
+
def self.auto_migrate!(name = Repository.default_name)
|
203
|
+
repository(name).auto_migrate!
|
204
|
+
end
|
205
|
+
|
206
|
+
def self.auto_upgrade!(name = Repository.default_name)
|
207
|
+
repository(name).auto_upgrade!
|
208
|
+
end
|
209
|
+
|
210
|
+
def self.prepare(*args, &blk)
|
211
|
+
yield repository(*args)
|
212
|
+
end
|
213
|
+
end
|