dm-core 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|