diff --git a/.gitignore b/.gitignore index 1799aac..fc0956c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -output/* _site/ .DS_Store *.swp diff --git a/404.html b/404.html new file mode 100644 index 0000000..79e6142 --- /dev/null +++ b/404.html @@ -0,0 +1,9 @@ +--- +layout: default +title: File Not Found +--- + +

Sorry, that file could not be found

+ +

We recently switched hosts, so some older links no longer exist.

+

Try the menu above or see the wiki.

diff --git a/content/CNAME b/CNAME similarity index 100% rename from content/CNAME rename to CNAME diff --git a/Rakefile b/Rakefile deleted file mode 100644 index 85d2f95..0000000 --- a/Rakefile +++ /dev/null @@ -1,24 +0,0 @@ -require 'grancher' - -# index.txt is always dirty, so it's always rebuilt -desc 'Publish the website by committing on the master branch' -task :publish => 'output/.published' - -file 'output/index.html' => FileList["content/**/*", "Sitefile"] do - sh "webby build" -end - -file 'output/.published' => 'output/index.html' do - touch 'output/.published' - grancher = Grancher.new do |g| - g.branch = 'master' - g.push_to = 'origin' - g.repo = '.' # defaults to '.' - g.message = 'Updated website' # defaults to 'Updated files.' - - # Put the website-directory in the root - g.directory 'output' - end - - grancher.commit -end diff --git a/Sitefile b/Sitefile deleted file mode 100644 index 7a49766..0000000 --- a/Sitefile +++ /dev/null @@ -1,5 +0,0 @@ -task :default => :build - -desc 'deploy the site to the webserver' -task :deploy => [ :build, 'deploy:rsync' ] - diff --git a/_config.yml b/_config.yml new file mode 100644 index 0000000..5f641a1 --- /dev/null +++ b/_config.yml @@ -0,0 +1,2 @@ +pygments: true +permalink: none diff --git a/_layouts/articles.html b/_layouts/articles.html new file mode 100644 index 0000000..5dd3d5d --- /dev/null +++ b/_layouts/articles.html @@ -0,0 +1,5 @@ +--- +layout: default +--- + +{{ content }} diff --git a/layouts/default.rhtml b/_layouts/default.html similarity index 82% rename from layouts/default.rhtml rename to _layouts/default.html index 4b63ace..42030b7 100644 --- a/layouts/default.rhtml +++ b/_layouts/default.html @@ -1,21 +1,17 @@ ---- -extension: html -filter: erb ---- - DataMapper - <%= @page.title %> + DataMapper - {{ page.title }} - + - +
- <%= @content %> + {{ content }}
diff --git a/_posts/2008-03-22-the_great_refactoring.markdown b/_posts/2008-03-22-the_great_refactoring.markdown new file mode 100644 index 0000000..3a596fe --- /dev/null +++ b/_posts/2008-03-22-the_great_refactoring.markdown @@ -0,0 +1,111 @@ +--- +layout: articles +categories: articles +title: The Great Refactoring +created_at: 2008-03-22T15:55:50-05:00 +summary: Change is afoot +author: afrench and ssmoot +--- + +{{ page.title }} +================ + +"Tip" DataMapper (hosted on [github](http://github.com/datamapper/dm-core)) is +going through a dramatic re-factor. Here's a quick summary of the anticipated +NEW public API. + +Not Just for Databases Anymore +------------------------------ + +DataMapper's class terminology will change and 'de-couple' itself from +database-specific terminology. Gone are "database", "table", "column" and +"join". Say hello to "Repository", "Resource", "Property", and "Link". + +"Why would you want to do that?", you ask. Ultimately it's because DataMapper +will soon support different types of persistence layers, not just databases. +It'll talk to all sorts of things like web services (REST and such), XML files, +YAML files, non-relational databases, even custom file-types or services of your +own design. Just implement an Adapter that conforms to a certain API and +DataMapper could support any type of data store. No need for a completely +separate library or anything. + +As an added benefit, DataMapper become more "RESTful". [Ryan Tomayko](http://tomayko.com/writings/rest-to-my-wife) +has a very good explanation +of REST that all should read entitled [How I Explained REST to My Wife](http://tomayko.com/writings/rest-to-my-wife). + +Model Definitions Are a Little Different +---------------------------------------- + +Since we're changing up the terminology, model definitions are going to change +up a little bit. Here's what a Planet model would look like using the new API: + +{% highlight ruby linenos %} +class Planet + include DataMapper::Resource + + resource_names[:legacy] = 'dying_planets' + + property :name, String + property :age, Integer + property :core, String, :private => true +end +{% endhighlight %} + +A couple of things are going on here. First, DataMapper::Base and +DataMapper::Persistence are gone and replaced with <%= +doc('DataMapper::Resource') %>. Next `set_table_name` has been replaced with +`resource_names` hash where you specify which arena play occurs in. After that +we have a couple of Property definitions that look a little different. + +First off, Properties will no longer take `:symbols` for their types and instead +take real constants like String, Integer, DateTime. Also on the docket are the +ability to define your own custom types. + +Think about that for a minute. If developers are able to define their own custom +types with their own materialization and serialization methods, DataMapper will +be able to support all kinds of wild data-types like GIS information, network +information, marshaled objects, JSON...pretty much anything a developer might +need, or want. + +A Command-Line Interface +------------------------ + +Taking a lesson from web frameworks, DataMapper will sport an interactive +command-line so that you can browse your resources without the need to load up +the entire environment of your application. Here's an example session: + +{% highlight bash linenos %} +$ dm mysql://root@localhost/great_musicians # connecting to a repository +{% endhighlight %} + +An IRB session boots up... + +{% highlight ruby %} +>> the_king = Person.first(:name => 'elvis') +>> the_king.alive? # => maybe +{% endhighlight %} + +This is very similar to `script/console` in Rails or `merb -i` in Merb, only it +won't load up the entire environment of your application, just your DataMapper +resources and their associations, methods, and such. If you prefer "fat models", +this will constitute the core of your application. + +How This All Comes Together +--------------------------- + +This is the coolest new feature of DataMapper: we're skipping all the way from +0.3.0 to 0.9! Get excited, contact the press, fire up the blogosphere! Its a +huge jump and we're honestly concerned that people may not be able to handle it. + +Alright, so it's not _that_ big of a deal, but we're confident that all of this +will get DataMapper so close to going 1.0 that we'll be able to taste it. To get +there, DataMapper's more advanced features like single table inheritance, +paranoia, and chained associations will be re-implimented to use all this new +stuff, and then we're sure 0.9 will need a touch up or two. + +So close....so very very close... + +Stay tuned in to the [mailing list](http://groups.google.com/group/datamapper), +check up on the [wiki](http://datamapper.org/), chat it up in +[#datamapper](irc://irc.freenode.net/#datamapper) and watch +[github commit messages](http://github.com/datamapper/dm-core/commits/master) for updates. diff --git a/_posts/2008-03-29-spotlight_on_cpk.markdown b/_posts/2008-03-29-spotlight_on_cpk.markdown new file mode 100644 index 0000000..0c76e47 --- /dev/null +++ b/_posts/2008-03-29-spotlight_on_cpk.markdown @@ -0,0 +1,107 @@ +--- +layout: articles +categories: articles +title: Spotlight on... Composite Keys +created_at: 2008-03-29T19:09:07-05:00 +summary: When a Primary Key Just Isn't Enough +author: afrench and ssmoot +--- + +{{ page.title }} +================ + +For those of us who have taken a course on database design in college or +university, you may have run across a concept called 'Composite Primary Keys' +(or sometimes 'Compound Keys' or 'Concatenated Keys', and abbreviated +CPK(Composite Primary Keys)s). It's usually right before you tackle JOINs and +right after you fight with the "surrogate key" or "primary key" concept. + +Boiling CPK(Composite Primary Keys)s down, they're just a way of identifying a +row by multiple keys rather than one. So instead of an auto_incrementing +"serial" primary key (as in `id`), you'd have a combination of `some_column` and +`some_other_column` that would uniquely identify a row. + +CPK(Composite Primary Keys)s aren't as prevalent in the Rails world as Serial +Keys (such as the auto-incrementing `:id` column), but if you're going to +support legacy, integration or reporting databases or just de-normalized schemas +for performance reasons, they can be invaluable. So sure, Surrogate Keys are a +great convenience, but sometimes they just aren't an option. + +Let's briefly take a look at how a few ruby ORMs support Composite Primary Keys +and then we'll talk about DataMapper's support for CPK(Composite Primary Keys)s. + +ActiveRecord +------------ + +In short, ActiveRecord doesn't support CPK(Composite Primary Keys)s without the +help of an external library. [Dr. Nic Williams](http://drnicwilliams.com/about/) +[Composite Keys](http://compositekeys.rubyforge.org/) is an effort to overcome +this limitation. + +Sequel +------ + +Unlike ActiveRecord, Sequel supports CPK(Composite Primary Keys)s natively: + +{% highlight ruby linenos %} +class Post < Sequel::Model + set_primary_key [ :category, :title ] +end + +post = Post.get('ruby', 'hello world') +post.key # => [ 'ruby', 'hello world' ] +{% endhighlight %} + +p(attribution). example compiled from . + +DataMapper +---------- + +The latest DataMapper was designed from the ground up to support CPK(Composite Primary Keys)s: + +{% highlight ruby linenos %} +class Pig + include DataMapper::Resource + + property :id, Integer, :key => true + property :slug, String, :key => true + property :name, String +end + +pig = Pig.get(1, 'Porky') + +pig.key # => [ 1, 'Wilbur' ] +{% endhighlight %} + +We declared our keys by adding the `:key => true` to the appropriate properties. +The order is important as it will determine the order keys are addressed +throughout the system. + +Next, we mixed and matched the keys' types. `:id` is a Integer, but `:slug` is a +String. DataMapper didn't flinch when we defined a key column as a String +because it supports [Natural Keys](http://en.wikipedia.org/wiki/Natural_key) as +well. + +Lastly, when retrieving rows via `get` and `[]` with a CPK(Composite Primary +Keys), we supplied the keys in the order they were defined within our model. For +example, we defined `:id` first, then `:slug` second; later, we retrieved Porky +by specifying his `:id` and `:slug` in the same order. Additionally, when we +asked Wilbur for his keys, he handed us an array in the order the keys were +defined. + +We didn't need to mix in an external library to get support for CPK(Composite +Primary Keys)s, nor did we need to call a `set_primary_key` method and then +supply more than one key to it. DataMapper supports Composite Primary Keys +intuitively and without compromise! + +In later "Spotlight On..." articles, we'll examine and demonstrate other +DataMapper features or persistence concepts as well as compare similar features +with other ORMs or libraries. + +Contribute a "Spotlight On..." Article +-------------------------------------- + +p(newRelease). Got something important to say? Want something explained a little
+better or demonstrated? Contribute or request a "Spotlight On..."
article! +Email the [DataMapper Mailing List](http://groups.google.com/group/datamapper) with the request or
+contribution and we'll post it here. diff --git a/_posts/2008-04-03-spotlight_on_laziness.markdown b/_posts/2008-04-03-spotlight_on_laziness.markdown new file mode 100644 index 0000000..849a568 --- /dev/null +++ b/_posts/2008-04-03-spotlight_on_laziness.markdown @@ -0,0 +1,193 @@ +--- +layout: articles +categories: articles +title: Spotlight on... Laziness +created_at: 2008-04-03T23:08:12-05:00 +summary: The Fastest Code Is No Code +author: afrench +--- + +{{ page.title }} +================ + +Laziness. It means "an unwillingness to work or use energy" and typically +indicates that the dishes don't get washed after lunch, the bath tub doesn't get +cleaned, and the trash sits around an extra few days and stinks up the place. + +But that very same definition in software takes on a whole new meaning: To avoid +doing work you don't have to do for as long as you can avoid it; sometimes never +doing it at all. It's a good thing. It means that expensive and slow tasks can +be put off until the very last cycle possible and thus only incur their cost +when it really is worth it. Maybe you never execute the code at all. + +You could put off running a specific subroutine because it's slow, or because it +locks a file that might be needed elsewhere, or because instantiating the +resulting object eats up RAM. Either way, deferring execution of a block of code +until the very last possible moment can be the difference between a snappy +application that rarely slows down and a slow application that rarely speeds up. + +But laziness isn't without its hidden costs. If you put off everything to the +very last moment, you forfeit the opportunity to do more than one thing at a +time, and likely create more work for yourself, rather than less. + +So where's the balance? + +Ultimately, it depends on your application. The tools you use should offer you +the flexibility you need to design your application optimally. Every system, +after all, is unique and breaks the mold of systems before it. + +This brings us to DataMapper. + +Lazy-loading attributes +----------------------- + +You likely already know that DataMapper supports lazy properties. + +{% highlight ruby linenos %} +class Post + include DataMapper::Resource + + property :id, Serial # auto_incrementing primary key + property :title, String, :lazy => true # intentionally lazy + property :body, Text # lazy by default +end +{% endhighlight %} + +In this case, we're intentionally marking this Post's `:title` property as lazy, +as well as letting the `:body` be lazy by default. If we go and inspect our +query log for the retrieval of a post with the ID of 1, we see + +{% highlight sql %} +SELECT `id` FROM `posts` WHERE `id` = 1 +{% endhighlight %} + +DataMapper didn't request the two lazy columns. But when we call `.title` off of +our post, we suddenly see + +{% highlight sql %} +SELECT `title` FROM `posts` WHERE `id` = 1 +{% endhighlight %} + +This is the very definition of a lazy-loaded property; The lazy column didn't +get requested from our data store until we actually needed it, and no sooner. + +But this is just for one individual instance of a post. How does this behave +when we have a collection of posts and iteratively call the `.title` method? + +{% highlight sql %} +SELECT `title` FROM `posts` WHERE `id` IN (1, 2, 3, 4, 5) +{% endhighlight %} + +DataMapper loaded up the title for all of the posts in our collection in one +query. It didn't issue the lazy-load retrieval from above over and over for each +individual post, nor did it chicken out and issue the lazy-load retrieval for +ALL of the posts in the data store. + +When you retrieve a set of results using DataMapper's `.all`, each instance it +returns knows about the others in the result set, which makes it brutally simple +to issue just one lazy-load retrieval of `:title`, and thus solving the n+1 +query problem without having to do anything special in the initial retrieval. + +Contextual Lazy-loading +----------------------- + +With a recent commit by [Guy van den Berg](http://www.guyvdb.info/ruby/lazy-loading-properties-in-datamapper/), +DataMapper just got a whole lot more flexible. + +Most applications have only a few main views of a resource: a brief summary view +used in listing results, a complete representation that might appear on a show +page and a comprehensive view for when someone is editing something and needs +access to metadata. Wouldn't it be nice to lump all of the lazy-load retrieval +queries into one query which loads up multiple lazy properties, rather than +query after query for each lazy property as you call them? + +DataMapper now does this! + +{% highlight ruby linenos %} +class Post + include DataMapper::Resource + + property :id, Serial + property :title, String, :lazy => [ :summary, :brief ] + property :body, Text, :lazy => [ :summary ] +end +{% endhighlight %} + +So now, when you load an attribute with the `:summary` context, DataMapper will +load up all of the other lazy-loaded properties marked `:summary` in one query +to the data store. + +In your query log, you'll see: + +{% highlight sql linenos %} +-- initial load +SELECT `id` FROM `posts` + +-- lazy-loading of multiple properties in a given context in one query +SELECT `id`, `title`, `body` FROM `posts` +{% endhighlight %} + +If you use this wisely, it would mean that DataMapper will never load more than +it needs nor will it ever fire off more than the absolutely necessary amount of +queries to get the job done. + +It's lazy ;-) + +Strategic Eager Loading +----------------------- + +Well, not for everything. + +Returning for a little bit to our "loaded set" discussion from above, every item +you pull out of the data store is aware of any other item that got pulled along +with it. This is a very powerful feature which lets DataMapper defeat n+1 query +problems when dealing with associations as well as lazy-loading of properties. + +For example, this is a severe "no no" in ActiveRecord: + +{% highlight ruby linenos %} + Zoo.find(:all).each do |zoo| + zoo.animals + end +{% endhighlight %} + +This is a very bad idea because the ORM must query the "animals" table over and +over again to load the association for each iteration. It's far better to use +`Zoo.find(:all, :include => [ :animals ]).each {}` because a JOIN occurs and +everything is retrieved in 1 query. + +But the same issue doesn't exist in DataMapper. Each instance is aware of the +other instances it was retrieved with. The same iterator example from above only +fires off 2 queries as you're iterating and calling the association inside the +`each`. If you forget to `:include => [ :association ]` in the initial query, +DataMapper only ever fires off one more query to get what it needs. + +[Yehuda Katz](http://www.yehudakatz.com/) has aptly named this 'Strategic Eager Loading'. + +Getting Around to It +-------------------- + +A conclusion for our talk about laziness will be written whenever I get around +to it. + +For now, just remember that DataMapper embraces lazy-loading, yet isn't overly +zealous when the lazy properties are finally retrieved. It also fills +associations strategically, and assumes you're going to iterate over the set of +results. You don't have to catch yourself when you write an iterator because +DataMapper loads associations for all of your items in the set, rather than on a +one-by-one basis. + +And, most importantly, you can avoid doing work you don't have to do for as long +as you can avoid it. + +
+ +Contribute a "Spotlight On..." Article +-------------------------------------- + +Got something important to say? Want something explained a little
+better or demonstrated? Contribute or request a "Spotlight On..."
article! +Email the [DataMapper Mailing List](http://groups.google.com/group/datamapper) with the request or
+contribution and we'll post it here. + +
diff --git a/_posts/2008-04-10-datamapper_talk_atmwrc08.markdown b/_posts/2008-04-10-datamapper_talk_atmwrc08.markdown new file mode 100644 index 0000000..e67f785 --- /dev/null +++ b/_posts/2008-04-10-datamapper_talk_atmwrc08.markdown @@ -0,0 +1,21 @@ +--- +layout: articles +categories: articles +title: DataMapper Presented at MWRC 2008 +created_at: 2008-04-10T23:07:39-05:00 +summary: Presentation on DataMapper by Wycats +author: afrench +--- + +{{ page.title }} +================ + +!/images/wykatz_at_mwrc2008.png(Yehuda Katz' presentation entitled 'Faster, Better ORM With DataMapper)! + +At this year's [Mountain West Ruby Conference 2008](http://mtnwestrubyconf.org/), +DataMapper's own Yehuda Katz gave a presentation entitled "Faster, Better ORM +With DataMapper." + +The video of the talk is available for viewing and downloading at + +and is brought to you by the good people at [confreaks.com](http://confreaks.com). diff --git a/_posts/2008-05-07-stunningly_easy_way_to_live_on_the_edge.markdown b/_posts/2008-05-07-stunningly_easy_way_to_live_on_the_edge.markdown new file mode 100644 index 0000000..68f82bf --- /dev/null +++ b/_posts/2008-05-07-stunningly_easy_way_to_live_on_the_edge.markdown @@ -0,0 +1,145 @@ +--- +layout: articles +categories: articles +title: The Stunningly Easy Way to Live On The Edge Of DataMapper +created_at: 2008-05-07T19:04:34-05:00 +summary: A little sake goes a long way +author: afrench +--- + +{{ page.title }} +================ + +DataMapper is organized into sub-projects, much like +[Merb](http://www.merbivore.com), and that tends to confuse even the people +working on it....until recently. Michael Ivey, an active contributer to the Merb +project, and our very own Dan Kubb have collaborated on a set of Sake tasks to +help automate and streamline checking out, packaging, installing, uninstalling, +updating, repackaging, and reinstalling the DataMapper and Merb projects. + +If you like to live life on the edge, this is the happiest way to do it. + +### Step 0 - The Setup + +A couple of very basic requirements before we begin. First, you'll need to have +an up-to-date installation of [Rubygems](http://www.rubygems.org/), the Ruby +package management system. To check what version you have do: + +{% highlight bash linenos %} +gem --version +{% endhighlight %} + +If you aren't on 1.2.x, update by running + +{% highlight bash linenos %} +sudo gem update --system +{% endhighlight %} + +Next, you'll need `git`. It's the source code management tool DataMapper uses. +Its installation is left up to the reader, but here's a few good resources to go +to for help: + +* [Git - Fast Version Control System](http://git.or.cz/) - Homepage +* [Installing GIT on MAC OSX 10.5 Leopard](http://dysinger.net/2007/12/30/installing-git-on-mac-os-x-105-leopard/) +* [Git On Windows](http://ropiku.wordpress.com/2007/12/28/git-on-windows/) +* [Installing Git on Ubuntu](http://chrisolsen.org/2008/03/10/installing-git-on-ubuntu/) + +After that, you'll need to `gem uninstall` any of the "dm-\*" projects you +already have installed. This includes 'data_objects' and its associated +adapters. + +Next, you'll need a few of the base dependencies. To install them, run + +{% highlight bash %} +sudo gem install addressable english rspec +{% endhighlight %} + +Once that's done, do the following: + +{% highlight bash linenos %} +mkdir -p ~/src +cd ~/src +{% endhighlight %} + +### Step 1 - Have Some Sake + +No, not the wonderful alcoholic beverage, the +[system-wide rake tasks library](http://errtheblog.com/posts/60-sake-bomb) by +[PJ Hyett and Chris Wanstrath of Err. The Blog](http://errtheblog.com/). +Ivey's and dkubb's automated +installation and reinstallation scripts are written as sake tasks, so you'll +need it installed on your machine. + +{% highlight bash linenos %} +sudo gem install sake +{% endhighlight %} + +Once you're done, you should be able to see sake in your path by executing +`which sake` and see where `gem` installed it. + +### Step 2 - Install the Tasks + +Now that you're all setup with sake and the `src` directory, it's time to +install the sake tasks. They can be found at +and are very easily installed by doing: + +{% highlight bash linenos %} +sake -i http://github.com/dkubb/dm-dev/raw/master/dm-dev.sake +{% endhighlight %} + +The tasks that get installed are available for perusal by issuing `sake -T` + +{% highlight bash %} +$ sake -T +sake dm:clone # Clone a copy of the DataMapper repository and dependencies +sake dm:gems:refresh # Pull fresh copies of DataMapper and refresh all the gems +sake dm:gems:wipe # Uninstall all RubyGems related to DataMapper +sake dm:install # Install dm-core, dm-more and do +sake dm:install:core # Install dm-core +sake dm:install:do # Install do drivers +sake dm:install:do:data_objects # Install data_objects +sake dm:install:do:mysql # Install do_mysql +sake dm:install:do:postgres # Install do_postgres +sake dm:install:do:sqlite3 # Install do_sqlite3 +sake dm:install:more # Install dm-more +sake dm:install:more:merb_datamapper # Install merb_datamapper +sake dm:sake:refresh # Remove and reinstall DataMapper sake recipes +sake dm:update # Update your local DataMapper. Run from inside the top-level dm dir +{% endhighlight %} + +### Step 3 - Live a little + +Change directories into the `src` directory and run `sake dm:clone`. You'll see +git cloning DataMapper Core, DataMapper More, and DataObjects from their +respective repositories on GitHub. When that's done, `cd dm` and have a look +around. + +When your ready, return to `~/src/dm` and issue `sake dm:install`. + +### All Together Now + +When executed together, these 3 steps amount to 7 lines at the command line. +Talk about stunningly easy. + +{% highlight bash linenos %} +mkdir -p ~/src +cd ~/src +sudo gem install sake +sake -i http://github.com/dkubb/dm-dev/raw/master/dm-dev.sake +sake dm:clone +cd dm +sake dm:install +{% endhighlight %} + +Changes happen to DataMapper and it's buddies all the time. To refresh your +installation of DataMapper and DataObjects, return to `~/src/dm` and issue: + +{% highlight bash linenos %} +sake dm:gems:refresh +{% endhighlight %} + +It will uninstall your local gems, pull down fresh changes from github, and +reinstall the gems again. + +On a side note, checkout for the original +merb related sake tasks by Michael Ivey wrote that these came from. diff --git a/content/articles/datamapper_090_released.txt b/_posts/2008-05-27-datamapper_090_released.markdown similarity index 72% rename from content/articles/datamapper_090_released.txt rename to _posts/2008-05-27-datamapper_090_released.markdown index 963e131..78c10e5 100644 --- a/content/articles/datamapper_090_released.txt +++ b/_posts/2008-05-27-datamapper_090_released.markdown @@ -1,29 +1,31 @@ --- -body_id: news +layout: articles +categories: [important, articles] title: DataMapper 0.9 is Released created_at: 2008-05-27T03:07:24-05:00 summary: And you thought that The Great Refactoring was for naught -release_type: important author: ssmoot, afrench -filter: - - erb - - textile --- -h1. <%= @page.title %> +{{ page.title }} +================ - -DataMapper 0.9 is ready for the world. It brings with it a massive overhaul of the internals of DataMapper, a shift in terminology, a dramatic bump in speed, improved code-base organization, and support for more than just database data-stores. +DataMapper 0.9 is ready for the world. It brings with it a massive overhaul of +the internals of DataMapper, a shift in terminology, a dramatic bump in speed, +improved code-base organization, and support for more than just database +data-stores. To install it: -<% coderay(:lang => "bash") do -%> +{% highlight bash %} sudo gem install addressable english rspec sudo gem install data_objects do_mysql do_postgres do_sqlite3 sudo gem install dm-core dm-more -<% end %> +{% endhighlight %} -This is NOT a backwards compatible release. Code written for DataMapper 0.3 will not function with DataMapper 0.9.* due to syntactical changes and library improvements. +This is NOT a backwards compatible release. Code written for DataMapper 0.3 will +not function with DataMapper 0.9.* due to syntactical changes and library +improvements. REPEAT: This is NOT a backwards compatible release. @@ -37,22 +39,22 @@ REPEAT: This is NOT a backwards compatible release. Creating a class -<% coderay(:lang => "ruby") do -%> +{% highlight ruby %} class Post < DataMapper::Base end -<% end %> +{% endhighlight %} -<% coderay(:lang => "ruby") do -%> +{% highlight ruby %} class Post include DataMapper::Resource -end<% end %> +end{% endhighlight %} Keys -<% coderay(:lang => "ruby") do -%> +{% highlight ruby %} # Key was not mandatory # Automatically added +id+ if missing # @@ -62,10 +64,10 @@ property :name, :string, :key => true # Composite Key property :id, :integer, :key => true property :slug, :string, :key => true -<% end %> +{% endhighlight %} -<% coderay(:lang => "ruby") do -%> +{% highlight ruby %} # keys are now mandatory property :id, Serial # @@ -75,95 +77,95 @@ property :slug, String, :key => true # Composite Key property :id, Integer, :key => true property :slug, String, :key => true -<% end %> +{% endhighlight %} Properties -<% coderay(:lang => "ruby") do -%> +{% highlight ruby %} property :title, :string property :body, :text property :posted_on, :datetime property :active, :boolean -<% end %> +{% endhighlight %} -<% coderay(:lang => "ruby") do -%> +{% highlight ruby %} property :title, String property :body, Text property :posted_on, DateTime property :active, Boolean -<% end %> +{% endhighlight %} Associations -<% coderay(:lang => "ruby") do -%> +{% highlight ruby %} has_many :comments belongs_to :blog has_and_belongs_to_many :categories has_one :author -<% end %> +{% endhighlight %} -<% coderay(:lang => "ruby") do -%> +{% highlight ruby %} has n, :comments belongs_to :blog has n, :categories => Resource has 1, :author -<% end %> +{% endhighlight %} Finders - <% coderay(:lang => "ruby") do -%> + {% highlight ruby %} Post.first :order => 'created_at DESC' Post.all :conditions => [ 'active = ?', true ] database.query 'SELECT 1' database.execute 'UPDATE posts...' - <% end %> + {% endhighlight %} - <% coderay(:lang => "ruby") do -%> + {% highlight ruby %} Post.first :order => [ :created_at.desc ] Post.all :conditions => [ 'active = ?', true ] repository(:default).adapter.query 'SELECT 1' repository(:default).adapter.execute 'UPDATE posts...' - <% end %> + {% endhighlight %} Validations - <% coderay(:lang => "ruby") do -%> + {% highlight ruby %} validates_presence_of :title validates_numericality_of :rating validates_format_of :email, :with => :email_address validates_length_of :summary, :within => (1..100) validates_uniqueness_of :slug - <% end %> + {% endhighlight %} - <% coderay(:lang => "ruby") do -%> + {% highlight ruby %} validates_present :title validates_is_number :rating validates_format :email, :as => :email_address validates_length :summary, :in => (1..100) validates_is_unique :slug - <% end %> + {% endhighlight %} Callbacks -<% coderay(:lang => "ruby") do -%> +{% highlight ruby %} before_save :categorize before_create do |post| @@ -171,10 +173,10 @@ before_create do |post| end # return false to abort -<% end %> +{% endhighlight %} -<% coderay(:lang => "ruby") do -%> +{% highlight ruby %} before :save, :categorize before :create do @@ -182,7 +184,7 @@ before :create do end # throw :halt to abort -<% end %> +{% endhighlight %} diff --git a/content/articles/datamapper_presented_at_railsconf08.txt b/_posts/2008-06-10-datamapper_presented_at_railsconf08.markdown similarity index 71% rename from content/articles/datamapper_presented_at_railsconf08.txt rename to _posts/2008-06-10-datamapper_presented_at_railsconf08.markdown index f664495..bb775a4 100644 --- a/content/articles/datamapper_presented_at_railsconf08.txt +++ b/_posts/2008-06-10-datamapper_presented_at_railsconf08.markdown @@ -1,21 +1,24 @@ --- -body_id: news +layout: articles +categories: articles title: DataMapper Presented at RailsConf 2008 created_at: 2008-06-10T12:30:16-05:00 summary: A warm reception for a very cool ORM -release_type: blog author: afrench -filter: - - erb - - textile --- -h1. <%= @page.title %> +{{ page.title }} +================ -Yehuda Katz (wycats) gave an updated version of his "DataMapper - The Persistence Framework" presentation at Rails Conf 2008. Though video isn't available for the talk, he was nice enough to share his slides. +Yehuda Katz (wycats) gave an updated version of his "DataMapper - The +Persistence Framework" presentation at Rails Conf 2008. Though video isn't +available for the talk, he was nice enough to share his slides. -They're available for download and review direct from SlideShare at "http://www.slideshare.net/wycats/datamapper/":http://www.slideshare.net/wycats/datamapper/ +They're available for download and review direct from SlideShare at +. -Adam French (afrench) was in attendance and participated in the 'Birds Of A Feather' discussion panel on "Rails Alternatives and You":http://en.oreilly.com/rails2008/public/schedule/detail/4426 led by Mark Bates, of "www.mackframework.com":http://www.mackframework.com fame. +Adam French (afrench) was in attendance and participated in the 'Birds Of A +Feather' discussion panel on [Rails Alternatives and You](http://en.oreilly.com/rails2008/public/schedule/detail/4426) led by Mark +Bates, of [www.mackframework.com](http://www.mackframework.com) fame. diff --git a/_posts/2009-09-15-datamapper_010_released.markdown b/_posts/2009-09-15-datamapper_010_released.markdown new file mode 100644 index 0000000..6ec0195 --- /dev/null +++ b/_posts/2009-09-15-datamapper_010_released.markdown @@ -0,0 +1,14 @@ +--- +layout: articles +categories: [important, articles] +title: DataMapper 0.10 is Released +created_at: 2009-09-15 08:31:59.869354 -07:00 +summary: After 11 months of work, over 1250 commits and 140 tickets fixed 0.10 is ready +author: dkubb +--- + +{{ page.title }} +================ + +DataMapper 0.10 is ready for release. We've worked on it for the past 11 months, +pushed 1250 commits, written 3000+ specs, and fixed 140 tickets in the process. diff --git a/content/community.txt b/community.markdown similarity index 77% rename from content/community.txt rename to community.markdown index eca1b86..9c329e4 100644 --- a/content/community.txt +++ b/community.markdown @@ -1,15 +1,16 @@ --- +layout: default title: DataMapper's Community created_at: Sun Mar 16 23:55:11 -0500 2008 -filter: - - erb - - textile --- -h1. <%= @page.title %> + +{{page.title}} +============== Like all open-source projects, DataMapper has a burgeoning community. -h2. Links, Blogs, Wiki's, and Stuff +Links, Blogs, Wiki's, and Stuff +-------------------------------
News and Notes
@@ -37,7 +38,8 @@ h2. Links, Blogs, Wiki's, and Stuff
Official Twitter Feed
-h2. The People Behind DataMapper +The People Behind DataMapper +----------------------------
  • @@ -104,48 +106,48 @@ h2. The People Behind DataMapper

    Robert Evans

  • -
  • - !http://www.gravatar.com/avatar/e7cff3cfd41c495e1012227d7dc24202(Luis Lavana)! +
  • + ![Luis Lavena](http://www.gravatar.com/avatar/e7cff3cfd41c495e1012227d7dc24202 "Luis Lavena")

    Luis Lavana

  • -
  • - !http://en.gravatar.com/avatar/c6e7bc52e950b434362d337bcfa01993(Bernerd Schaefer)! +
  • + ![Bernerd Schaefer](http://en.gravatar.com/avatar/c6e7bc52e950b434362d337bcfa01993 "Bernerd Schaefer")

    Bernerd Schaefer

  • -
  • - !/images/people/alex_coles.jpg(Alex Coles)! +
  • + ![Alex Coles](/images/people/alex_coles.jpg "Alex Coles")

    Alex Coles

  • -
  • - !http://en.gravatar.com/avatar/7f75c16b039cd5946fdee1e0771e4f09?s=80&r=any(Sindre Aarsaether)! +
  • + ![Sindre Aarsaether](http://en.gravatar.com/avatar/7f75c16b039cd5946fdee1e0771e4f09?s=80&r=any "Sindre Aarsaether")

    Sindre Aarsaether

  • -
  • - !http://en.gravatar.com/avatar/cc371b26b5881c44cde17f7885ccd608?s=80&r=any(Tony Pitale)! +
  • + ![Tony Pitale](http://en.gravatar.com/avatar/cc371b26b5881c44cde17f7885ccd608?s=80&r=any "Tony Pitale")

    Tony Pitale

  • -
  • - !http://en.gravatar.com/avatar/476d2a42689da3bd46375955fda053df?s=80&r=any(Jonathan Stott)! +
  • + ![Jonathan Stott](http://en.gravatar.com/avatar/476d2a42689da3bd46375955fda053df?s=80&r=any "Jonathan Stott")

    Jonathan Stott

  • -
  • - !http://railsontherun.com/matt_aimonetti.jpg(Matt Aimonetti)! +
  • + ![Matt Aimonetti](http://railsontherun.com/matt_aimonetti.jpg "Matt Aimonetti")

    Matt Aimonetti

  • -
  • - !http://en.gravatar.com/userimage/3495582/149a1aaf5921e34fbcb1ba6a975ed2d4.jpeg(Evan Light)! +
  • + ![Evan Light](http://en.gravatar.com/userimage/3495582/149a1aaf5921e34fbcb1ba6a975ed2d4.jpeg "Evan Light")

    Evan Light

  • -
  • - !http://img.skitch.com/20080901-m66fgxbr395p92q9bati3ijjf9.jpg(Carl Lereche)! +
  • + ![Carl Lereche](http://img.skitch.com/20080901-m66fgxbr395p92q9bati3ijjf9.jpg "Carl Lereche")

    Carl Lereche

  • -
  • - !http://www.gravatar.com/avatar/5e518814b76962fdd1ad0e74dfac5ea7(Martin Gamsjaeger)! +
  • + ![Martin Gamsjaeger](http://www.gravatar.com/avatar/5e518814b76962fdd1ad0e74dfac5ea7 "Martin Gamsjaeger")

    Martin Gamsjaeger

  • -
  • - !http://farm4.static.flickr.com/3119/2818724949_c9eaaf3ae3_o.jpg(Bryan Ray)! +
  • + ![Bryan Ray](http://farm4.static.flickr.com/3119/2818724949_c9eaaf3ae3_o.jpg "Bryan Ray")

    Bryan Ray

  • diff --git a/content/.htaccess b/content/.htaccess deleted file mode 100644 index 6725203..0000000 --- a/content/.htaccess +++ /dev/null @@ -1 +0,0 @@ -Redirect 302 /dm-dev.sake http://github.com/dkubb/dm-dev/raw/master/dm-dev.sake \ No newline at end of file diff --git a/content/404.html b/content/404.html deleted file mode 100644 index 7ea6cc0..0000000 --- a/content/404.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - DataMapper - File Not Found - - - - - - - -
    - - -

    Sorry, that file could not be found

    -

    We recently switched hosts, so some older links no longer exist.

    -

    Try the menu above or see the wiki.

    - - -
    - - - \ No newline at end of file diff --git a/content/articles/datamapper_010_released.txt b/content/articles/datamapper_010_released.txt deleted file mode 100644 index d917558..0000000 --- a/content/articles/datamapper_010_released.txt +++ /dev/null @@ -1,15 +0,0 @@ ---- -body_id: news -title: DataMapper 0.10 is Released -created_at: 2009-09-15 08:31:59.869354 -07:00 -summary: After 11 months of work, over 1250 commits and 140 tickets fixed 0.10 is ready -release_type: important -author: dkubb -filter: - - erb - - textile ---- - -h1. <%= @page.title %> - -DataMapper 0.10 is ready for release. We've worked on it for the past 11 months, pushed 1250 commits, written 3000+ specs, and fixed 140 tickets in the process. diff --git a/content/articles/datamapper_talk_atmwrc08.txt b/content/articles/datamapper_talk_atmwrc08.txt deleted file mode 100644 index daa5e28..0000000 --- a/content/articles/datamapper_talk_atmwrc08.txt +++ /dev/null @@ -1,19 +0,0 @@ ---- -body_id: news -title: DataMapper Presented at MWRC 2008 -created_at: 2008-04-10T23:07:39-05:00 -summary: Presentation on DataMapper by Wycats -release_type: blog -author: afrench -filter: - - erb - - textile ---- - -h1. <%= @page.title %> - -!/images/wykatz_at_mwrc2008.png(Yehuda Katz' presentation entitled 'Faster, Better ORM With DataMapper)! - -At this year's "Mountain West Ruby Conference 2008":http://mtnwestrubyconf.org/, DataMapper's own Yehuda Katz gave a presentation entitled "Faster, Better ORM With DataMapper." - -The video of the talk is available for viewing and downloading at "http://mwrc2008.confreaks.com/04katz.html":http://mwrc2008.confreaks.com/04katz.html and is brought to you by the good people at "confreaks.com":http://confreaks.com diff --git a/content/articles/spotlight_on_cpk.txt b/content/articles/spotlight_on_cpk.txt deleted file mode 100644 index 9803009..0000000 --- a/content/articles/spotlight_on_cpk.txt +++ /dev/null @@ -1,75 +0,0 @@ ---- -body_id: news -title: Spotlight on... Composite Keys -created_at: 2008-03-29T19:09:07-05:00 -summary: When a Primary Key Just Isn't Enough -release_type: blog -author: afrench and ssmoot -filter: - - erb - - textile ---- - -h1. <%= @page.title %> - -For those of us who have taken a course on database design in college or university, you may have run across a concept called 'Composite Primary Keys' (or sometimes 'Compound Keys' or 'Concatenated Keys', and abbreviated CPK(Composite Primary Keys)s). It's usually right before you tackle JOINs and right after you fight with the "surrogate key" or "primary key" concept. - -Boiling CPK(Composite Primary Keys)s down, they're just a way of identifying a row by multiple keys rather than one. So instead of an auto_incrementing "serial" primary key (as in @id@), you'd have a combination of @some_column@ and @some_other_column@ that would uniquely identify a row. - -CPK(Composite Primary Keys)s aren't as prevalent in the Rails world as Serial Keys (such as the auto-incrementing @:id@ column), but if you're going to support legacy, integration or reporting databases or just de-normalized schemas for performance reasons, they can be invaluable. So sure, Surrogate Keys are a great convenience, but sometimes they just aren't an option. - -Let's briefly take a look at how a few ruby ORMs support Composite Primary Keys and then we'll talk about DataMapper's support for CPK(Composite Primary Keys)s. - -h2. ActiveRecord - -In short, ActiveRecord doesn't support CPK(Composite Primary Keys)s without the help of an external library. "Dr. Nic Williams":http://drnicwilliams.com/about/ "Composite Keys":http://compositekeys.rubyforge.org/ is an effort to overcome this limitation. - -h2. Sequel - -Unlike ActiveRecord, Sequel supports CPK(Composite Primary Keys)s natively: - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> -class Post < Sequel::Model - set_primary_key [ :category, :title ] -end - -post = Post.get('ruby', 'hello world') -post.key # => [ 'ruby', 'hello world' ] -<% end %> - -p(attribution). example compiled from "http://code.google.com/p/ruby-sequel/wiki/SequelModels":http://code.google.com/p/ruby-sequel/wiki/SequelModels - -h2. DataMapper - -The latest DataMapper was designed from the ground up to support CPK(Composite Primary Keys)s: - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> -class Pig - include DataMapper::Resource - - property :id, Integer, :key => true - property :slug, String, :key => true - property :name, String -end - -pig = Pig.get(1, 'Porky') - -pig.key # => [ 1, 'Wilbur' ] -<% end %> - -We declared our keys by adding the @:key => true@ to the appropriate properties. The order is important as it will determine the order keys are addressed throughout the system. - -Next, we mixed and matched the keys' types. @:id@ is a Integer, but @:slug@ is a String. DataMapper didn't flinch when we defined a key column as a String because it supports "Natural Keys":http://en.wikipedia.org/wiki/Natural_key as well. - -Lastly, when retrieving rows via @get@ and @[]@ with a CPK(Composite Primary Keys), we supplied the keys in the order they were defined within our model. For example, we defined @:id@ first, then @:slug@ second; later, we retrieved Porky by specifying his @:id@ and @:slug@ in the same order. Additionally, when we asked Wilbur for his keys, he handed us an array in the order the keys were defined. - -We didn't need to mix in an external library to get support for CPK(Composite Primary Keys)s, nor did we need to call a @set_primary_key@ method and then supply more than one key to it. DataMapper supports Composite Primary Keys intuitively and without compromise! - -In later "Spotlight On..." articles, we'll examine and demonstrate other DataMapper features or persistence concepts as well as compare similar features with other ORMs or libraries. - -h2(newRelease). Contribute a "Spotlight On..." Article - -p(newRelease). Got something important to say? Want something explained a little
    -better or demonstrated? Contribute or request a "Spotlight On..."
    article! -Email the "DataMapper Mailing List":http://groups.google.com/group/datamapper with the request or
    -contribution and we'll post it here. diff --git a/content/articles/spotlight_on_laziness.txt b/content/articles/spotlight_on_laziness.txt deleted file mode 100644 index 6b5a9a9..0000000 --- a/content/articles/spotlight_on_laziness.txt +++ /dev/null @@ -1,138 +0,0 @@ ---- -body_id: news -title: Spotlight on... Laziness -created_at: 2008-04-03T23:08:12-05:00 -summary: The Fastest Code Is No Code -release_type: blog -author: afrench -filter: - - erb - - textile ---- - -h1. <%= @page.title %> - -Laziness. It means "an unwillingness to work or use energy" and typically indicates that the dishes don't get washed after lunch, the bath tub doesn't get cleaned, and the trash sits around an extra few days and stinks up the place. - -But that very same definition in software takes on a whole new meaning: To avoid doing work you don't have to do for as long as you can avoid it; sometimes never doing it at all. It's a good thing. It means that expensive and slow tasks can be put off until the very last cycle possible and thus only incur their cost when it really is worth it. Maybe you never execute the code at all. - -You could put off running a specific subroutine because it's slow, or because it locks a file that might be needed elsewhere, or because instantiating the resulting object eats up RAM. Either way, deferring execution of a block of code until the very last possible moment can be the difference between a snappy application that rarely slows down and a slow application that rarely speeds up. - -But laziness isn't without its hidden costs. If you put off everything to the very last moment, you forfeit the opportunity to do more than one thing at a time, and likely create more work for yourself, rather than less. - -So where's the balance? - -Ultimately, it depends on your application. The tools you use should offer you the flexibility you need to design your application optimally. Every system, after all, is unique and breaks the mold of systems before it. - -This brings us to DataMapper. - -h2. Lazy-loading attributes - -You likely already know that DataMapper supports lazy properties. - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> -class Post - include DataMapper::Resource - - property :id, Serial # auto_incrementing primary key - property :title, String, :lazy => true # intentionally lazy - property :body, Text # lazy by default -end -<% end %> - -In this case, we're intentionally marking this Post's @:title@ property as lazy, as well as letting the @:body@ be lazy by default. If we go and inspect our query log for the retrieval of a post with the ID of 1, we see - -<% coderay(:lang => "sql") do -%> -SELECT `id` FROM `posts` WHERE `id` = 1 -<% end %> - -DataMapper didn't request the two lazy columns. But when we call @.title@ off of our post, we suddenly see - -<% coderay(:lang => "sql") do -%> -SELECT `title` FROM `posts` WHERE `id` = 1 -<% end %> - -This is the very definition of a lazy-loaded property; The lazy column didn't get requested from our data store until we actually needed it, and no sooner. - -But this is just for one individual instance of a post. How does this behave when we have a collection of posts and iteratively call the @.title@ method? - -<% coderay(:lang => "sql") do -%> -SELECT `title` FROM `posts` WHERE `id` IN (1, 2, 3, 4, 5) -<% end %> - -DataMapper loaded up the title for all of the posts in our collection in one query. It didn't issue the lazy-load retrieval from above over and over for each individual post, nor did it chicken out and issue the lazy-load retrieval for ALL of the posts in the data store. - -When you retrieve a set of results using DataMapper's @.all@, each instance it returns knows about the others in the result set, which makes it brutally simple to issue just one lazy-load retrieval of @:title@, and thus solving the n+1 query problem without having to do anything special in the initial retrieval. - -h2. Contextual Lazy-loading - -With a recent commit by "Guy van den Berg":http://www.guyvdb.info/ruby/lazy-loading-properties-in-datamapper/, DataMapper just got a whole lot more flexible. - -Most applications have only a few main views of a resource: a brief summary view used in listing results, a complete representation that might appear on a show page and a comprehensive view for when someone is editing something and needs access to metadata. Wouldn't it be nice to lump all of the lazy-load retrieval queries into one query which loads up multiple lazy properties, rather than query after query for each lazy property as you call them? - -DataMapper now does this! - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> -class Post - include DataMapper::Resource - - property :id, Serial - property :title, String, :lazy => [ :summary, :brief ] - property :body, Text, :lazy => [ :summary ] -end -<% end %> - -So now, when you load an attribute with the @:summary@ context, DataMapper will load up all of the other lazy-loaded properties marked @:summary@ in one query to the data store. - -In your query log, you'll see: - -<% coderay(:lang => "sql", :line_numbers => "inline") do -%> --- initial load -SELECT `id` FROM `posts` - --- lazy-loading of multiple properties in a given context in one query -SELECT `id`, `title`, `body` FROM `posts` -<% end %> - -If you use this wisely, it would mean that DataMapper will never load more than it needs nor will it ever fire off more than the absolutely necessary amount of queries to get the job done. - -It's lazy ;-) - -h2. Strategic Eager Loading - -Well, not for everything. - -Returning for a little bit to our "loaded set" discussion from above, every item you pull out of the data store is aware of any other item that got pulled along with it. This is a very powerful feature which lets DataMapper defeat n+1 query problems when dealing with associations as well as lazy-loading of properties. - -For example, this is a severe "no no" in ActiveRecord: - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> - Zoo.find(:all).each do |zoo| - zoo.animals - end -<% end %> - -This is a very bad idea because the ORM must query the "animals" table over and over again to load the association for each iteration. It's far better to use @Zoo.find(:all, :include => [ :animals ]).each {}@ because a JOIN occurs and everything is retrieved in 1 query. - -But the same issue doesn't exist in DataMapper. Each instance is aware of the other instances it was retrieved with. The same iterator example from above only fires off 2 queries as you're iterating and calling the association inside the @each@. If you forget to @:include => [ :association ]@ in the initial query, DataMapper only ever fires off one more query to get what it needs. - -"Yehuda Katz":http://www.yehudakatz.com/ has aptly named this 'Strategic Eager Loading'. - -h2. Getting Around to It - -A conclusion for our talk about laziness will be written whenever I get around to it. - -For now, just remember that DataMapper embraces lazy-loading, yet isn't overly zealous when the lazy properties are finally retrieved. It also fills associations strategically, and assumes you're going to iterate over the set of results. You don't have to catch yourself when you write an iterator because DataMapper loads associations for all of your items in the set, rather than on a one-by-one basis. - -And, most importantly, you can avoid doing work you don't have to do for as long as you can avoid it. - -
    - -h2. Contribute a "Spotlight On..." Article - -Got something important to say? Want something explained a little
    -better or demonstrated? Contribute or request a "Spotlight On..."
    article! -Email the "DataMapper Mailing List":http://groups.google.com/group/datamapper with the request or
    -contribution and we'll post it here. - -
    diff --git a/content/articles/stunningly_easy_way_to_live_on_the_edge.txt b/content/articles/stunningly_easy_way_to_live_on_the_edge.txt deleted file mode 100644 index a785360..0000000 --- a/content/articles/stunningly_easy_way_to_live_on_the_edge.txt +++ /dev/null @@ -1,121 +0,0 @@ ---- -body_id: news -title: The Stunningly Easy Way to Live On The Edge Of DataMapper -created_at: 2008-05-07T19:04:34-05:00 -summary: A little sake goes a long way -release_type: blog -author: afrench -filter: - - erb - - textile ---- - -h1. <%= @page.title %> - -DataMapper is organized into sub-projects, much like "Merb":http://www.merbivore.com, and that tends to confuse even the people working on it....until recently. Michael Ivey, an active contributer to the Merb project, and our very own Dan Kubb have collaborated on a set of Sake tasks to help automate and streamline checking out, packaging, installing, uninstalling, updating, repackaging, and reinstalling the DataMapper and Merb projects. - -If you like to live life on the edge, this is the happiest way to do it. - -h3. Step 0 - The Setup - -A couple of very basic requirements before we begin. First, you'll need to have an up-to-date installation of "Rubygems":http://www.rubygems.org/, the Ruby package management system. To check what version you have do: - -<% coderay(:lang => "bash", :line_numbers => "inline") do -%> -gem --version -<% end %> - -If you aren't on 1.2.x, update by running - -<% coderay(:lang => "bash", :line_numbers => "inline") do -%> -sudo gem update --system -<% end %> - -Next, you'll need @git@. It's the source code management tool DataMapper uses. Its installation is left up to the reader, but here's a few good resources to go to for help: - -* "Git - Fast Version Control System":http://git.or.cz/ - Homepage -* "Installing GIT on MAC OSX 10.5 Leopard":http://dysinger.net/2007/12/30/installing-git-on-mac-os-x-105-leopard/ -* "Git On Windows":http://ropiku.wordpress.com/2007/12/28/git-on-windows/ -* "Installing Git on Ubuntu":http://chrisolsen.org/2008/03/10/installing-git-on-ubuntu/ - -After that, you'll need to @gem uninstall@ any of the "dm-*" projects you already have installed. This includes 'data_objects' and its associated adapters. - -Next, you'll need a few of the base dependencies. To install them, run - -<% coderay(:lang => "bash") do -%> -sudo gem install addressable english rspec -<% end %> - -Once that's done, do the following: - -<% coderay(:lang => "bash", :line_numbers => "inline") do -%> -mkdir -p ~/src -cd ~/src -<% end %> - -h3. Step 1 - Have Some Sake - -No, not the wonderful alcoholic beverage, the "system-wide rake tasks library":http://errtheblog.com/posts/60-sake-bomb by "PJ Hyett and Chris Wanstrath of Err. The Blog":http://errtheblog.com/. Ivey's and dkubb's automated installation and reinstallation scripts are written as sake tasks, so you'll need it installed on your machine. - -<% coderay(:lang => "bash", :line_numbers => "inline") do -%> -sudo gem install sake -<% end %> - -Once you're done, you should be able to see sake in your path by executing @which sake@ and see where @gem@ installed it. - -h3. Step 2 - Install the Tasks - -Now that you're all setup with sake and the @src@ directory, it's time to install the sake tasks. They can be found at "http://github.com/dkubb/dm-dev/":http://github.com/dkubb/dm-dev/ and are very easily installed by doing: - -<% coderay(:lang => "bash", :line_numbers => "inline") do -%> -sake -i http://github.com/dkubb/dm-dev/raw/master/dm-dev.sake -<% end %> - -The tasks that get installed are available for perusal by issuing @sake -T@ - -<% coderay(:line_numbers => nil) do -%> -$ sake -T -sake dm:clone # Clone a copy of the DataMapper repository and dependencies -sake dm:gems:refresh # Pull fresh copies of DataMapper and refresh all the gems -sake dm:gems:wipe # Uninstall all RubyGems related to DataMapper -sake dm:install # Install dm-core, dm-more and do -sake dm:install:core # Install dm-core -sake dm:install:do # Install do drivers -sake dm:install:do:data_objects # Install data_objects -sake dm:install:do:mysql # Install do_mysql -sake dm:install:do:postgres # Install do_postgres -sake dm:install:do:sqlite3 # Install do_sqlite3 -sake dm:install:more # Install dm-more -sake dm:install:more:merb_datamapper # Install merb_datamapper -sake dm:sake:refresh # Remove and reinstall DataMapper sake recipes -sake dm:update # Update your local DataMapper. Run from inside the top-level dm dir -<% end %> - -h3. Step 3 - Live a little - -Change directories into the @src@ directory and run @sake dm:clone@. You'll see git cloning DataMapper Core, DataMapper More, and DataObjects from their respective repositories on GitHub. When that's done, @cd dm@ and have a look around. - -When your ready, return to @~/src/dm@ and issue @sake dm:install@. - -h3. All Together Now - -When executed together, these 3 steps amount to 7 lines at the command line. Talk about stunningly easy. - -<% coderay(:lang => "bash", :line_numbers => "inline") do -%> -mkdir -p ~/src -cd ~/src -sudo gem install sake -sake -i http://github.com/dkubb/dm-dev/raw/master/dm-dev.sake -sake dm:clone -cd dm -sake dm:install -<% end %> - -Changes happen to DataMapper and it's buddies all the time. To refresh your installation of DataMapper and DataObjects, return to @~/src/dm@ and issue: - -<% coderay(:lang => "bash", :line_numbers => "inline") do -%> -sake dm:gems:refresh -<% end %> - -It will uninstall your local gems, pull down fresh changes from github, and reinstall the gems again. - -On a side note, checkout "http://merbivore.com/merb-dev.sake":http://merbivore.com/merb-dev.sake for the original merb related sake tasks by Michael Ivey wrote that these came from. diff --git a/content/articles/the_great_refactoring.txt b/content/articles/the_great_refactoring.txt deleted file mode 100644 index 823b4c3..0000000 --- a/content/articles/the_great_refactoring.txt +++ /dev/null @@ -1,72 +0,0 @@ ---- -body_id: news -title: The Great Refactoring -created_at: 2008-03-22T15:55:50-05:00 -summary: Change is afoot -release_type: blog -author: afrench and ssmoot -filter: - - erb - - textile ---- - -h1. <%= @page.title %> - -"Tip" DataMapper (hosted on "github":http://github.com/datamapper/dm-core) is going through a dramatic re-factor. Here's a quick summary of the anticipated NEW public API. - -h2. Not Just for Databases Anymore - -DataMapper's class terminology will change and 'de-couple' itself from database-specific terminology. Gone are "database", "table", "column" and "join". Say hello to "Repository", "Resource", "Property", and "Link". - -"Why would you want to do that?", you ask. Ultimately it's because DataMapper will soon support different types of persistence layers, not just databases. It'll talk to all sorts of things like web services (REST and such), XML files, YAML files, non-relational databases, even custom file-types or services of your own design. Just implement an Adapter that conforms to a certain API and DataMapper could support any type of data store. No need for a completely separate library or anything. - -As an added benefit, DataMapper become more "RESTful". "Ryan Tomayko":http://tomayko.com/writings/rest-to-my-wife has a very good explanation of REST that all should read entitled "How I Explained REST to My Wife":http://tomayko.com/writings/rest-to-my-wife. - -h2. Model Definitions Are a Little Different - -Since we're changing up the terminology, model definitions are going to change up a little bit. Here's what a Planet model would look like using the new API: - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> -class Planet - include DataMapper::Resource - - resource_names[:legacy] = 'dying_planets' - - property :name, String - property :age, Integer - property :core, String, :private => true -end -<% end %> - -A couple of things are going on here. First, DataMapper::Base and DataMapper::Persistence are gone and replaced with <%= doc('DataMapper::Resource') %>. Next @set_table_name@ has been replaced with @resource_names@ hash where you specify which arena play occurs in. After that we have a couple of Property definitions that look a little different. - -First off, Properties will no longer take @:symbols@ for their types and instead take real constants like String, Integer, DateTime. Also on the docket are the ability to define your own custom types. - -Think about that for a minute. If developers are able to define their own custom types with their own materialization and serialization methods, DataMapper will be able to support all kinds of wild data-types like GIS information, network information, marshaled objects, JSON...pretty much anything a developer might need, or want. - -h2. A Command-Line Interface - -Taking a lesson from web frameworks, DataMapper will sport an interactive command-line so that you can browse your resources without the need to load up the entire environment of your application. Here's an example session: - -<% coderay(:lang => "bash", :line_numbers => "inline") do -%> -$ dm mysql://root@localhost/great_musicians # connecting to a repository -<% end %> - -An IRB session boots up... - -<% coderay(:lang => "ruby") do -%> ->> the_king = Person.first(:name => 'elvis') ->> the_king.alive? # => maybe -<% end %> - -This is very similar to @script/console@ in Rails or @merb -i@ in Merb, only it won't load up the entire environment of your application, just your DataMapper resources and their associations, methods, and such. If you prefer "fat models", this will constitute the core of your application. - -h2. How This All Comes Together - -This is the coolest new feature of DataMapper: we're skipping all the way from 0.3.0 to 0.9! Get excited, contact the press, fire up the blogosphere! Its a huge jump and we're honestly concerned that people may not be able to handle it. - -Alright, so it's not _that_ big of a deal, but we're confident that all of this will get DataMapper so close to going 1.0 that we'll be able to taste it. To get there, DataMapper's more advanced features like single table inheritance, paranoia, and chained associations will be re-implimented to use all this new stuff, and then we're sure 0.9 will need a touch up or two. - -So close....so very very close... - -Stay tuned in to the "mailing list":http://groups.google.com/group/datamapper, check up on the "wiki":http://datamapper.org/, chat it up in "#datamapper":irc://irc.freenode.net/#datamapper and watch "github commit messages":http://github.com/datamapper/dm-core/commits/master for updates. diff --git a/content/contribute.txt b/content/contribute.txt deleted file mode 100644 index 2a0dfb1..0000000 --- a/content/contribute.txt +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: Contribute to DataMapper -created_at: Wed Aug 29 20:37:00 +0930 2007 -filter: - - erb - - textile ---- -h1. <%= @page.title %> - -DataMapper is always looking for more contributers. When you've got an itch to scratch, jump in and contribute! Write a few specs showing us how your code works, create a patch and "submit it":http://datamapper.lighthouseapp.com/projects/20609-datamapper/ as a new ticket or a fix for an existing one. After a few patches and many thanks, you'll get commit access. - -We benchmark all but the most trivial of patches, because we care about performance and you should too! - -h2. What we need - -Currently DataMapper needs help in a few particular areas: - -* API Documentation (using the "YARD documentation style":http://github.com/lsegal/yard/) -* Tutorials -* Code contributions -* Bug Reports - -h2. Git - Edge DataMapper - -DataMapper development uses "Git":http://git.or.cz SCM. Please see "using git":using-git.html to learn how to contribute. - -
    -
    $ git clone git://github.com/datamapper/dm-core.git
    -
    - - -h2. Code Style Guidelines - -When contributing any code to DataMapper, please follow these guidelines. - -# Spec first. Spec thoroughly. (DataMapper is written with "Rspec":http://rspec.info/) -# Parentheses around parameter lists for methods -# Two space indent - not tabs! -# Documentation is required (use the "YARD documentation style":http://github.com/lsegal/yard/) diff --git a/content/development/index.txt b/content/development/index.txt deleted file mode 100644 index e370d71..0000000 --- a/content/development/index.txt +++ /dev/null @@ -1,32 +0,0 @@ ---- -title: Development -created_at: Mon Mar 17 01:37:12 -0500 2008 -filter: - - erb - - textile ---- - - -h1. Development - -DataMapper development has switched to the "Git":http://git.or.cz SCM. Please see "using git":/using-git.html to learn how to contribute. To check out "tip" DataMapper anonymously: - -<% coderay(:lang => "bash", :line_numbers => "inline") do -%> -git clone git://github.com/datamapper/extlib.git -git clone git://github.com/datamapper/dm-core.git -git clone git://github.com/datamapper/dm-more.git -<% end %> - -Or visit the "edge guide":/articles/stunningly_easy_way_to_live_on_the_edge.html. - -If you have a "github":http://www.github.com account, log in, and _fork_ "the repo":http://github.com/datamapper/dm-core/. When you think you're ready, send dkubb a "pull request". - -h2. Coding Conventions and Considerations - -When contributing any code to DataMapper, please follow these guidelines. - -# Spec first. Spec thoroughly. (DataMapper is written with "Rspec":http://rspec.info/) -# Parentheses around parameter lists for methods -# Two space indent - not tabs! -# Write optimal code, not magic code -# Documentation is required (use the "official documentation style":/docs/) diff --git a/content/docs/callbacks.txt b/content/docs/callbacks.txt deleted file mode 100644 index 222cc94..0000000 --- a/content/docs/callbacks.txt +++ /dev/null @@ -1,89 +0,0 @@ ---- -title: Hooks (AKA Callbacks) -body_id: docs -created_at: Fri Nov 30 15:29:01 +1030 2007 -filter: - - erb - - textile ---- - -h1. <%= @page.title %> - -DataMapper supports callbacks using an "aspect-oriented approach":http://en.wikipedia.org/wiki/Aspect_oriented. You can define callbacks for any method as well as any class method arbitrarily. - -h2. Adding Instance-Level Advice - -To declare advice (callback) for a specific method, first define a new method to be run when another is called, then define your point-cut. - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> -class Post - include DataMapper::Resource - - # ... key and properties here - - before :save, :categorize - - def categorize - # ... code here - end -end -<% end %> - -Alternatively, you can declare the advice during the point-cut by supplying a block rather than a symbol representing a method. - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> -class Post - include DataMapper::Resource - - # ... key and properties here - - before :save do - # ... code here - end - -end -<% end %> - -h2. Adding Class-Level Advice - -To install advice around a class method, use @before_class_method@ or @after_class_method@: - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> -class Post - include DataMapper::Resource - - # ... key and properties here - - before_class_method :find, :prepare - - def self.prepare - # ... code here - end -end -<% end %> - -Class level advice does not have access to any resulting instances from the class method, so they might not be the best fit for @after_create@ or @after_save@. - -h2. Throw :halt, in the name of love... - -In order to abort advice and prevent the advised method from being called, throw @:halt@ - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> -class Post - include DataMapper::Resource - - # ... key and properties here - - # This record will save properly - before :save do |post| - true - end - - # But it will not be destroyed - before :destroy do |post| - throw :halt - end -end -<% end %> - -Remember, if you @throw :halt@ inside an @after@ advice, the advised method will have already ran and returned. Because of this, the @after@ advice will be the only thing halted. diff --git a/content/docs/dm_more/dm-aggregates.txt b/content/docs/dm_more/dm-aggregates.txt deleted file mode 100644 index c856cf6..0000000 --- a/content/docs/dm_more/dm-aggregates.txt +++ /dev/null @@ -1,78 +0,0 @@ ---- -title: DM-Aggregates -created_at: 2008-09-06 14:11:54.160780 -05:00 -filter: - - erb - - textile ---- -h1. <%= @page.title %> - -DM-Aggregates provides a reporting API which offers aggregating functions like @count@, @min@, @max@, @avg@, @sum@, and @aggregate@. - -h3. Setup - -Simply @require 'dm-aggregates'@ somewhere before you connect to your data-store and you're ready to go. - -h3. Count - -You can issue a totaling query with DM-Aggregates in a couple of ways. First, you can build a totaling query off of your Resource: - -<% coderay(:lang => "ruby") do -%> - Dragon.count(:homes_destroyed.lte => 12) -<% end %> - -Secondly, you can call @count@ off of a pre-build query object. - -<% coderay(:lang => "ruby") do -%> - Dragon.all(:age.lte => 3).count -<% end %> - -Your query will become a totaling query without having to retrieve the objects and total on the retrieved array. These two approaches can be combined as well. - -<% coderay(:lang => "ruby") do -%> - Dragon.all(:homes_destroyed.lte => 12).count(:rating => 'amateur') -<% end %> - -h3. Min, Max - -Minimum and Maximum values from fields in your data-store can be retrieved by specifying which property you want to retrieve the value from. - -<% coderay(:lang => "ruby") do -%> - Dragon.min(:homes_destroyed) - Dragon.max(:homes_destroyed) -<% end %> - -Further conditions to your min and max queries are simply supplied after the property. - -<% coderay(:lang => "ruby") do -%> - Dragon.min(:homes_destroyed, :battles_won.gte => 20) - Dragon.max(:homes_destroyed, :title.not => nil, :nickname.like => "%puff%") -<% end %> - -h3. Sum, Average - -Both @sum@, and @avg@ work very similarly to @min@ and @max@. - -<% coderay(:lang => "ruby") do -%> - Dragon.sum(:toes_on_claw) - Dragon.sum(:toes_on_claw, :is_fire_breathing => true) - - Dragon.avg(:toes_on_claw) - Dragon.avg(:toes_on_claw, :is_fire_breathing => true) -<% end %> - -h3. Aggregate - -The @aggregate@ method will let you combine all of the other aggregating methods together to retrieve in one call to the data-store. DM-Aggregates adds in a few extra symbol operators in order to enable this. - -<% coderay(:lang => "ruby") do -%> - Dragon.aggregate(:all.count, :name.count, :toes_on_claw.min, - :toes_on_claw.max, :toes_on_claw.avg, :toes_on_claw.sum, - :is_fire_breathing) - # [ - # [ 1, 1, 3, 3, 3.0, 3, false ], - # [ 2, 1, 4, 5, 4.5, 9, true ] - # ] -<% end %> - -This will compile all of the aggregating queries together and return them in an array of arrays. diff --git a/content/docs/dm_more/index.txt b/content/docs/dm_more/index.txt deleted file mode 100644 index 5fa00b2..0000000 --- a/content/docs/dm_more/index.txt +++ /dev/null @@ -1,128 +0,0 @@ ---- -title: DM More -created_at: 2008-08-30 19:16:25.492127 +01:00 -filter: - - erb - - textile ---- -h1. <%= h(@page.title) %> - -DataMapper is intended to have a lean and minimalistic core, which provides the minimum necessary features for an ORM. It's also designed to be easily extensible, so that everything you want in an ORM can be added in with a minimum of fuss. It does this through plugins, which provide everything from automatically updated timestamps to factories for generating DataMapper resources. The biggest collection of these plugins is in dm-more, which isn't to say that there's anything wrong with plugins which aren't included in dm-more -- it will never house all the possible plugins. - -This page gives an overview of the plugins available in dm-more, loosely categorized by what type of plugin they are. - -h2. Resource Plugins - -These plugins modify the behavior of all resources in an application, adding new functionality to them, or providing easier ways of doing things. - -h3. <%= link_to_page('DM-Validations', :title => 'Validations') %> - -This provides validations for resources. The plugin both defines automatic validations based on the properties specified and also allows assignment of manual validations. It also supports contextual validation, allowing a resource to be considered valid for some purposes but not others. - -h3. <%= link_to_page('DM-Timestamps', :title => 'DM-Timestamps') %> - -This defines callbacks on the common timestamp properties, making them auto-update when the models are created or updated. The targeted properties are @:created_at@ and @:updated_at@ for DateTime properties and @:created_on@ and @:updated_on@ for Date properties. - -h3. <%= link_to_page('DM-Aggregates', :title => 'DM-Aggregates') %> - -This provides methods for database calls to aggregate functions such as @count@, @sum@, @avg@, @max@ and @min@. These aggregate functions are added to both collections and Models. - -h3. <%= link_to_page('DM-Types', :title => 'DM-Types') %> - -This provides several more allowable property types. @Enum@ and @Flag@ allow a field to take a few set values. @URI@, @FilePath@, @Regexp@, @EpochTime@ and @BCryptHash@ save database representations of the classes, restoring them on retrieval. @Csv@, @Json@ and @Yaml@ store data in the field in the serial formats and de-serialize them on retrieval. - -h3. DM-Serializer - -This provides '@to_*@' methods which take a resource and convert it to a serial format to be restored later. Currently the plugin provides @to_xml@, @to_yaml@ and @to_json@ - -h3. DM-Constraints - -This plugin provides foreign key constrains on has n relationships for Postgres and MySQL adapters. - -h3. DM-Adjust - -This plugin allows properties on resources, collections and models to incremented or decremented by a fixed amount. - -h2. is Plugins - -These plugins make new functionality available to models, which can be accessed via the @is@ method, for example @is :list@. These make the models behave in new ways. - -h3. DM-Is-List - -The model acts as an item on a list. It has a position, and there are methods defined for moving it up or down the list based on this position. The position can also be scoped, for example on a user id. - -h3. DM-Is-Tree - -The model acts as a node of a tree. It gains methods for querying parents and children as well as all the nodes of the current generation, the trail of ancestors to the root node and the root node itself. - -h3. DM-Is-Nested_Set - -The model acts as an item in a 'nested set'. This might be used for some kind of categorization system, or for threaded conversations on a forum. The advantage this has over a tree is that is easy to fetch all the descendants or ancestors of a particular set in one query, not just the next generation. Added to a nested set is more complex under the hood, but the plugin takes care of this for you. - -h3. DM-Is-Versioned - -The model is versioned. When it is updated, instead of the previous version being lost in the mists of time, it is saved in a subsidiary table, so that it can be restored later if needed. - -h3. DM-Is-State_Machine - -The model acts as a state machine. Instead of a column being allowed to take any value, it is used to track the state of the machine, which is updated through events that cause transitions. For example, this might step a model through a sign-up process, or some other complex task. - -h3. DM-Is-Remixable - -The model becomes 'remixable'. It can then be included (or remixed) in other models, which defines a new table to hold the remixed model and can have other properties or methods defined on it. It's something like class table inheritance for relationships :) - -h2. Adapters - -These plugins provide new adapters for different storage schemes, allowing them to be used to store resources, instead of the more conventional relational database store. - -h3. DM-CouchDB-Adapter - -An adapter for the JSON based document database couch-db. The adaptor has support for both defining models backed by a couch-db store and also for couch-db views. - -h3. DM-Rest-Adapter - -An adapter for a XML based REST-backed storage scheme. All the usual DataMapper operations are performed as HTTP GETs, POSTs, UPDATEs and DELETEs, operating on the URIs of the resources. - -h2. Integration Plugins - -These plugins are designed to ease integration with other libraries, currently just web frameworks. - -h3. merb_datamapper - -Integration with the merb web framework. The plugin takes care of setting up the DataMapper connection when the framework starts, provides several useful rake tasks as well as generators for Models, ResourceControllers and Migrations. - -h3. rails_datamapper - -Integration with Rails. It provides a Model generator and also takes care of connecting to the data-store through DataMapper. - -h2. Utility Plugins - -These provide useful functionality, though are unlikely to be used by every project or assist more with development than production use. - -h3. DM-Sweatshop - -A model factory for DataMapper, supporting the creation of random models for specing or to fill an application for development. Properties can be picked at random or made to conform to a variety of regular expressions. dm-sweatshop also understands has n relationships and can assign a random selection of child models to a parent. - -h3. DM-Migrations - -Migrations for DataMapper, allowing modification of the database schema with more control than @auto_migrate!@ and @auto_upgrade!@. Migrations can be written to create, modify and drop tables and columns. In addition, the plugin provides support for specing migrations and verifying they perform as intended. - -h3. DM-Shorthand - -This plugin eases operations involving models across multiple repositories, allowing wrapping in a @repository(:foo)@ block to be replaced with a @MyModel(:foo).some_method@ call. - -h3. DM-Observer - -Observers watch other classes, doing things when certain operations are performed on the remote class. This can be anything, but they are commonly used for writing logs or notifying via email or xmpp when a critical operation has occurred. - -h3. DM-CLI - -The @dm@ executable is a DataMapper optimized version of @irb@. It automatically connections to a data-store based on the arguments passed to it and supports easy loading of DataMapper plugins, models from a directory as well as reading connection information from a YAML configuration file. - -h3. DM-Querizer - -This provides alternate syntax for queries, replacing the hash which DataMapper uses with a more 'ruby-ish' use of @&&@, @==@ and @=~@. - -h3. DM-Ar_Finders - -ActiveRecord style syntax for DataMapper. This includes functionality such as @find_by_name@, @find_or_create@ and @find_all_by_title@. diff --git a/content/docs/dm_more/timestamps.txt b/content/docs/dm_more/timestamps.txt deleted file mode 100644 index 3913c47..0000000 --- a/content/docs/dm_more/timestamps.txt +++ /dev/null @@ -1,34 +0,0 @@ ---- -title: DM-Timestamps -created_at: 2008-09-06 14:11:54.160780 -05:00 -filter: - - erb - - textile ---- -h1. <%= @page.title %> - -DM-Timestamps provides automatic updates of @created_at@ or @created_on@ and @updated_at@ or @updated_on@ properties for your resources. To use it, simply @require 'dm-timestamps'@ and assign one or all of these properties to your Resource. - -Here's some basic usage. - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> -require 'rubygems' -require 'dm-core' -require 'dm-timestamps' - -DataMapper.setup(:default, 'sqlite3::memory:') - -class Post - include DataMapper::Resource - property :id, Serial - property :title, String, :length => 255 - - property :created_at, DateTime - property :created_on, Date - - property :updated_at, DateTime - property :updated_on, Date -end -<% end %> - -If you're familiar with the 'magic' properties from ActiveRecord, this is very similar. When your model is initially saved, DM-Timestamps will update that object's @created_at@ and/or @created_on@ fields with the time of creation. When the object is edited and then saved out to the data-store, DM-Timestamps will update the @updated_at@ and @updated_on@ fields with the time of update. diff --git a/content/docs/dm_more/types.txt b/content/docs/dm_more/types.txt deleted file mode 100644 index e890008..0000000 --- a/content/docs/dm_more/types.txt +++ /dev/null @@ -1,112 +0,0 @@ ---- -title: DM-Types -created_at: 2008-09-06 18:30:16.740699 +01:00 -filter: - - erb - - textile ---- - -h1. <%# @page.title %> - -Types are used by DataMapper to map ruby objects into values in the data-store on saving and to translate the values back into ruby objects when a resource is retrieved. The core library supplies several different types, providing mappings for most of the simple ruby objects as direct values in the data-store as well as the Object type which can store any Marshallable ruby object. - -They're intended to make the storage of ruby objects as transparent as possible, no matter what the ruby object is, or which data-store is used as the backend. - -h2. dm-types - -In <%= link_to_page('DM More') %> there is the dm-types gem, which supplies several more types that map less common ruby classes to data-store values or take care of serializing them to text based formats. - -h3. Enum - -The Enum type uses an Integer create a property which can take one of a number of symbolic values. A use for this might be bug status for an issue tracker or the protocol being used in a packet logging application. - -<% coderay(:lang => 'ruby', :line_numbers => 'inline') do -%> -class Issue - include DataMapper::Resource - - property :status, Enum[ :new, :open, :closed, :invalid ], :default => :new - # other properties ... -end - -@i = Issue.new -@i.status -#=> :new -@i.status = :open -#=> :open -<% end %> - -h3. Flag - -A Flag is similar to an Enum, though the property that is created can hold multiple symbol values at once. This could be used for recording which colours a product can be ordered in, or what extra features an account has enabled. - -<% coderay(:lang => 'ruby', :line_numbers => 'inline') do -%> -class Widget - include DataMapper::Resource - - property :colours, Flag[ :red, :green, :blue, :heliotrope ] - - # other properties ... -end - -@w = Widget.new -@w.colours = [ :red, :heliotrope ] -<% end %> - -h3. Object Mappings - -These map objects into simple data-store primitives, then re-initialize as the ruby types - -
    -
    EpochTime
    -
    A ruby Time object stored in the data-store as an Integer -- the number of seconds since the UNIX epoch.
    -
    URI
    -
    A ruby URI object, pre-parsed for use of methods such as @#params@ or @#uri@, stored as a string in the data-store.
    -
    FilePath
    -
    Stored as a string in the data-store, FilePaths initialize Pathname objects, making it easy to perform various file operations on the file.
    -
    Regex
    -
    A ruby regex, stored as a string.
    -
    IPAddress
    -
    Ruby IPAddr, stored as the string representation.
    -
    BCryptHash
    -
    Stored in the data-store as string representing the salt, hash and cost of a password using OpenBSD's bcrypt algorithm, it offers an alternative to the more usual pair of hash and salt columns.
    -
    - -h3. Serializers - -These store values in the data-store using text based serialization formats. They work via calling dumping the object to the format on saving and parsing the text to reinitialize them on loading. - -* Csv -* Json -* Yaml - -h2. Writing Your Own - -Writing your own custom type isn't difficult. There are two (or perhaps three) methods to implement, as well as the selection of an appropriate primitive. All types are a class which should descend from @DataMapper::Type@. - -h3. The Primitive - -DataMapper offers several choices for a data-store primitive. - -* Integer -* Float -* String -* Date -* DateTime - -To assign a primitive to a type, either make the type descend from @DataMapper::Type(PrimitiveClass)@ or within the class definition, use @primitive PrimitiveClass@. - -h3. dump - -A type's @self.dump(value, property)@ method is called when the object is saved to the data-store. It is responsible for mapping whatever is assigned to the property on to the primitive type. For example, the EpochTime Type saves an integer directly to the data-store, or calls @to_i()@ if a Time object is passed to it. - -h3. load - -The @self.load(value, property)@ method is called when the property is retrieved from the data-store. It takes the primitive value and initializes a ruby object from it. For example, the Json type performs @JSON.parse(value)@ to convert the json string back into appropriate ruby. - -h3. typecast - -Typecasting is provided by the types @self.typecast(value, property)@ method, which tries to coerce whatever value the property has into an appropriate type. A type doesn't have to provide a typecast but it can be useful, for example to allow the string '2008-09-06' to be converted to a ruby Date without having to reload the model. - -h3. Examples - -For examples of the types, the best place to look is dm-types on github. diff --git a/content/docs/install.txt b/content/docs/install.txt deleted file mode 100644 index e7aaf31..0000000 --- a/content/docs/install.txt +++ /dev/null @@ -1,43 +0,0 @@ ---- -title: Installation Issues -body_id: docs -created_at: Tue Dec 04 13:20:00 +1030 2007 -filter: - - erb - - textile ---- - -h1. <%= @page.title %> - -If you've followed the "install instructions":/getting-started.html but run into problems, you can find some tips below. - -h2(newRelease). Windows Users - -p(newRelease). At present, "DataObjects":https://rubyforge.org/projects/dorb/ does not run well on Windows natively
    and will require you to install cygwin or another linux-like
    environment. People have been able to get it installed and running
    on Windows but with severe drops in performance. This is a known
    issue and we're working on it. - -h2. Dependencies - -First port of call if you're having issues with an installation is to make sure you have all the dependencies installed. Rubygems should take care of this for you, but just in case, make sure you have the following gems as well: - -* fastthread -* json -* rspec - for running specs on DataMapper itself - -h2(#trunk). Using Trunk - -You will also need to install the DataObject gem and the adaptor for your platform - -<% coderay(:lang => "bash", :line_numbers => "inline") do -%> -sudo gem install data_objects -sudo gem install do_mysql -<% end %> - -The current database adaptors are: - -* do_mysql -* do_sqlite3 -* do_postgres - -h2. Getting Help - -If you still have issues, we suggest getting onto the "mailing list":http://groups.google.com/group/datamapper or the "IRC channel":irc://irc.freenode.net/#datamapper and asking around. There's friendly people there to help you out. diff --git a/content/docs/misc.txt b/content/docs/misc.txt deleted file mode 100644 index 17cc1d6..0000000 --- a/content/docs/misc.txt +++ /dev/null @@ -1,80 +0,0 @@ ---- -title: Miscellaneous Features -created_at: Thu Mar 20 23:26:54 -0500 2008 -filter: - - erb - - textile ---- -h1. <%= @page.title %> - -DataMapper comes loaded features, many of which other ORMs require external libraries for. - -h2. Single Table Inheritance - -Many ORMs support Single Table Inheritance and DataMapper is no different. In order to declare a model for Single Table Inheritance, define a property with the data-type of <%= doc('Types::Discriminator') %> - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> -class Person - include DataMapper::Resource - - property :name, String - property :job, String, :length => 255 - property :type, Discriminator - ... -end - -class Male < Person; end -class Father < Male; end -class Son < Male; end - -class Woman < Person; end -class Mother < Woman; end -class Daughter < Woman; end - -<% end %> - -When DataMapper sees your @type@ column declared as type <%= doc('Types::Discriminator') %>, it will automatically insert the class name of the object you've created and later instantiate that row as that class. It also supports deep inheritance, so doing Woman.all will select all women, mothers, and daughters (and deeper inherited classes if they exist). - -h2. Paranoia - -Sometimes...most times...you don't _really_ want to destroy a row in the database, you just want to mark it as deleted so that you can restore it later if need be. This is aptly-named Paranoia and DataMapper has basic support for this baked right in. Just declare a property and assign it a type of <%= doc('Types::ParanoidDateTime') %> or <%= doc('Types::ParanoidBoolean') %>: - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> -property :deleted_at, ParanoidDateTime -<% end %> - -h2. Multiple Data-Store Connections - -DataMapper sports a concept called a context which encapsulates the data-store context in which you want operations to occur. For example, when you setup a connection in "getting-started":/getting-started.html, you were defining a context known as @:default@ - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> - DataMapper.setup(:default, 'mysql://localhost/dm_core_test') -<% end %> - -But if you supply a context name, you will now have 2 database contexts with their own unique loggers, connection pool, identity map....one default context and one named context. - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> -DataMapper.setup(:external, 'mysql://someother_host/dm_core_test') -<% end %> - -To use one context rather than another, simply wrap your code block inside a @database@ call. It will return whatever your block of code returns. - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> -repository(:external) { Person.first } -# hits up your :external database and retrieves the first Person -<% end %> - -This will use your connection to the @:external@ data-store and the first Person it finds. Later, when you call @.save@ on that person, it'll get saved back to the @:external@ data-store; An object is aware of what context it came from and should be saved back to. - -h2. Chained Associations - -Say you want to find all of the animals in a zoo, but Animal belongs to Exhibit which belongs to Zoo. Other ORMs solve this problem by providing a means to describe the double JOINs into the retrieval call for Animals. ActiveRecord specifically will let you specify JOINs in a hash-of-hashes syntax which will make most developers throw up a little in their mouths. - -DataMapper's solution is to let you chain association calls: - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> -zoo = Zoo.first -zoo.exhibits.animals # retrieves all animals for all exhibits for that zoo -<% end %> - -This has great potential for browsing collections of content, like browsing all blog posts' comments by category or tag. At present, chaining beyond 2 associations is still experimental. diff --git a/content/docs/properties.txt b/content/docs/properties.txt deleted file mode 100644 index 0862cc2..0000000 --- a/content/docs/properties.txt +++ /dev/null @@ -1,182 +0,0 @@ ---- -title: Properties -body_id: docs -created_at: Tue Dec 04 13:27:16 +1030 2007 -filter: - - erb - - textile ---- - -h1. <%= @page.title %> - -A model's properties are not introspected from the fields in the data-store; In fact the reverse happens. You declare the properties for a model inside it's class definition, which is then used to generate the fields in the data-store. - -This has a few advantages. First it means that a model's properties are documented in the model itself, not a migration or XML file. If you've ever been annoyed at having to look in a schema file to see the list of properties and types for a model, you'll find this particularly useful. There's no need for a special @annotate@ rake task either. - -Second, it lets you limit access to properties using Ruby's access semantics. Properties can be declared public, private or protected. They are public by default. - -Finally, since DataMapper only cares about properties explicitly defined in your models, DataMapper plays well with legacy data-stores and shares them easily with other applications. - -h2. Declaring Properties - -Inside your class, call the property method for each property you want to add. The only two required arguments are the name and type, everything else is optional. - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> -class Post - include DataMapper::Resource - - property :id, Serial # primary serial key - property :title, String, :nullable => false # Cannot be null - property :published, Boolean, :default => false # Default value for new records is false -end -<% end %> - -h2. Keys - -h3. Primary Keys - -Primary keys are not automatically created for you, as with ActiveRecord. You MUST configure at least one key property on your data-store. More often than not, you'll want an auto-incrementing integer as a primary key, so DM has a shortcut: - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> - property :id, Serial -<% end %> - -h3. Natural Keys - -Anything can be a key. Just pass @:key => true@ as an option during the property definition. Most commonly, you'll see String as a natural key: - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> - property :slug, String, :key => true # any Type is available here -<% end %> - -Natural Keys are protected against mass-assignment, so their @setter=@ will need to be called individually if your looking to set them. - -*Fair warning:* Using Boolean, Discriminator, and the time related types as keys may cause your DBA to hunt you down and "educate" you. DM will not be held responsible for any injuries or death that may result. - -h3. Composite Keys - -You can have more than one property in the primary key: - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> -class Post - include DataMapper::Resource - - property :old_id, Integer, :key => true - property :new_id, Integer, :key => true -end -<% end %> - -h2. Setting Defaults - -Defaults can be set via the @:default@ key for a property. They can be static values, such as @12@ or @"Hello"@, but DataMapper also offers the ability to use a Proc to set the default value. The property becomes whatever the Proc returns, which will be called the first time the property is used without having first set a value. The Proc itself receives two arguments: The resource the property is being set on, and the property itself. - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> -class Image - include DataMapper::Resource - - property :id, Serial - property :path, FilePath, :nullable => false - property :md5sum, String, :length => 32, :default => lambda { |r, p| Digest::MD5.hexdigest(r.path.read) if r.path } -end -<% end %> - -When creating the resource, or the first time the @md5sum@ property is accessed, it will be set to the hex digest of the file referred to by @path@. - -*Fair Warning*: A property default must _not_ refer to the value of the property it is about to set, or there will be an infinite loop. - -h2. Lazy Loading - -Properties can be configured to be lazy loaded. A lazily loaded property is not requested from the data-store by default. Instead it is only loaded when it's accessor is called for the first time. This means you can stop default queries from being greedy, a particular problem with text fields. Text fields are lazily loaded by default, which you can over-ride if you need to. - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> -class Post - include DataMapper::Resource - - property :id, Serial - property :title, String - property :body, Text # Is lazily loaded by default - property :notes, Text, :lazy => false # Isn't lazy, will load by default -end -<% end %> - -Lazy Loading can also be done via contexts, which let you group lazily loaded properties together, so that when one is fetched, all the associated ones will be as well, cutting down on trips to the data-store. - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> -class Post - include DataMapper::Resource - - property :id, Serial - property :title, String - property :subtitle, String :lazy => [ :show ] - property :body, Text :lazy => [ :show ] - property :views, Integer, :lazy => [ :show ] - property :summary, Text -end -<% end %> - -In this example, only the title (and the id, of course) will be loaded from the data-store on a @Post.all()@. But as soon as the value for subtitle, body or views are called, all three will be loaded at once, since they're members of the @:show@ group. The summary property on the other hand, will only be fetched when it is asked for. - -h2. Available Types - -DM-Core supports the following 'primitive' data-types. - -* Boolean -* String -* Text -* Float -* Integer -* BigDecimal, -* DateTime, Date, Time -* Object, (marshalled) -* Discriminator - -If you include DM-Types, the following data-types are supported: - -* Csv -* Enum -* EpochTime -* FilePath -* Flag -* IPAddress -* URI -* Yaml -* Json -* BCryptHash -* Regex - -h2. Limiting Access - -Access for properties is defined using the same semantics as Ruby. Accessors are public by default, but you can declare them as private or protected if you need to. You can set access using the @:accessor@ option. For demonstration, we'll reopen our Post class. - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> -class Post - property :title, String, :accessor => :private # Both reader and writer are private - property :body, Text, :accessor => :protected # Both reader and writer are protected -end -<% end %> - -You also have more fine grained control over how you declare access. You can, for example, have a public reader and private writer by using the @:writer@ and @:reader@ options. (Remember, the default is Public) - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> -class Post - property :title, String, :writer => :private # Only writer is private - property :tags, String, :reader => :protected # Only reader is protected -end -<% end %> - -h2. Over-riding Accessors - -When a property has declared accessors for getting and setting, it's values are added to the model. Just like using @attr_accessor@, you can over-ride these with your own custom accessors. It's a simple matter of adding an accessor after the property declaration. Reopening the Post class.... - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> -class Post - property :slug, String - - def slug=(new_slug) - raise ArgumentError if new_slug != 'DataMapper is Awesome' - attribute_set(:slug, new_title) # use attribute_set instead of talking - # to the @ivars directly. - # This tracks dirtiness. - end -end -<% end %> diff --git a/content/getting-started.txt b/content/getting-started.txt deleted file mode 100644 index 6dd58d4..0000000 --- a/content/getting-started.txt +++ /dev/null @@ -1,147 +0,0 @@ ---- -page_id: gettingStarted -title: Getting started with DataMapper -created_at: Wed Aug 29 20:36:53 +0930 2007 -filter: - - erb - - textile ---- - -h1. <%= @page.title %> - -First, if you think you might need some help, there's an active community supporting DataMapper through "the mailing list":http://groups.google.com/group/datamapper and the @#datamapper@ IRC channel on irc.freenode.net. - -So lets imagine we're setting up some models for a blogging app. We'll keep it nice and simple. The first thing to decide on is what models we want. Post is a given. So is Comment. But let's mix it up and do Category too. - -h2. Install DataMapper - -If you have RubyGems installed, pop open your console and install a few things. - -<% coderay(:lang => "bash", :line_numbers => "inline") do -%> -gem install dm-core -<% end %> - -If you are planning on using DataMapper with a database, install a database driver from the DataObjects project: (Substitute @do_sqlite3@ with @do_postgres@ or @do_sqlite3@ depending on your preferences) - -<% coderay(:lang => "bash", :line_numbers => "inline") do -%> -gem install do_sqlite3 -<% end %> - -h2. Require it in your application - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> -require 'rubygems' -require 'dm-core' -<% end %> - -h2. Specify your database connection - -You need make sure this is set before you define your models. - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> - # An in-memory Sqlite3 connection: - DataMapper.setup(:default, 'sqlite3::memory:') - - # A MySQL connection: - DataMapper.setup(:default, 'mysql://localhost/the_database_name') - - # A Postgres connection: - DataMapper.setup(:default, 'postgres://localhost/the_database_name') -<% end %> - -h2. Define your models - -The Post model is going to need to be persistent, so we'll include <%= doc('DataMapper::Resource') %>. The convention with model names is to use the singular, not plural version...but that's just the convention, you can do whatever you want. - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> -class Post - include DataMapper::Resource - - property :id, Serial - property :title, String - property :body, Text - property :created_at, DateTime -end - -class Comment - include DataMapper::Resource - - property :id, Serial - property :posted_by, String - property :email, String - property :url, String - property :body, Text -end - -class Category - include DataMapper::Resource - - property :id, Serial - property :name, String -end -<% end %> - -The above example is simplified, but you can also specify more options such as constraints for your properties. - -h2. Associations - -Ideally, these declarations should be done inside your class definition with the properties and things, but for demonstration purposes, we're just going to crack open the classes. - -h3. One To Many - -Posts can have comments, so we'll need to setup a simple one-to-many association between then: - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> -class Post - has n, :comments -end - -class Comment - belongs_to :post -end -<% end %> - -h3. Has and belongs to many - -Categories can have many Posts and Posts can have many Categories, so we'll need a many to many relationships commonly referred to "has and belongs to many". We'll setup a quick model to wrap our join table between the two so that we can record a little bit of meta-data about when the post was categorized into a category. - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> -class Categorization - include DataMapper::Resource - - property :id, Serial - property :created_at, DateTime - - belongs_to :category - belongs_to :post -end - -# Now we re-open our Post and Categories classes to define associations -class Post - has n, :categorizations - has n, :categories, :through => :categorizations -end - -class Category - has n, :categorizations - has n, :posts, :through => :categorizations -end - -<% end %> - -h2. Set up your database tables - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> -Post.auto_migrate! -Category.auto_migrate! -Comment.auto_migrate! -Categorization.auto_migrate! -<% end %> - -This will issue the necessary CREATE statements to define each storage according to their properties. - -You could also do: - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> -DataMapper.auto_migrate! -<% end %> diff --git a/content/index.txt b/content/index.txt deleted file mode 100644 index 43a1126..0000000 --- a/content/index.txt +++ /dev/null @@ -1,57 +0,0 @@ ---- -body_id: home -title: DataMapper -filter: - - erb - - textile -dirty: true ---- - -p(blurb). DataMapper is a "Object Relational Mapper":http://en.wikipedia.org/wiki/Object-relational_mapping written in "Ruby.":http://ruby-lang.org/ The goal is to create an ORM which is fast, thread-safe and feature rich. - -p(blurb). To learn a little more about this project and why you should be interested,
    read the "Why Datamapper?":/why.html page. - -<% update = @pages.find(:in_directory => "articles", :sort_by => "created_at", :reverse => true, :release_type => 'important') %> - -h2(latest-release). Recent News - -p(latest-release). <%= update.title %>
    <%= update.summary %> Read More - -
    - -h2. Help! - -If you're having trouble, don't forget to check the documentation, which has both references and step by step tutorials.
    "Read documentation":/docs - -
    - -
    - -h2. Issues - -If you're still having trouble, or you think you came across something you think might be a bug, let us know. - -"Log a ticket":http://datamapper.lighthouseapp.com/projects/20609-datamapper/overview - -
    - -h1. News - -
    -<% - articles = @pages.find(:limit => :all, :in_directory => 'articles') { |a| a.release_type != 'draft' }.sort { |a, b| b.created_at <=> a.created_at } - - paginate(articles, 20) do |page| -%> - -
    <%= page.title %>
    -
    -

    <%= page.summary %>

    -

    <%= page.created_at.strftime(" Posted On %m/%d/%Y") %> by <%= page.author %>

    -
    - -<% end %> -
    - -<%= link_to("Prev", @pager.prev) if @pager.prev? %> -<%= link_to("Next", @pager.next) if @pager.next? %> diff --git a/content/news.txt b/content/news.txt deleted file mode 100644 index 9287327..0000000 --- a/content/news.txt +++ /dev/null @@ -1,30 +0,0 @@ ---- -body_id: news -title: DataMapper News and Notes -filter: - - erb - - textile ---- - -h1. <%= @page.title %> - -
    -<% - articles = @pages.find(:limit => :all, :in_directory => 'articles') { |a| a.release_type != 'draft' }.sort { |a, b| b.created_at <=> a.created_at } - - paginate(articles, 20) do |page| -%> - -
    <%= page.title %>
    -
    -

    <%= page.summary %>

    -

    <%= page.created_at.strftime(" Posted On %m/%d/%Y") %> by <%= page.author %>

    -
    - -<% end %> -
    - -

    - <%= link_to("Prev", @pager.prev) if @pager.prev? %> - <%= link_to("Next", @pager.next) if @pager.next? %> -

    diff --git a/content/using-git.rhtml b/content/using-git.rhtml deleted file mode 100644 index 41cdd13..0000000 --- a/content/using-git.rhtml +++ /dev/null @@ -1,160 +0,0 @@ ---- -title: Using Git -created_at: Mon Feb 18 16:18:12 -0600 2008 -filter: - - erb ---- -

    <%= @page.title %>

    - -

    The DataMapper project uses the Git SCM. Committers need to use git to commit their code directly to the main repository.

    - -

    This page contains information on getting Git installed, getting source code with Git, and steps for working with Git.

    - -

    Also, see these references: Git – SVN Crash Course and Everyday GIT With 20 Commands Or So

    - -

    Getting Git for Your System

    - -

    You can use an earlier version, but 1.5.x is definitely recommended.

    - - - -

    Setup

    - -

    Configure Git with your proper name and email. This will display when you submit changes to the DataMapper repository.

    - -
    git config  --global user.name "My Name"
    -git config  --global user.email "my@email"
    -
    - -

    If you prefer to use different credentials for different projects, you can also configure the above for a single repository only. See the git documentation.

    - -

    Formatting Git Commit Messages

    - -

    In general, use an editor to create your commit messages rather than passing them on the command line. The format should be:

    - -
      -
    • A hard wrap at 72 characters
    • -
    • A single, short, summary of the commit
    • -
    • Followed by a single blank line
    • -
    • Followed by supporting details
    • -
    - -

    The supporting details could be a bulleted enumeration or an explanatory paragraph. The single summary line helps folks reviewing commits. An example commit:

    - -<% coderay(:lang => "bash") do -%> -Fixes for Module#make_my_day return values. - -* Return nil when passed ':(' -* Return true when passed ':)' -* Updated specs for #make_my_day for nil argument case -* Updated CI excludes. -<% end %> - -

    Getting the Code

    - -

    DataMapper is hosted at GitHub. Getting the code is easy once you have git installed but is slightly different depending on your access. In both cases that exact command will put the repository in a local directory called dm. You can give it a different name just by appending it to the command.

    - -

    New Users and Developers

    - -<% coderay(:lang => "bash", :line_numbers => "inline") do -%> -git clone git://github.com/datamapper/dm-core.git -<% end %> - -

    Committers with Commit Bit

    - -<% coderay(:lang => "bash", :line_numbers => "inline") do -%> -git clone git@github.com/datamapper/dm-core.git -<% end %> - -

    Git Workflow

    - -

    Working with Git is significantly different than working with SVN. In particular, although similar, git pull is not svn update, git push is not svn commit, and git add is not svn add. If you are a SVN user, be sure to read the man pages for the different git commands.

    - -

    The following workflow is recommended by Rein and is the guideline for contributing code to DataMapper.

    - -
      -
    1. -

      Create a local working copy of the source code (we did this earlier.)

      -
      # See above for the exact invocation
      -
    2. - -
    3. -

      Change to the newly created directory that contains the local working copy. (Substitute the directory if you created it with a different name, obviously.)

      -
      cd dm
      -
    4. - -
    5. -

      Create a branch for your work. It is important that you do your work in a local branch, rather than master.

      -
      git checkout -b new_feature
      -
    6. - -
    7. -

      Edit the code and test your changes. Then commit to your local working copy.

      -
      git add .
      -      git commit
      -
    8. - -
    9. -

      When you are ready to send your local changes back to the DataMapper repository, you first need to ensure that your local copy is up-to-date. First, ensure you have committed your local changes. Then switch from your topic branch to the master branch.

      -
      git checkout master
      -
    10. - -
    11. -

      Update your local copy with changes from the DataMapper repository

      -
      git pull origin master --rebase
      -
    12. - -
    13. -

      Switch back to your topic branch and integrate any new changes. The git rebase command will save your changes away, update the topic branch, and then reapply them.

      -
      git checkout new_feature
      -      git rebase master
      -

      Warning! If you have shared the topic branch publicly, you must use

      - -
      git merge master
      -

      Rebase causes the commit layout to change and will confuse anyone you’ve shared this branch with.

      -
    14. - -
    15. -

      If there are conflicts applying your changes during the git rebase command, fix them and use the following to finish applying them

      -
      git rebase --continue
      -
    16. - -
    17. -

      Now, switch back to the master branch and merge your changes from the topic branch

      -
      git checkout master
      -      git merge new_feature
      -
    18. - -
    19. -

      You might want to check that your commits ended up as you intended. To do so, you can have a look at the log

      -
      git log
      -
    20. - -
    21. -

      Get your changes in the main repository. If you have commit rights, you can just use the git push command. Otherwise, see the section below for information on creating a set of patches to send.

      -
      git push origin master
      -
    22. - -
    23. -

      At this point, you can delete the branch if you like.

      -
      git branch -d new_feature
      -
    24. -
    - -

    Patches: git-format-patch

    - -

    If you are a new committer (or want to create a patch instead of directly pushing the code for some other reason) you should create a patch file for your commits. The patch file should be then attached to a ticket on Lighthouse. You can also send the patch to the mailing list but please use the ticket tracker if at all possible. Either way, the patch file(s) should be created using Git.

    - -

    First, make your changes as detailed below and then use the git format-patch command to create the patch files. Usually using the command is as simple as specifying the commits you want to create patches for, and that is done in one of two ways: by giving a range of commits or a starting point.

    - -

    For our purposes, the simplest way to create a patch is to begin at the end of step 8 above (after you have rebased your branch) and then, instead of merging:

    - -
    git format-patch master..
    - -

    This will create a separate patch file for each commit in your working branch that is not in master, named [number]-[first line of commit message].patch. You can then attach these to a ticket (or e-mail them).

    - -

    You can also inspect your changes using git log master.. or git diff master.. to ensure that the patches will be generated correctly if you are uncertain.

    diff --git a/content/why.txt b/content/why.txt deleted file mode 100644 index df9a70a..0000000 --- a/content/why.txt +++ /dev/null @@ -1,138 +0,0 @@ ---- -body_id: why -title: Why DataMapper? -filter: - - erb - - textile ---- - -h1. Why DataMapper? - -DataMapper differentiates itself from other Ruby Object/Relational Mappers in a number of ways: - -h2. Identity Map - -One row in the database should equal one object reference. Pretty simple idea. Pretty profound impact. If you run the following code in ActiveRecord you'll see all @false@ results. Do the same in DataMapper and it's @true@ all the way down. - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> -@parent = Tree.first(:conditions => { :name => 'bob' }) - -@parent.children.each do |child| - puts @parent.object_id == child.parent.object_id -end -<% end %> - -This makes DataMapper faster and allocate less resources to get things done. - -h2. Plays Well With Others - -With DataMapper you define your mappings in your model. Your data-store can develop independently of your models using Migrations. - -To support data-stores which you don't have the ability to manage yourself, it's simply a matter of telling DataMapper where to look. - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> -class Fruit - include DataMapper::Resource - - storage_names[:default] = 'frt' # equivalent to set_table_name in AR - - property :id, Serial - property :name, String, :field => 'col2' -end -<% end %> - -DataMapper only issues updates or creates for the properties it knows about. So it plays well with others. You can use it in an Integration Database without worrying that your application will be a bad actor causing trouble for all of your other processes. - -h2. Laziness Can Be A Virtue - -Columns of potentially infinite length, like Text columns, are expensive in data-stores. They're generally stored in a different place from the rest of your data. So instead of a fast sequential read from your hard-drive, your data-store has to hop around all over the place to get what it needs. - -With DataMapper, these fields are treated like in-row associations by default, meaning they are loaded if and only if you access them. If you want more control you can enable or disable this feature for any column (not just text-fields) by passing a @lazy@ option to your column mapping with a value of @true@ or @false@. - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> -class Animal - include DataMapper::Resource - - property :id, Serial - property :name, String - property :notes, Text # lazy-loads by default -end -<% end %> - -Plus, lazy-loading of Text property happens automatically and intelligently when working with associations. The following only issues 2 queries to load up all of the notes fields on each animal: - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> -animals = Animal.all -animals.each do |pet| - pet.notes -end -<% end %> - -h2. Strategic Eager Loading - -DataMapper will only issue the very bare minimums of queries to your data-store that it needs to. For example, the following example will only issue 2 queries. Notice how we don't supply any extra @:include@ information. - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> -zoos = Zoo.all -zoos.each do |zoo| - # on first iteration, DM loads up all of the exhibits for all of the items in zoos - # in 1 query to the data-store. - - zoo.exhibits.each do |exhibit| - # n+1 queries in other ORMs, not in DataMapper - puts "Zoo: #{zoo.name}, Exhibit: #{exhibit.name}" - end -end -<% end %> - -The idea is that you aren't going to load a set of objects and use only an association in just one of them. This should hold up pretty well against a 99% rule. - -When you don't want it to work like this, just load the item you want in it's own set. So DataMapper thinks ahead. We like to call it "performant by default". *This feature single-handedly wipes out the "N+1 Query Problem".* - -DataMapper also waits until the very last second to actually issue the query to your data-store. For example, @zoos = Zoo.all@ won't run the query until you start iterating over @zoos@ or call one of the 'kicker' methods like @#length@. If you never do anything with the results of a query, DataMapper won't incur the latency of talking to your data-store. - -h2. All Ruby, All The Time - -DataMapper goes further than most Ruby ORMs in letting you avoid writing raw query fragments yourself. It provides more helpers and a unique hash-based conditions syntax to cover more of the use-cases where issuing your own SQL would have been the only way to go. - -For example, any finder option that are non-standard is considered a condition. So you can write @Zoo.all(:name => 'Dallas')@ and DataMapper will look for zoos with the name of 'Dallas'. - -It's just a little thing, but it's so much nicer than writing @Zoo.find(:all, :conditions => [ 'name = ?', 'Dallas' ])@ and won't incur the Ruby overhead of @Zoo.find_by_name('Dallas')@, nor is it more difficult to understand once the number of parameters increases. - -What if you need other comparisons though? Try these: - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> -Zoo.first(:name => 'Galveston') - -# 'gt' means greater-than. 'lt' is less-than. -Person.all(:age.gt => 30) - -# 'gte' means greather-than-or-equal-to. 'lte' is also available -Person.all(:age.gte => 30) - -Person.all(:name.not => 'bob') - -# If the value of a pair is an Array, we do an IN-clause for you. -Person.all(:name.like => 'S%', :id => [ 1, 2, 3, 4, 5 ]) - -# Does a NOT IN () clause for you. -Person.all(:name.not => [ 'bob', 'rick', 'steve' ]) - -# Ordering -Person.all(:order => [ :age.desc ]) -# .asc is the default -<% end %> - -To query a model by it's associations, you can use a QueryPath: - -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> - Person.all(:links => [ :pets ], Person.pets.name => 'Pixel') -<% end %> - -You can even chain calls to @all@ or @first@ to continue refining your query or search within a scope. See "Finders":/docs/find.html for more information. - -h2. Open Development - -DataMapper sports a very accessible code-base and a welcoming community. Outside contributions and feedback are welcome and encouraged, especially constructive criticism. Go ahead, fork DataMapper, we'd love to see what you come up with! - -Make your voice heard! "Submit a ticket or patch":http://datamapper.lighthouseapp.com/projects/20609-datamapper/, speak up on our "mailing-list":http://groups.google.com/group/datamapper/, chat with us on "irc":irc://irc.freenode.net/#datamapper, write a spec, get it reviewed, ask for commit rights. It's as easy as that to become a contributor. diff --git a/contribute.markdown b/contribute.markdown new file mode 100644 index 0000000..961e3d7 --- /dev/null +++ b/contribute.markdown @@ -0,0 +1,48 @@ +--- +layout: default +title: Contribute to DataMapper +created_at: Wed Aug 29 20:37:00 +0930 2007 +--- + +{{page.title}} +============== + +DataMapper is always looking for more contributors. When you've got an itch to +scratch, jump in and contribute! Write a few specs showing us how your code +works, create a patch and [submit it](http://datamapper.lighthouseapp.com/projects/20609-datamapper/) +as a new ticket or a fix for an existing one. After a few patches and many thanks, you'll +get commit access. + +We benchmark all but the most trivial of patches, because we care about +performance and you should too! + +What we need +------------ + +Currently DataMapper needs help in a few particular areas: + +* API Documentation (using the [YARD documentation style](http://github.com/lsegal/yard/)) +* Tutorials +* Code contributions +* Bug Reports + +Git - Edge DataMapper +--------------------- + +DataMapper development uses [Git](http://git.or.cz) SCM. Please see [using git](using-git.html) +to learn how to contribute. + +
    +
    $ git clone git://github.com/datamapper/dm-core.git
    +
    + + +Code Style Guidelines +--------------------- + +When contributing any code to DataMapper, please follow these guidelines. + +1. Spec first. Spec thoroughly. (DataMapper is written with [Rspec](http://rspec.info/)) +2. Parentheses around parameter lists for methods +3. Two space indent - not tabs! +4. Documentation is required (use the [YARD documentation style](http://github.com/lsegal/yard/)) diff --git a/content/css/coderay.css b/css/coderay.css similarity index 100% rename from content/css/coderay.css rename to css/coderay.css diff --git a/content/css/ie_hacks.css b/css/ie_hacks.css similarity index 100% rename from content/css/ie_hacks.css rename to css/ie_hacks.css diff --git a/content/css/site.css b/css/site.css similarity index 100% rename from content/css/site.css rename to css/site.css diff --git a/development/index.markdown b/development/index.markdown new file mode 100644 index 0000000..4eb097b --- /dev/null +++ b/development/index.markdown @@ -0,0 +1,35 @@ +--- +layout: default +title: Development +created_at: Mon Mar 17 01:37:12 -0500 2008 +--- + +Development +=========== + +DataMapper development has switched to the [Git](http://git.or.cz) SCM. Please +see [using git](/using-git.html) to learn how to contribute. +To check out "tip" DataMapper anonymously: + +{% highlight bash %} +git clone git://github.com/datamapper/extlib.git +git clone git://github.com/datamapper/dm-core.git +git clone git://github.com/datamapper/dm-more.git +{% endhighlight %} + +Or visit the [edge guide](/articles/stunningly_easy_way_to_live_on_the_edge.html). + +If you have a [github](http://www.github.com) account, log in, and _fork_ +[the repo](http://github.com/datamapper/dm-core/). +When you think you're ready, send dkubb a "pull request". + +Coding Conventions and Considerations +------------------------------------- + +When contributing any code to DataMapper, please follow these guidelines. + +* Spec first. Spec thoroughly. (DataMapper is written with [Rspec](http://rspec.info/)) +* Parentheses around parameter lists for methods +* Two space indent - not tabs! +* Write optimal code, not magic code +* Documentation is required (use the [official documentation style](/docs/)) diff --git a/content/docs/associations.txt b/docs/associations.markdown similarity index 58% rename from content/docs/associations.txt rename to docs/associations.markdown index 0b2b6c1..567fc38 100644 --- a/content/docs/associations.txt +++ b/docs/associations.markdown @@ -1,15 +1,18 @@ --- +layout: default title: Associations body_id: docs created_at: Tue Dec 04 14:46:32 +1030 2007 -filter: - - erb - - textile --- -h1. <%= @page.title %> +{{ page.title }} +================ -Associations are a way of declaring relationships between models, for example a blog Post "has many" Comments, or a Post belongs to an Author. They add a series of methods to your models which allow you to create relationships and retrieve related models along with a few other useful features. Which records are related to which are determined by their foreign keys. +Associations are a way of declaring relationships between models, for example a +blog Post "has many" Comments, or a Post belongs to an Author. They add a series +of methods to your models which allow you to create relationships and retrieve +related models along with a few other useful features. Which records are related +to which are determined by their foreign keys. The types of associations currently in DataMapper are: @@ -44,13 +47,17 @@ The types of associations currently in DataMapper are: -h2. Declaring Associations +Declaring Associations +---------------------- -This is done via declarations inside your model class. The class name of the related model is determined by the symbol you pass in. For illustration, we'll add an association of each type. Pay attention to the pluralization or the related model's name. +This is done via declarations inside your model class. The class name of the +related model is determined by the symbol you pass in. For illustration, we'll +add an association of each type. Pay attention to the pluralization or the +related model's name. -h3. has n and belongs_to (or One-To-Many) +### has n and belongs_to (or One-To-Many) -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> +{% highlight ruby linenos %} class Post include DataMapper::Resource @@ -63,11 +70,11 @@ class Comment belongs_to :post end -<% end %> +{% endhighlight %} -h3. has n, :through (or One-To-Many-Through) +### has n, :through (or One-To-Many-Through) -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> +{% highlight ruby linenos %} class Photo include DataMapper::Resource @@ -89,11 +96,11 @@ class Tagging belongs_to :photo end -<% end %> +{% endhighlight %} -h3. Has, and belongs to, many (Or Many-To-Many) +### Has, and belongs to, many (Or Many-To-Many) -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> +{% highlight ruby linenos %} class Article include DataMapper::Resource @@ -106,18 +113,20 @@ class Category has n, :articles, :through => Resource end -<% end %> +{% endhighlight %} -The use of Resource in place of a class name tells DataMapper to use an anonymous resource to link the two models up. +The use of Resource in place of a class name tells DataMapper to use an +anonymous resource to link the two models up. -h2. Adding To Associations +Adding To Associations +---------------------- Adding to associations, to add a comment to a post for example, is quite simple. -@build@ or @create@ can be called directly on the association, or an already -existing item can be appended to the association with @<<@ and then the item +`build` or `create` can be called directly on the association, or an already +existing item can be appended to the association with `<<` and then the item saved. -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> +{% highlight ruby linenos %} # Assume we set up comments and posts as before, as actual models. # find a post to add a comment to @@ -138,42 +147,55 @@ saved. # and save. @post.save -<% end %> +{% endhighlight %} -h2. Customizing Associations +Customizing Associations +------------------------ -The association declarations make certain assumptions about which classes are being related and the names of foreign keys based on some simple conventions. In some situations you may need to tweak them a little. The association declarations accept additional options to allow you to customize them as you need +The association declarations make certain assumptions about which classes are +being related and the names of foreign keys based on some simple conventions. In +some situations you may need to tweak them a little. The association +declarations accept additional options to allow you to customize them as you +need -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> +{% highlight ruby linenos %} class Post include DataMapper::Resource belongs_to :author, :class_name => 'User', :child_key => [ :post_id ] end -<% end %> +{% endhighlight %} -h2. Adding Conditions to Associations +Adding Conditions to Associations +--------------------------------- -If you want to order the association, or supply a scope, you can just pass in the options... +If you want to order the association, or supply a scope, you can just pass in +the options... -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> +{% highlight ruby linenos %} class Post include DataMapper::Resource has n, :comments, :order => [ :published_on.desc ], :rating.gte => 5 # Post#comments will now be ordered by published_on, and filtered by rating > 5. end -<% end %> +{% endhighlight %} -h2. Finders off Associations +Finders off Associations +------------------------ -When you call an association off of a model, internally DataMapper creates a Query object which it then executes when you start iterating or call @length@ off of. But if you instead call @.all@ or @.first@ off of the association and provide it the exact same arguments as a regular @all@ and @first@, it merges the new query with the query from the association and hands you back a requested subset of the association's query results. +When you call an association off of a model, internally DataMapper creates a +Query object which it then executes when you start iterating or call `length` +off of. But if you instead call `.all` or `.first` off of the association and +provide it the exact same arguments as a regular `all` and `first`, it merges +the new query with the query from the association and hands you back a requested +subset of the association's query results. In a way, it acts like a database view in that respect. -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> +{% highlight ruby linenos %} @post = Post.first @post.categories # returns the full association @post.categories.all(:limit => 10, :order => [ :name.asc ]) # return the first 10 categories ordered by name @post.categories(:limit => 10, :order => [ :name.asc ]) # alias for #all, you can pass in the options directly -<% end %> +{% endhighlight %} diff --git a/docs/callbacks.markdown b/docs/callbacks.markdown new file mode 100644 index 0000000..d8490fa --- /dev/null +++ b/docs/callbacks.markdown @@ -0,0 +1,98 @@ +--- +layout: default +title: Hooks (AKA Callbacks) +body_id: docs +created_at: Fri Nov 30 15:29:01 +1030 2007 +--- + +{{ page.title }} +================ + +DataMapper supports callbacks using an [aspect-oriented approach](http://en.wikipedia.org/wiki/Aspect_oriented). +You can define callbacks for any method as well as any class method arbitrarily. + +Adding Instance-Level Advice +---------------------------- + +To declare advice (callback) for a specific method, first define a new method to +be run when another is called, then define your point-cut. + +{% highlight ruby linenos %} +class Post + include DataMapper::Resource + + # ... key and properties here + + before :save, :categorize + + def categorize + # ... code here + end +end +{% endhighlight %} + +Alternatively, you can declare the advice during the point-cut by supplying a +block rather than a symbol representing a method. + +{% highlight ruby linenos %} +class Post + include DataMapper::Resource + + # ... key and properties here + + before :save do + # ... code here + end + +end +{% endhighlight %} + +Adding Class-Level Advice +------------------------- + +To install advice around a class method, use `before_class_method` or `after_class_method`: + +{% highlight ruby linenos %} +class Post + include DataMapper::Resource + + # ... key and properties here + + before_class_method :find, :prepare + + def self.prepare + # ... code here + end +end +{% endhighlight %} + +Class level advice does not have access to any resulting instances from the +class method, so they might not be the best fit for `after_create` or +`after_save`. + +Throw :halt, in the name of love... +----------------------------------- + +In order to abort advice and prevent the advised method from being called, throw `:halt` + +{% highlight ruby linenos %} +class Post + include DataMapper::Resource + + # ... key and properties here + + # This record will save properly + before :save do |post| + true + end + + # But it will not be destroyed + before :destroy do |post| + throw :halt + end +end +{% endhighlight %} + +Remember, if you `throw :halt` inside an `after` advice, the advised method will +have already ran and returned. Because of this, the `after` advice will be the +only thing halted. diff --git a/content/docs/create_and_destroy.txt b/docs/create_and_destroy.markdown similarity index 50% rename from content/docs/create_and_destroy.txt rename to docs/create_and_destroy.markdown index 23769b5..3464310 100644 --- a/content/docs/create_and_destroy.txt +++ b/docs/create_and_destroy.markdown @@ -1,17 +1,17 @@ --- +layout: default title: Create, Update, Save and Destroy body_id: docs created_at: Tue Dec 04 14:46:32 +1030 2007 -filter: - - erb - - textile --- -h1. <%= @page.title %> +{{ page.title }} +================ -To illustrate the various methods used in manipulating records, we'll create, save and destroy a record. +To illustrate the various methods used in manipulating records, we'll create, +save and destroy a record. -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> +{% highlight ruby linenos %} class Zoo include DataMapper::Resource @@ -21,37 +21,47 @@ class Zoo property :inception, DateTime property :open, Boolean, :default => false end -<% end %> +{% endhighlight %} -h2. Creating +Creating +-------- -Let's create a new instance of the model, update its properties and save it to the data store. Save will return true if the save succeeds, or false when something went wrong. +Let's create a new instance of the model, update its properties and save it to +the data store. Save will return true if the save succeeds, or false when +something went wrong. -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> +{% highlight ruby linenos %} zoo = Zoo.new zoo.attributes = { :name => 'The Glue Factory', :inception => Time.now } zoo.save -<% end %> +{% endhighlight %} -Pretty straight forward. In this example we've updated the attributes using the @#attributes=@ method, but there are multiple ways of setting the values of a model's properties. +Pretty straight forward. In this example we've updated the attributes using the +`#attributes=` method, but there are multiple ways of setting the values of a +model's properties. -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> +{% highlight ruby linenos %} zoo = Zoo.new(:name => 'Awesome Town Zoo') # Pass in a hash to the new method zoo.name = 'Dodgy Town Zoo' # Set individual property zoo.attributes = { :name => 'No Fun Zoo', :open => false } # Set multiple properties at once -<% end %> +{% endhighlight %} -You can also update a model's properties and save it with one method call. @#update_attributes@ will return true if the record saves, false if the save fails, exactly like the @#save@ method. +You can also update a model's properties and save it with one method call. +`#update_attributes` will return true if the record saves, false if the save +fails, exactly like the `#save` method. -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> +{% highlight ruby linenos %} zoo.update_attributes(:name => 'Funky Town Municipal Zoo') -<% end %> +{% endhighlight %} -h2. Destroy +Destroy +------- -To destroy a record, you simply call it's @#destroy!@ method. It will return true or false depending if the record is successfully deleted. Here is an example of finding an existing record then destroying it. +To destroy a record, you simply call it's `#destroy!` method. It will return +true or false depending if the record is successfully deleted. Here is an +example of finding an existing record then destroying it. -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> +{% highlight ruby linenos %} zoo = Zoo.get(5) zoo.destroy! #=> true -<% end %> +{% endhighlight %} diff --git a/docs/dm_more/dm-aggregates.markdown b/docs/dm_more/dm-aggregates.markdown new file mode 100644 index 0000000..415d0fe --- /dev/null +++ b/docs/dm_more/dm-aggregates.markdown @@ -0,0 +1,87 @@ +--- +layout: default +title: DM-Aggregates +created_at: 2008-09-06 14:11:54.160780 -05:00 +--- + +{{ page.title }} +================ + +DM-Aggregates provides a reporting API which offers aggregating functions like +`count`, `min`, `max`, `avg`, `sum`, and `aggregate`. + +### Setup + +Simply `require 'dm-aggregates'` somewhere before you connect to your data-store +and you're ready to go. + +### Count + +You can issue a totaling query with DM-Aggregates in a couple of ways. First, +you can build a totaling query off of your Resource: + +{% highlight ruby %} + Dragon.count(:homes_destroyed.lte => 12) +{% endhighlight %} + +Secondly, you can call `count` off of a pre-build query object. + +{% highlight ruby %} + Dragon.all(:age.lte => 3).count +{% endhighlight %} + +Your query will become a totaling query without having to retrieve the objects +and total on the retrieved array. These two approaches can be combined as well. + +{% highlight ruby %} + Dragon.all(:homes_destroyed.lte => 12).count(:rating => 'amateur') +{% endhighlight %} + +### Min, Max + +Minimum and Maximum values from fields in your data-store can be retrieved by +specifying which property you want to retrieve the value from. + +{% highlight ruby %} + Dragon.min(:homes_destroyed) + Dragon.max(:homes_destroyed) +{% endhighlight %} + +Further conditions to your min and max queries are simply supplied after the +property. + +{% highlight ruby %} + Dragon.min(:homes_destroyed, :battles_won.gte => 20) + Dragon.max(:homes_destroyed, :title.not => nil, :nickname.like => "%puff%") +{% endhighlight %} + +### Sum, Average + +Both `sum`, and `avg` work very similarly to `min` and `max`. + +{% highlight ruby %} + Dragon.sum(:toes_on_claw) + Dragon.sum(:toes_on_claw, :is_fire_breathing => true) + + Dragon.avg(:toes_on_claw) + Dragon.avg(:toes_on_claw, :is_fire_breathing => true) +{% endhighlight %} + +### Aggregate + +The `aggregate` method will let you combine all of the other aggregating methods +together to retrieve in one call to the data-store. DM-Aggregates adds in a few +extra symbol operators in order to enable this. + +{% highlight ruby %} + Dragon.aggregate(:all.count, :name.count, :toes_on_claw.min, + :toes_on_claw.max, :toes_on_claw.avg, :toes_on_claw.sum, + :is_fire_breathing) + # [ + # [ 1, 1, 3, 3, 3.0, 3, false ], + # [ 2, 1, 4, 5, 4.5, 9, true ] + # ] +{% endhighlight %} + +This will compile all of the aggregating queries together and return them in an +array of arrays. diff --git a/docs/dm_more/index.markdown b/docs/dm_more/index.markdown new file mode 100644 index 0000000..2249ccb --- /dev/null +++ b/docs/dm_more/index.markdown @@ -0,0 +1,208 @@ +--- +layout: default +title: DM More +created_at: 2008-08-30 19:16:25.492127 +01:00 +--- + +{{ page.title }} +================ + +DataMapper is intended to have a lean and minimalistic core, which provides the +minimum necessary features for an ORM. It's also designed to be easily +extensible, so that everything you want in an ORM can be added in with a minimum +of fuss. It does this through plugins, which provide everything from +automatically updated timestamps to factories for generating DataMapper +resources. The biggest collection of these plugins is in dm-more, which isn't to +say that there's anything wrong with plugins which aren't included in dm-more -- +it will never house all the possible plugins. + +This page gives an overview of the plugins available in dm-more, loosely +categorized by what type of plugin they are. + +Resource Plugins +---------------- + +These plugins modify the behavior of all resources in an application, adding new +functionality to them, or providing easier ways of doing things. + +### DM-Validations + +This provides validations for resources. The plugin both defines automatic +validations based on the properties specified and also allows assignment of +manual validations. It also supports contextual validation, allowing a resource +to be considered valid for some purposes but not others. + +### DM-Timestamps + +This defines callbacks on the common timestamp properties, making them +auto-update when the models are created or updated. The targeted properties are +`:created_at` and `:updated_at` for DateTime properties and `:created_on` and +`:updated_on` for Date properties. + +### DM-Aggregates + +This provides methods for database calls to aggregate functions such as `count`, +`sum`, `avg`, `max` and `min`. These aggregate functions are added to both +collections and Models. + +### DM-Types + +This provides several more allowable property types. `Enum` and `Flag` allow a +field to take a few set values. `URI`, `FilePath`, `Regexp`, `EpochTime` and +`BCryptHash` save database representations of the classes, restoring them on +retrieval. `Csv`, `Json` and `Yaml` store data in the field in the serial +formats and de-serialize them on retrieval. + +### DM-Serializer + +This provides '`to_*`' methods which take a resource and convert it to a serial +format to be restored later. Currently the plugin provides `to_xml`, `to_yaml` +and `to_json` + +### DM-Constraints + +This plugin provides foreign key constrains on has n relationships for Postgres +and MySQL adapters. + +### DM-Adjust + +This plugin allows properties on resources, collections and models to +incremented or decremented by a fixed amount. + +is Plugins +---------- + +These plugins make new functionality available to models, which can be accessed +via the `is` method, for example `is :list`. These make the models behave in new +ways. + +### DM-Is-List + +The model acts as an item on a list. It has a position, and there are methods +defined for moving it up or down the list based on this position. The position +can also be scoped, for example on a user id. + +### DM-Is-Tree + +The model acts as a node of a tree. It gains methods for querying parents and +children as well as all the nodes of the current generation, the trail of +ancestors to the root node and the root node itself. + +### DM-Is-Nested_Set + +The model acts as an item in a 'nested set'. This might be used for some kind of +categorization system, or for threaded conversations on a forum. The advantage +this has over a tree is that is easy to fetch all the descendants or ancestors +of a particular set in one query, not just the next generation. Added to a +nested set is more complex under the hood, but the plugin takes care of this for +you. + +### DM-Is-Versioned + +The model is versioned. When it is updated, instead of the previous version +being lost in the mists of time, it is saved in a subsidiary table, so that it +can be restored later if needed. + +### DM-Is-State_Machine + +The model acts as a state machine. Instead of a column being allowed to take any +value, it is used to track the state of the machine, which is updated through +events that cause transitions. For example, this might step a model through a +sign-up process, or some other complex task. + +### DM-Is-Remixable + +The model becomes 'remixable'. It can then be included (or remixed) in other +models, which defines a new table to hold the remixed model and can have other +properties or methods defined on it. It's something like class table inheritance +for relationships :) + +Adapters +-------- + +These plugins provide new adapters for different storage schemes, allowing them +to be used to store resources, instead of the more conventional relational +database store. + +### DM-CouchDB-Adapter + +An adapter for the JSON based document database couch-db. The adaptor has +support for both defining models backed by a couch-db store and also for +couch-db views. + +### DM-Rest-Adapter + +An adapter for a XML based REST-backed storage scheme. All the usual DataMapper +operations are performed as HTTP GETs, POSTs, UPDATEs and DELETEs, operating on +the URIs of the resources. + +Integration Plugins +------------------- + +These plugins are designed to ease integration with other libraries, currently +just web frameworks. + +### merb_datamapper + +Integration with the merb web framework. +The plugin takes care of setting up the DataMapper connection when the framework +starts, provides several useful rake tasks as well as generators for Models, +ResourceControllers and Migrations. + +### rails_datamapper + +Integration with Rails. It provides a +Model generator and also takes care of connecting to the data-store through +DataMapper. + +Utility Plugins +--------------- + +These provide useful functionality, though are unlikely to be used by every +project or assist more with development than production use. + +### DM-Sweatshop + +A model factory for DataMapper, supporting the creation of random models for +specing or to fill an application for development. Properties can be picked at +random or made to conform to a variety of regular expressions. dm-sweatshop also +understands has n relationships and can assign a random selection of child +models to a parent. + +### DM-Migrations + +Migrations for DataMapper, allowing modification of the database schema with +more control than `auto_migrate!` and `auto_upgrade!`. Migrations can be written +to create, modify and drop tables and columns. In addition, the plugin provides +support for specing migrations and verifying they perform as intended. + +### DM-Shorthand + +This plugin eases operations involving models across multiple repositories, +allowing wrapping in a `repository(:foo)` block to be replaced with a +`MyModel(:foo).some_method` call. + +### DM-Observer + +Observers watch other classes, doing things when certain operations are +performed on the remote class. This can be anything, but they are commonly used +for writing logs or notifying via email or xmpp when a critical operation has +occurred. + +### DM-CLI + +The `dm` executable is a DataMapper optimized version of `irb`. It automatically +connections to a data-store based on the arguments passed to it and supports +easy loading of DataMapper plugins, models from a directory as well as reading +connection information from a YAML configuration file. + +### DM-Querizer + +This provides alternate syntax for queries, replacing the hash which DataMapper +uses with a more 'ruby-ish' use of `&&`, `==` and `=~`. + +### DM-Ar_Finders + +ActiveRecord style syntax for DataMapper. This includes functionality such as +`find_by_name`, `find_or_create` and `find_all_by_title`. diff --git a/docs/dm_more/timestamps.markdown b/docs/dm_more/timestamps.markdown new file mode 100644 index 0000000..72f1547 --- /dev/null +++ b/docs/dm_more/timestamps.markdown @@ -0,0 +1,41 @@ +--- +layout: default +title: DM-Timestamps +created_at: 2008-09-06 14:11:54.160780 -05:00 +--- + +{{ page.title }} +================ + +DM-Timestamps provides automatic updates of `created_at` or `created_on` and +`updated_at` or `updated_on` properties for your resources. To use it, simply +`require 'dm-timestamps'` and assign one or all of these properties to your +Resource. + +Here's some basic usage. + +{% highlight ruby linenos %} +require 'rubygems' +require 'dm-core' +require 'dm-timestamps' + +DataMapper.setup(:default, 'sqlite3::memory:') + +class Post + include DataMapper::Resource + property :id, Serial + property :title, String, :length => 255 + + property :created_at, DateTime + property :created_on, Date + + property :updated_at, DateTime + property :updated_on, Date +end +{% endhighlight %} + +If you're familiar with the 'magic' properties from ActiveRecord, this is very +similar. When your model is initially saved, DM-Timestamps will update that +object's `created_at` and/or `created_on` fields with the time of creation. When +the object is edited and then saved out to the data-store, DM-Timestamps will +update the `updated_at` and `updated_on` fields with the time of update. diff --git a/docs/dm_more/types.markdown b/docs/dm_more/types.markdown new file mode 100644 index 0000000..37fbb1f --- /dev/null +++ b/docs/dm_more/types.markdown @@ -0,0 +1,152 @@ +--- +layout: default +title: DM-Types +created_at: 2008-09-06 18:30:16.740699 +01:00 +--- + +{{ page.title }} +================ + +Types are used by DataMapper to map ruby objects into values in the data-store +on saving and to translate the values back into ruby objects when a resource is +retrieved. The core library supplies several different types, providing mappings +for most of the simple ruby objects as direct values in the data-store as well +as the Object type which can store any Marshallable ruby object. + +They're intended to make the storage of ruby objects as transparent as possible, +no matter what the ruby object is, or which data-store is used as the backend. + +dm-types +-------- + +In [DM More](/docs/dm_more) there is the dm-types gem, which supplies several +more types that map less common ruby classes to data-store values or take care +of serializing them to text based formats. + +### Enum + +The Enum type uses an Integer create a property which can take one of a number +of symbolic values. A use for this might be bug status for an issue tracker or +the protocol being used in a packet logging application. + +{% highlight ruby linenos %} +class Issue + include DataMapper::Resource + + property :status, Enum[ :new, :open, :closed, :invalid ], :default => :new + # other properties ... +end + +@i = Issue.new +@i.status +#=> :new +@i.status = :open +#=> :open +{% endhighlight %} + +### Flag + +A Flag is similar to an Enum, though the property that is created can hold +multiple symbol values at once. This could be used for recording which colours a +product can be ordered in, or what extra features an account has enabled. + +{% highlight ruby linenos %} +class Widget + include DataMapper::Resource + + property :colours, Flag[ :red, :green, :blue, :heliotrope ] + + # other properties ... +end + +@w = Widget.new +@w.colours = [ :red, :heliotrope ] +{% endhighlight %} + +### Object Mappings + +These map objects into simple data-store primitives, then re-initialize as the +ruby types + +EpochTime +: A ruby Time object stored in the data-store as an Integer -- the number of + seconds since the UNIX epoch. + +URI +: A ruby URI object, pre-parsed for use of methods such as `#params` or `#uri`, + stored as a string in the data-store. + +FilePath +: Stored as a string in the data-store, FilePaths initialize Pathname objects, + making it easy to perform various file operations on the file. + +Regex +: A ruby regex, stored as a string. + +IPAddress +: Ruby IPAddr, stored as the string representation. + +BCryptHash +: Stored in the data-store as string representing the salt, hash and cost of a + password using OpenBSD's bcrypt algorithm, it offers an alternative to the + more usual pair of hash and salt columns. + +### Serializers + +These store values in the data-store using text based serialization formats. +They work via calling dumping the object to the format on saving and parsing the +text to reinitialize them on loading. + +* Csv +* Json +* Yaml + +Writing Your Own +---------------- + +Writing your own custom type isn't difficult. There are two (or perhaps three) +methods to implement, as well as the selection of an appropriate primitive. All +types are a class which should descend from `DataMapper::Type`. + +### The Primitive + +DataMapper offers several choices for a data-store primitive. + +* Integer +* Float +* String +* Date +* DateTime + +To assign a primitive to a type, either make the type descend from +`DataMapper::Type(PrimitiveClass)` or within the class definition, use +`primitive PrimitiveClass`. + +### dump + +A type's `self.dump(value, property)` method is called when the object is saved +to the data-store. It is responsible for mapping whatever is assigned to the +property on to the primitive type. For example, the EpochTime Type saves an +integer directly to the data-store, or calls `to_i()` if a Time object is passed +to it. + +### load + +The `self.load(value, property)` method is called when the property is retrieved +from the data-store. It takes the primitive value and initializes a ruby object +from it. For example, the Json type performs `JSON.parse(value)` to convert the +json string back into appropriate ruby. + +### typecast + +Typecasting is provided by the types `self.typecast(value, property)` method, +which tries to coerce whatever value the property has into an appropriate type. +A type doesn't have to provide a typecast but it can be useful, for example to +allow the string '2008-09-06' to be converted to a ruby Date without having to +reload the model. + +### Examples + +For examples of the types, the best place to look is dm-types on +github. diff --git a/content/docs/find.txt b/docs/find.markdown similarity index 58% rename from content/docs/find.txt rename to docs/find.markdown index 6470d3e..39e5c40 100644 --- a/content/docs/find.txt +++ b/docs/find.markdown @@ -1,21 +1,23 @@ --- +layout: default title: Finding Records body_id: docs created_at: Tue Dec 04 14:46:32 +1030 2007 -filter: - - erb - - textile --- -h1. <%= @page.title %> +{{ page.title }} +================ -The finder methods for DataMapper objects are defined in <%= doc('DataMapper::Repository') %>. They include @get()@, @all()@, @first()@ +The finder methods for DataMapper objects are defined in <%= +doc('DataMapper::Repository') %>. They include `get()`, `all()`, `first()` -h2. Finder Methods +Finder Methods +-------------- -DataMapper has methods which allow you to grab a single record by key, the first match to a set of conditions, or a collection of records matching conditions. +DataMapper has methods which allow you to grab a single record by key, the first +match to a set of conditions, or a collection of records matching conditions. -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> +{% highlight ruby linenos %} zoo = Zoo.get(1) # get the zoo with primary key of 1. zoo = Zoo.get!(1) # Or get! if you want an ObjectNotFoundError on failure zoo = Zoo.get('DFW') # wow, support for natural primary keys @@ -24,21 +26,22 @@ zoo = Zoo.first(:name => 'Luke') # first matching record with the name zoos = Zoo.all # all zoos zoos = Zoo.all(:open => true) # all zoos that are open zoos = Zoo.all(:opened_on => (s..e)) # all zoos that opened on a date in the date-range -<% end %> +{% endhighlight %} -h2. Scopes and Chaining +Scopes and Chaining +------------------- -A call to @all()@ or @first()@ can be chained together to further build a query to the data-store: +A call to `all()` or `first()` can be chained together to further build a query to the data-store: -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> +{% highlight ruby linenos %} all_zoos = Zoo.all open_zoos = all_zoos.all(:open => true) big_open_zoos = open_zoos.all(:animal_count => 1000) -<% end %> +{% endhighlight %} As a direct consequence, you can define scopes without any extra work in your model. -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> +{% highlight ruby linenos %} class Zoo # all the keys and property setup here def self.open @@ -50,24 +53,29 @@ class Zoo end big_open_zoos = Zoo.big.open -<% end %> +{% endhighlight %} -Scopes like this can even have arguments. Do anything in them, just ensure they return a Query of some kind. +Scopes like this can even have arguments. Do anything in them, just ensure they +return a Query of some kind. -h2. Conditions +Conditions +---------- -Rather than defining conditions using SQL fragments, we can actually specify conditions using a hash. +Rather than defining conditions using SQL fragments, we can actually specify +conditions using a hash. -The examples above are pretty simple, but you might be wondering how we can specify conditions beyond equality without resorting to SQL. Well, thanks to some clever additions to the Symbol class, it's easy! +The examples above are pretty simple, but you might be wondering how we can +specify conditions beyond equality without resorting to SQL. Well, thanks to +some clever additions to the Symbol class, it's easy! -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> +{% highlight ruby linenos %} exhibitions = Exhibition.all(:run_time.gt => 2, :run_time.lt => 5) # => SQL conditions: 'run_time > 1 AND run_time < 5' -<% end %> +{% endhighlight %} Valid symbol operators for the conditions are: -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> +{% highlight ruby linenos %} gt # greater than lt # less than gte # greater than or equal @@ -76,62 +84,68 @@ not # not equal eql # equal like # like in # in - will be used automatically when an array is passed in as an argument -<% end %> +{% endhighlight %} -h2. Order +Order +----- To specify the order in which your results are to be sorted, use: -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> +{% highlight ruby linenos %} @zoos_by_tiger_count = Zoo.all(:order => [ :tiger_count.desc ]) # in SQL => select * from zoos ORDER BY tiger_count DESC -<% end %> +{% endhighlight %} Available order vectors are: -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> +{% highlight ruby linenos %} asc # sorting ascending desc # sorting descending -<% end %> +{% endhighlight %} Once you have the query, the order can be modified too. Just call reverse: -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> +{% highlight ruby linenos %} @least_tigers_first = @zoos_by_tiger_count.reverse # in SQL => select * from zoos ORDER BY tiger_count ASC -<% end %> +{% endhighlight %} -h3. Compatibility +### Compatibility DataMapper supports other conditions syntaxes as well: -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> +{% highlight ruby linenos %} zoos = Zoo.all(:conditions => { :id => 34 }) zoos = Zoo.all(:conditions => [ "id = ?", 34 ]) # even mix and match zoos = Zoo.all(:conditions => { :id => 34 }, :name.like => '%foo%') -<% end %> +{% endhighlight %} -h2. Talking directly to your data-store +Talking directly to your data-store +----------------------------------- Sometimes you may find that you need to tweak a query manually. -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> +{% highlight ruby linenos %} zoos = repository(:default).adapter.query('SELECT name, open FROM zoos WHERE open = 1') # Note that this will not return Zoo objects, rather the raw data straight from the database -<% end %> +{% endhighlight %} -@zoos@ will be full of Struct objects with @name@, and @open@ attributes, rather than instances of the Zoo class. They'll also be read-only. You can still use the interpolated array condition syntax as well: +`zoos` will be full of Struct objects with `name`, and `open` attributes, rather +than instances of the Zoo class. They'll also be read-only. You can still use +the interpolated array condition syntax as well: -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> +{% highlight ruby linenos %} zoos = repository(:default).adapter.query('SELECT name, open FROM zoos WHERE name = ?', "Awesome Zoo") -<% end %> +{% endhighlight %} -h2. Counting +Counting +-------- -With DM-More's DM-Aggregates included, the @count@ method it adds will returns an integer of the number of records matching the every condition you pass in. +With DM-More's DM-Aggregates included, the `count` method it adds will returns +an integer of the number of records matching the every condition you pass in. -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> +{% highlight ruby linenos %} count = Zoo.count(:age.gt => 200) #=> 2 -<% end %> +{% endhighlight %} diff --git a/content/docs/index.txt b/docs/index.markdown similarity index 84% rename from content/docs/index.txt rename to docs/index.markdown index 1de40c6..c3e330d 100644 --- a/content/docs/index.txt +++ b/docs/index.markdown @@ -1,13 +1,12 @@ --- +layout: default title: Documentation body_id: docs created_at: Fri Nov 30 15:29:01 +1030 2007 -filter: - - erb - - textile --- -h1. <%= @page.title %> +{{ page.title }} +================
    API
    @@ -36,15 +35,19 @@ h1. <%= @page.title %>
    Plugins galore for DataMapper.
    -h1. Documentarians Wanted! +Documentarians Wanted! +---------------------- -Want to help DataMapper, but don't know where to start? A great way to contribute is to help out in the documentation effort. +Want to help DataMapper, but don't know where to start? A great way to +contribute is to help out in the documentation effort. -h2. Documentation Style +Documentation Style +------------------- -DataMapper uses the YARD standard for RDoc documentation. Here's a brief sample from the internals of DM-Core: +DataMapper uses the YARD standard for RDoc documentation. Here's a brief sample +from the internals of DM-Core: -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> +{% highlight ruby linenos %} ## # Setups up a connection to a data-store # @@ -66,6 +69,7 @@ DataMapper uses the YARD standard for RDoc documentation. Here's a brief sample def self.setup(name, uri_or_options) raise ArgumentError, "+name+ must be a Symbol, but was #{name.class}", caller unless Symbol === name ... -<% end %> +{% endhighlight %} -For more information about the YARD documentation style, see the YARD "Getting Started":http://yard.soen.ca/getting_started document. +For more information about the YARD documentation style, see the YARD +[Getting Started](http://yard.soen.ca/getting_started) document. diff --git a/docs/install.markdown b/docs/install.markdown new file mode 100644 index 0000000..d9fb089 --- /dev/null +++ b/docs/install.markdown @@ -0,0 +1,55 @@ +--- +layout: default +title: Installation Issues +body_id: docs +created_at: Tue Dec 04 13:20:00 +1030 2007 +--- + +{{ page.title }} +================ + +If you've followed the [install instructions](/getting-started.html) but run into +problems, you can find some tips below. + +

    Windows Users

    + +

    At present, [DataObjects](http://rubyforge.org/projects/dorb/) +does not run well on Windows natively
    and will require you to install cygwin +or another linux-like
    environment. People have been able to get it installed +and running
    on Windows but with severe drops in performance. This is a known +
    issue and we're working on it.

    + +Dependencies +------------ + +First port of call if you're having issues with an installation is to make sure +you have all the dependencies installed. Rubygems should take care of this for +you, but just in case, make sure you have the following gems as well: + +* fastthread +* json +* rspec - for running specs on DataMapper itself + +Using Trunk +----------- + +You will also need to install the DataObject gem and the adaptor for your +platform + +{% highlight bash linenos %} +sudo gem install data_objects +sudo gem install do_mysql +{% endhighlight %} + +The current database adaptors are: + +* do_mysql +* do_sqlite3 +* do_postgres + +Getting Help +------------ + +If you still have issues, we suggest getting onto the [mailing list](http://groups.google.com/group/datamapper) +or the [IRC channel](irc://irc.freenode.net/#datamapper) and asking around. There's friendly +people there to help you out. diff --git a/docs/misc.markdown b/docs/misc.markdown new file mode 100644 index 0000000..b723596 --- /dev/null +++ b/docs/misc.markdown @@ -0,0 +1,110 @@ +--- +layout: default +title: Miscellaneous Features +created_at: Thu Mar 20 23:26:54 -0500 2008 +--- + +{{ page.title }} +================ + +DataMapper comes loaded features, many of which other ORMs require external +libraries for. + +Single Table Inheritance +------------------------ + +Many ORMs support Single Table Inheritance and DataMapper is no different. In +order to declare a model for Single Table Inheritance, define a property with +the data-type of <%= doc('Types::Discriminator') %> + +{% highlight ruby linenos %} +class Person + include DataMapper::Resource + + property :name, String + property :job, String, :length => 255 + property :type, Discriminator + ... +end + +class Male < Person; end +class Father < Male; end +class Son < Male; end + +class Woman < Person; end +class Mother < Woman; end +class Daughter < Woman; end + +{% endhighlight %} + +When DataMapper sees your `type` column declared as type <%= +doc('Types::Discriminator') %>, it will automatically insert the class name of +the object you've created and later instantiate that row as that class. It also +supports deep inheritance, so doing Woman.all will select all women, mothers, +and daughters (and deeper inherited classes if they exist). + +Paranoia +-------- + +Sometimes...most times...you don't _really_ want to destroy a row in the +database, you just want to mark it as deleted so that you can restore it later +if need be. This is aptly-named Paranoia and DataMapper has basic support for +this baked right in. Just declare a property and assign it a type of <%= +doc('Types::ParanoidDateTime') %> or <%= doc('Types::ParanoidBoolean') %>: + +{% highlight ruby linenos %} +property :deleted_at, ParanoidDateTime +{% endhighlight %} + +Multiple Data-Store Connections +------------------------------- + +DataMapper sports a concept called a context which encapsulates the data-store +context in which you want operations to occur. For example, when you setup a +connection in [getting-started](/getting-started.html), you were defining a +context known as `:default` + +{% highlight ruby linenos %} + DataMapper.setup(:default, 'mysql://localhost/dm_core_test') +{% endhighlight %} + +But if you supply a context name, you will now have 2 database contexts with +their own unique loggers, connection pool, identity map....one default context +and one named context. + +{% highlight ruby linenos %} +DataMapper.setup(:external, 'mysql://someother_host/dm_core_test') +{% endhighlight %} + +To use one context rather than another, simply wrap your code block inside a +`database` call. It will return whatever your block of code returns. + +{% highlight ruby linenos %} +repository(:external) { Person.first } +# hits up your :external database and retrieves the first Person +{% endhighlight %} + +This will use your connection to the `:external` data-store and the first Person +it finds. Later, when you call `.save` on that person, it'll get saved back to +the `:external` data-store; An object is aware of what context it came from and +should be saved back to. + +Chained Associations +-------------------- + +Say you want to find all of the animals in a zoo, but Animal belongs to Exhibit +which belongs to Zoo. Other ORMs solve this problem by providing a means to +describe the double JOINs into the retrieval call for Animals. ActiveRecord +specifically will let you specify JOINs in a hash-of-hashes syntax which will +make most developers throw up a little in their mouths. + +DataMapper's solution is to let you chain association calls: + +{% highlight ruby linenos %} +zoo = Zoo.first +zoo.exhibits.animals # retrieves all animals for all exhibits for that zoo +{% endhighlight %} + +This has great potential for browsing collections of content, like browsing all +blog posts' comments by category or tag. At present, chaining beyond 2 +associations is still experimental. diff --git a/docs/properties.markdown b/docs/properties.markdown new file mode 100644 index 0000000..d44dc58 --- /dev/null +++ b/docs/properties.markdown @@ -0,0 +1,232 @@ +--- +layout: default +title: Properties +body_id: docs +created_at: Tue Dec 04 13:27:16 +1030 2007 +--- + +{{ page.title }} +================ + +A model's properties are not introspected from the fields in the data-store; In +fact the reverse happens. You declare the properties for a model inside it's +class definition, which is then used to generate the fields in the data-store. + +This has a few advantages. First it means that a model's properties are +documented in the model itself, not a migration or XML file. If you've ever been +annoyed at having to look in a schema file to see the list of properties and +types for a model, you'll find this particularly useful. There's no need for a +special `annotate` rake task either. + +Second, it lets you limit access to properties using Ruby's access semantics. +Properties can be declared public, private or protected. They are public by +default. + +Finally, since DataMapper only cares about properties explicitly defined in your +models, DataMapper plays well with legacy data-stores and shares them easily +with other applications. + +Declaring Properties +-------------------- + +Inside your class, call the property method for each property you want to add. +The only two required arguments are the name and type, everything else is +optional. + +{% highlight ruby linenos %} +class Post + include DataMapper::Resource + + property :id, Serial # primary serial key + property :title, String, :nullable => false # Cannot be null + property :published, Boolean, :default => false # Default value for new records is false +end +{% endhighlight %} + +Keys +---- + +### Primary Keys + +Primary keys are not automatically created for you, as with ActiveRecord. You +MUST configure at least one key property on your data-store. More often than +not, you'll want an auto-incrementing integer as a primary key, so DM has a +shortcut: + +{% highlight ruby linenos %} + property :id, Serial +{% endhighlight %} + +### Natural Keys + +Anything can be a key. Just pass `:key => true` as an option during the property +definition. Most commonly, you'll see String as a natural key: + +{% highlight ruby linenos %} + property :slug, String, :key => true # any Type is available here +{% endhighlight %} + +Natural Keys are protected against mass-assignment, so their `setter=` will need +to be called individually if your looking to set them. + +*Fair warning:* Using Boolean, Discriminator, and the time related types as keys +may cause your DBA to hunt you down and "educate" you. DM will not be held +responsible for any injuries or death that may result. + +### Composite Keys + +You can have more than one property in the primary key: + +{% highlight ruby linenos %} +class Post + include DataMapper::Resource + + property :old_id, Integer, :key => true + property :new_id, Integer, :key => true +end +{% endhighlight %} + +Setting Defaults +---------------- + +Defaults can be set via the `:default` key for a property. They can be static +values, such as `12` or `"Hello"`, but DataMapper also offers the ability to use +a Proc to set the default value. The property becomes whatever the Proc returns, +which will be called the first time the property is used without having first +set a value. The Proc itself receives two arguments: The resource the property +is being set on, and the property itself. + +{% highlight ruby linenos %} +class Image + include DataMapper::Resource + + property :id, Serial + property :path, FilePath, :nullable => false + property :md5sum, String, :length => 32, :default => lambda { |r, p| Digest::MD5.hexdigest(r.path.read) if r.path } +end +{% endhighlight %} + +When creating the resource, or the first time the `md5sum` property is accessed, +it will be set to the hex digest of the file referred to by `path`. + +*Fair Warning*: A property default must _not_ refer to the value of the property +it is about to set, or there will be an infinite loop. + +Lazy Loading +------------ + +Properties can be configured to be lazy loaded. A lazily loaded property is not +requested from the data-store by default. Instead it is only loaded when it's +accessor is called for the first time. This means you can stop default queries +from being greedy, a particular problem with text fields. Text fields are lazily +loaded by default, which you can over-ride if you need to. + +{% highlight ruby linenos %} +class Post + include DataMapper::Resource + + property :id, Serial + property :title, String + property :body, Text # Is lazily loaded by default + property :notes, Text, :lazy => false # Isn't lazy, will load by default +end +{% endhighlight %} + +Lazy Loading can also be done via contexts, which let you group lazily loaded +properties together, so that when one is fetched, all the associated ones will +be as well, cutting down on trips to the data-store. + +{% highlight ruby linenos %} +class Post + include DataMapper::Resource + + property :id, Serial + property :title, String + property :subtitle, String :lazy => [ :show ] + property :body, Text :lazy => [ :show ] + property :views, Integer, :lazy => [ :show ] + property :summary, Text +end +{% endhighlight %} + +In this example, only the title (and the id, of course) will be loaded from the +data-store on a `Post.all()`. But as soon as the value for subtitle, body or +views are called, all three will be loaded at once, since they're members of the +`:show` group. The summary property on the other hand, will only be fetched when +it is asked for. + +Available Types +--------------- + +DM-Core supports the following 'primitive' data-types. + +* Boolean +* String +* Text +* Float +* Integer +* BigDecimal, +* DateTime, Date, Time +* Object, (marshalled) +* Discriminator + +If you include DM-Types, the following data-types are supported: + +* Csv +* Enum +* EpochTime +* FilePath +* Flag +* IPAddress +* URI +* Yaml +* Json +* BCryptHash +* Regex + +Limiting Access +--------------- + +Access for properties is defined using the same semantics as Ruby. Accessors are +public by default, but you can declare them as private or protected if you need +to. You can set access using the `:accessor` option. For demonstration, we'll +reopen our Post class. + +{% highlight ruby linenos %} +class Post + property :title, String, :accessor => :private # Both reader and writer are private + property :body, Text, :accessor => :protected # Both reader and writer are protected +end +{% endhighlight %} + +You also have more fine grained control over how you declare access. You can, +for example, have a public reader and private writer by using the `:writer` and +`:reader` options. (Remember, the default is Public) + +{% highlight ruby linenos %} +class Post + property :title, String, :writer => :private # Only writer is private + property :tags, String, :reader => :protected # Only reader is protected +end +{% endhighlight %} + +Over-riding Accessors +--------------------- + +When a property has declared accessors for getting and setting, it's values are +added to the model. Just like using `attr_accessor`, you can over-ride these +with your own custom accessors. It's a simple matter of adding an accessor after +the property declaration. Reopening the Post class.... + +{% highlight ruby linenos %} +class Post + property :slug, String + + def slug=(new_slug) + raise ArgumentError if new_slug != 'DataMapper is Awesome' + attribute_set(:slug, new_title) # use attribute_set instead of talking + # to the @ivars directly. + # This tracks dirtiness. + end +end +{% endhighlight %} diff --git a/content/docs/validations.txt b/docs/validations.markdown similarity index 51% rename from content/docs/validations.txt rename to docs/validations.markdown index 8de6dfd..6396727 100644 --- a/content/docs/validations.txt +++ b/docs/validations.markdown @@ -1,26 +1,33 @@ --- +layout: default title: Validations body_id: docs created_at: Fri Jun 13 17:41:32 +1030 2007 -filter: - - erb - - textile --- -h1. <%= @page.title %> +{{ page.title }} +================ -DataMapper validations allow you to vet data prior to saving to a database. To make validations available to your app you simply '@require "dm-validations"@' in your application. With DataMapper there are two different ways you can validate your classes' properties. +DataMapper validations allow you to vet data prior to saving to a database. To +make validations available to your app you simply '`require "dm-validations"`' +in your application. With DataMapper there are two different ways you can +validate your classes' properties. -h2. Manual Validation +Manual Validation +----------------- -Much like a certain other Ruby ORM we can call validation methods directly by passing them a property name (or an array of names) to validate against. +Much like a certain other Ruby ORM we can call validation methods directly by +passing them a property name (or an array of names) to validate against. -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> +{% highlight ruby linenos %} validates_length :name validates_length [ :name, :description ] -<% end %> +{% endhighlight %} -These are the currently available manual validations available. Please refer to the API for more detailed information. +These are the currently available manual validations available. Please refer to +the API +for more detailed information. * validates_present * validates_absent @@ -34,13 +41,15 @@ These are the currently available manual validations available. Please refer to * validates_is_unique * validates_within -h2. Auto-Validations +Auto-Validations +---------------- -By adding triggers to your property definitions you can both define and validate your classes properties all in one fell swoop. +By adding triggers to your property definitions you can both define and validate +your classes properties all in one fell swoop. Triggers that generate validator creation: -<% coderay(:lang => "ruby") do -%> +{% highlight ruby %} # implicitly creates a validates_present :nullable => false :length => (1..n) @@ -56,12 +65,12 @@ Triggers that generate validator creation: :format => /\w+_\w+/ :format => lambda {|str| str } :format => Proc.new { |str| str } -<% end %> +{% endhighlight %} Here we see an example of a class with both a manual and and auto-validation declared: -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> +{% highlight ruby linenos %} require 'dm-validations' class Account @@ -74,21 +83,30 @@ Here we see an example of a class with both a manual and and auto-validation dec property :content, Text, :length => (100..500) end -<% end %> +{% endhighlight %} -h2. Validating +Validating +---------- -DataMapper validations, when included, alter the default save/create/update process for a model. +DataMapper validations, when included, alter the default save/create/update +process for a model. -You may manually validate a resource using the @valid?@ method, which will return true if the resource is valid, and false if it is invalid. +You may manually validate a resource using the `valid?` method, which will +return true if the resource is valid, and false if it is invalid. -In addition to the @valid?@ method, there is also an @all_valid?@ method that recursively walks both the current object and its associated objects and returns a comprehensive true/false result for the entire walk. If anything returns @false@, @all_valid?@ will return @false@ +In addition to the `valid?` method, there is also an `all_valid?` method that +recursively walks both the current object and its associated objects and returns +a comprehensive true/false result for the entire walk. If anything returns +`false`, `all_valid?` will return `false` -h2. Working with Validation Errors +Working with Validation Errors +------------------------------ -If your validators find errors in your model, they will populate the <%= doc('Validate::ValidationErrors') %> object that is available through each of your models via calls to your model's @errors@ method. +If your validators find errors in your model, they will populate the <%= +doc('Validate::ValidationErrors') %> object that is available through each of +your models via calls to your model's `errors` method. -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> +{% highlight ruby linenos %} my_account = Account.new(:name => "Jose") if my_account.save # my_account is valid and has been saved @@ -97,45 +115,64 @@ If your validators find errors in your model, they will populate the <%= doc('Va puts e end end -<% end %> +{% endhighlight %} -h2. Error Messages +Error Messages +-------------- -The error messages for validations provided by DataMapper are generally clear, and explain exactly what has gone wrong. If they're not what you want though, they can be changed. This is done via providing a @:message@ in the options hash, for example: +The error messages for validations provided by DataMapper are generally clear, +and explain exactly what has gone wrong. If they're not what you want though, +they can be changed. This is done via providing a `:message` in the options +hash, for example: -<% coderay(:lang => "ruby") do -%> +{% highlight ruby %} validates_is_unique :title, :scope => :section_id, :message => "There's already a page of that title in this section" -<% end %> +{% endhighlight %} -This example also demonstrates the use of the @:scope@ option to only check the property's uniqueness within a narrow scope. This object won't be valid if another object with the same @section_id@ already has that title. +This example also demonstrates the use of the `:scope` option to only check the +property's uniqueness within a narrow scope. This object won't be valid if +another object with the same @section_id@ already has that title. -Something similar can be done for auto-validations, too, via setting @:messages@ in the property options. +Something similar can be done for auto-validations, too, via setting `:messages` +in the property options. -<% coderay(:lang => "ruby") do -%> +{% highlight ruby %} property :email, String, :nullable => false, :unique => true, :format => :email_address, :messages => { :presence => "We need your email address.", :is_unique => "We already have that email.", :format => "Doesn't look like an email address to me ..." } -<% end %> +{% endhighlight %} -To set an error message on an arbitrary field of the model, DataMapper provides the @add@ command. +To set an error message on an arbitrary field of the model, DataMapper provides +the `add` command. -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> +{% highlight ruby linenos %} @resource.errors.add(:title, "Doesn't mention DataMapper") -<% end %> +{% endhighlight %} This is probably of most use in custom validations, so ... -h2. Custom Validations +Custom Validations +------------------ -DataMapper provides a number of validations for various common situations such as checking for the length or presence of strings, or that a number falls in a particular range. Often this is enough, especially when validations are combined together to check a field for a number of properties. For the situations where it isn't, DataMapper provides a couple of methods: @validates_with_block@ and @validates_with_method@. They're very similar in operation, with one accepting a block as the argument and the other taking a symbol representing a method name. +DataMapper provides a number of validations for various common situations such +as checking for the length or presence of strings, or that a number falls in a +particular range. Often this is enough, especially when validations are combined +together to check a field for a number of properties. For the situations where +it isn't, DataMapper provides a couple of methods: `validates_with_block` and +`validates_with_method`. They're very similar in operation, with one accepting a +block as the argument and the other taking a symbol representing a method name. -The method or block performs the validation tests and then should return @true@ if the resource is valid or @false@ if it is invalid. If the resource isn't valid instead of just returning @false@, an array containing @false@ and an error message, such as @[ false, "FAIL!" ]@ can be returned. This will add the message to the @errors@ on the resource. +The method or block performs the validation tests and then should return `true` +if the resource is valid or `false` if it is invalid. If the resource isn't +valid instead of just returning `false`, an array containing `false` and an +error message, such as `[ false, "FAIL!" ]` can be returned. This will add the +message to the `errors` on the resource. -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> +{% highlight ruby linenos %} class WikiPage include DataMapper::Resource @@ -155,23 +192,37 @@ The method or block performs the validation tests and then should return @true@ end end end -<% end %> +{% endhighlight %} -Instead of setting an error on the whole resource, you can set an error on an individual property by passing this as the first argument to @validates_with_block@ or @validates_with_method@. To use the previous example, replacing line 5 with: +Instead of setting an error on the whole resource, you can set an error on an +individual property by passing this as the first argument to +`validates_with_block` or `validates_with_method`. To use the previous example, +replacing line 5 with: -<% coderay(:lang => "ruby") do -%> +{% highlight ruby %} validates_with_method :body, :method => :check_citations -<% end %> +{% endhighlight %} -This would result in the citations error message being added to the error messages for the body, which might improve how it is presented to the user. +This would result in the citations error message being added to the error +messages for the body, which might improve how it is presented to the user. -h2. Conditional Validations +Conditional Validations +----------------------- -Validations don't always have to be run. For example, an issue tracking system designed for git integration might require a commit identifier for the fix--but only for a ticket which is being set to 'complete'. A new, open or invalid ticket, of course, doesn't necessarily have one. To cope with this situation and others like it, DataMapper offers conditional validation, using the @:if@ and @:unless@ clauses on a validation. +Validations don't always have to be run. For example, an issue tracking system +designed for git integration might require a commit identifier for the fix--but +only for a ticket which is being set to 'complete'. A new, open or invalid +ticket, of course, doesn't necessarily have one. To cope with this situation and +others like it, DataMapper offers conditional validation, using the `:if` and +`:unless` clauses on a validation. -@:if@ and @:unless@ take as their value a symbol representing a method name or a Proc. The associated validation will run only if (or unless) the method or Proc returns something which evaluates to @true@. The chosen method should take no arguments, whilst the Proc will be called with a single argument, the resource being validated. +`:if` and `:unless` take as their value a symbol representing a method name or a +Proc. The associated validation will run only if (or unless) the method or Proc +returns something which evaluates to `true`. The chosen method should take no +arguments, whilst the Proc will be called with a single argument, the resource +being validated. -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> +{% highlight ruby linenos %} class Ticket include DataMapper::Resource @@ -183,25 +234,42 @@ Validations don't always have to be run. For example, an issue tracking system validates_present :commit, :if => Proc.new {|t| t.status == :complete } end -<% end %> +{% endhighlight %} -The autovalidation that requires the title to be present will always run, but the validates_present on the commit hash will only run if the status is @:complete@. Another example might be a change summary that is only required if the resource is already there--'initial commit' is hardly an enlightening message. +The autovalidation that requires the title to be present will always run, but +the validates_present on the commit hash will only run if the status is +`:complete`. Another example might be a change summary that is only required if +the resource is already there--'initial commit' is hardly an enlightening +message. -<% coderay(:lang => "ruby") do -%> +{% highlight ruby %} validates_length :change_summary, :min => 10, :unless => :new_record? -<% end %> +{% endhighlight %} Sometimes a simple on and off switch is not enough, and so ... -h2. Contextual Validations +Contextual Validations +---------------------- -DataMapper Validations also provide a means of grouping your validations into contexts. This enables you to run different sets of validations under different contexts. All validations are performed in a context, even the auto-validations. This context is the @:default@ context. Unless you specify otherwise, any validations added will be added to the @:default@ context and the @valid?@ method checks all the validations in this context. +DataMapper Validations also provide a means of grouping your validations into +contexts. This enables you to run different sets of validations under different +contexts. All validations are performed in a context, even the auto-validations. +This context is the `:default` context. Unless you specify otherwise, any +validations added will be added to the `:default` context and the `valid?` +method checks all the validations in this context. -One example might be differing standards for saving a draft version of an article, compared with the full and ready to publish article. A published article has a title, a body of over 1000 characters, and a sidebar picture. A draft article just needs a title and some kind of body. The length and the sidebar picture we can supply later. There's also a @published@ property, which is used as part of queries to select articles for public display. +One example might be differing standards for saving a draft version of an +article, compared with the full and ready to publish article. A published +article has a title, a body of over 1000 characters, and a sidebar picture. A +draft article just needs a title and some kind of body. The length and the +sidebar picture we can supply later. There's also a `published` property, which +is used as part of queries to select articles for public display. -To set a context on a validation, we use the @:when@ option. It might also be desirable to set @:auto_validation => false@ on the properties concerned, especially if we're messing with default validations. +To set a context on a validation, we use the `:when` option. It might also be +desirable to set `:auto_validation => false` on the properties concerned, +especially if we're messing with default validations. -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> +{% highlight ruby linenos %} class Article include DataMapper::Resource @@ -263,15 +331,23 @@ To set a context on a validation, we use the @:when@ option. It might also be d @article.save(:publish) #=> true # we can save it just fine as a published article though. -<% end %> +{% endhighlight %} -That was a long example, but it shows how to set up validations in differing contexts and also how to save in a particular context. *One thing to be careful of when saving in a context is to make sure that any database level constraints, such as a @NOT NULL@ column definition in a database, are checked in that context, or a data-store error may ensue.* +That was a long example, but it shows how to set up validations in differing +contexts and also how to save in a particular context. *One thing to be careful +of when saving in a context is to make sure that any database level constraints, +such as a `NOT NULL` column definition in a database, are checked in that +context, or a data-store error may ensue.* -h2. Setting Properties Before Validation +Setting Properties Before Validation +------------------------------------ -It is sometimes necessary to set properties before a resource is saved or validated. Perhaps a required property can have a default value set from other properties or derived from the environment. To set these properties, a @before :valid?@ hook should be used. +It is sometimes necessary to set properties before a resource is saved or +validated. Perhaps a required property can have a default value set from other +properties or derived from the environment. To set these properties, a `before :valid?` +hook should be used. -<% coderay(:lang => "ruby", :line_numbers => "inline") do -%> +{% highlight ruby linenos %} class Article include DataMapper::Resource @@ -287,6 +363,8 @@ It is sometimes necessary to set properties before a resource is saved or valida self.permalink = title.gsub(/\s+/,'-') end end -<% end %> +{% endhighlight %} -Be careful not to @save@ your resource in these kinds of methods, or your application will spin off into infinite trying to save your object while saving your object. +Be careful not to `save` your resource in these kinds of methods, or your +application will spin off into infinite trying to save your object while saving +your object. diff --git a/getting-started.markdown b/getting-started.markdown new file mode 100644 index 0000000..c429d99 --- /dev/null +++ b/getting-started.markdown @@ -0,0 +1,172 @@ +--- +layout: default +page_id: gettingStarted +title: Getting started with DataMapper +created_at: Wed Aug 29 20:36:53 +0930 2007 +--- + +{{page.title}} +============== + +First, if you think you might need some help, there's an active community +supporting DataMapper through +[the mailing list](http://groups.google.com/group/datamapper) and the `#datamapper` IRC +channel on irc.freenode.net. + +So lets imagine we're setting up some models for a blogging app. We'll keep it +nice and simple. The first thing to decide on is what models we want. Post is a +given. So is Comment. But let's mix it up and do Category too. + +Install DataMapper +------------------ + +If you have RubyGems installed, pop open your console and install a few things. + +{% highlight ruby %} +gem install dm-core +{% endhighlight %} + +If you are planning on using DataMapper with a database, install a database +driver from the DataObjects project: (Substitute `do_sqlite3` with `do_postgres` +or `do_sqlite3` depending on your preferences) + +{% highlight ruby %} +gem install do_sqlite3 +{% endhighlight %} + +Require it in your application +------------------------------ + +{% highlight ruby %} +require 'rubygems' +require 'dm-core' +{% endhighlight %} + +Specify your database connection +-------------------------------- + +You need make sure this is set before you define your models. + +{% highlight ruby %} + # An in-memory Sqlite3 connection: + DataMapper.setup(:default, 'sqlite3::memory:') + + # A MySQL connection: + DataMapper.setup(:default, 'mysql://localhost/the_database_name') + + # A Postgres connection: + DataMapper.setup(:default, 'postgres://localhost/the_database_name') +{% endhighlight %} + +Define your models +------------------ + +The Post model is going to need to be persistent, so we'll include <%= +doc('DataMapper::Resource') %>. The convention with model names is to use the +singular, not plural version...but that's just the convention, you can do +whatever you want. + +{% highlight ruby %} +class Post + include DataMapper::Resource + + property :id, Serial + property :title, String + property :body, Text + property :created_at, DateTime +end + +class Comment + include DataMapper::Resource + + property :id, Serial + property :posted_by, String + property :email, String + property :url, String + property :body, Text +end + +class Category + include DataMapper::Resource + + property :id, Serial + property :name, String +end +{% endhighlight %} + +The above example is simplified, but you can also specify more options such as +constraints for your properties. + +Associations +------------ + +Ideally, these declarations should be done inside your class definition with the +properties and things, but for demonstration purposes, we’re just going to crack +open the classes. + +### One To Many + +Posts can have comments, so we’ll need to setup a simple one-to-many association +between then: + +{% highlight ruby %} +class Post + has n, :comments +end + +class Comment + belongs_to :post +end +{% endhighlight %} + +### Has and belongs to many + +Has and belongs to many Categories can have many Posts and Posts can have many +Categories, so we’ll need a many to many relationships commonly referred to “has +and belongs to many”. We’ll setup a quick model to wrap our join table between +the two so that we can record a little bit of meta-data about when the post was +categorized into a category. + +{% highlight ruby %} + +class Categorization + include DataMapper::Resource + + property :id, Serial + property :created_at, DateTime + + belongs_to :category + belongs_to :post +end + +# Now we re-open our Post and Categories classes to define associations +class Post + has n, :categorizations + has n, :categories, :through => :categorizations +end + +class Category + has n, :categorizations + has n, :posts, :through => :categorizations +end + +{% endhighlight %} + +Set up your database tables +--------------------------- + +{% highlight ruby %} +Post.auto_migrate! +Category.auto_migrate! +Comment.auto_migrate! +Categorization.auto_migrate! +{% endhighlight %} + +This will issue the necessary `CREATE` statements to define each storage according +to their properties. + +You could also do: + +{% highlight ruby %} +DataMapper.auto_migrate! +{% endhighlight %} diff --git a/content/images/avatar_test.gif b/images/avatar_test.gif similarity index 100% rename from content/images/avatar_test.gif rename to images/avatar_test.gif diff --git a/content/images/bug.gif b/images/bug.gif similarity index 100% rename from content/images/bug.gif rename to images/bug.gif diff --git a/content/images/content_bkg.gif b/images/content_bkg.gif similarity index 100% rename from content/images/content_bkg.gif rename to images/content_bkg.gif diff --git a/content/images/download_button.gif b/images/download_button.gif similarity index 100% rename from content/images/download_button.gif rename to images/download_button.gif diff --git a/content/images/footer_bkg.gif b/images/footer_bkg.gif similarity index 100% rename from content/images/footer_bkg.gif rename to images/footer_bkg.gif diff --git a/content/images/get_started_button.gif b/images/get_started_button.gif similarity index 100% rename from content/images/get_started_button.gif rename to images/get_started_button.gif diff --git a/content/images/header_bkg.gif b/images/header_bkg.gif similarity index 100% rename from content/images/header_bkg.gif rename to images/header_bkg.gif diff --git a/content/images/home_bkg.gif b/images/home_bkg.gif similarity index 100% rename from content/images/home_bkg.gif rename to images/home_bkg.gif diff --git a/content/images/logo.gif b/images/logo.gif similarity index 100% rename from content/images/logo.gif rename to images/logo.gif diff --git a/content/images/martini.gif b/images/martini.gif similarity index 100% rename from content/images/martini.gif rename to images/martini.gif diff --git a/content/images/new_release_bkg.gif b/images/new_release_bkg.gif similarity index 100% rename from content/images/new_release_bkg.gif rename to images/new_release_bkg.gif diff --git a/content/images/page_bkg.gif b/images/page_bkg.gif similarity index 100% rename from content/images/page_bkg.gif rename to images/page_bkg.gif diff --git a/content/images/paginator_bkg.gif b/images/paginator_bkg.gif similarity index 100% rename from content/images/paginator_bkg.gif rename to images/paginator_bkg.gif diff --git a/content/images/people/alex_coles.jpg b/images/people/alex_coles.jpg similarity index 100% rename from content/images/people/alex_coles.jpg rename to images/people/alex_coles.jpg diff --git a/content/images/people/ben_burket.png b/images/people/ben_burket.png similarity index 100% rename from content/images/people/ben_burket.png rename to images/people/ben_burket.png diff --git a/content/images/people/guy_v.png b/images/people/guy_v.png similarity index 100% rename from content/images/people/guy_v.png rename to images/people/guy_v.png diff --git a/content/images/people/heimidal.jpg b/images/people/heimidal.jpg similarity index 100% rename from content/images/people/heimidal.jpg rename to images/people/heimidal.jpg diff --git a/content/images/people/ior3k.jpg b/images/people/ior3k.jpg similarity index 100% rename from content/images/people/ior3k.jpg rename to images/people/ior3k.jpg diff --git a/content/images/people/matt_aimonetti.png b/images/people/matt_aimonetti.png similarity index 100% rename from content/images/people/matt_aimonetti.png rename to images/people/matt_aimonetti.png diff --git a/content/images/people/rando.jpg b/images/people/rando.jpg similarity index 100% rename from content/images/people/rando.jpg rename to images/people/rando.jpg diff --git a/content/images/people/reinh.jpg b/images/people/reinh.jpg similarity index 100% rename from content/images/people/reinh.jpg rename to images/people/reinh.jpg diff --git a/content/images/people/yehuda.jpg b/images/people/yehuda.jpg similarity index 100% rename from content/images/people/yehuda.jpg rename to images/people/yehuda.jpg diff --git a/content/images/puff_bkg.gif b/images/puff_bkg.gif similarity index 100% rename from content/images/puff_bkg.gif rename to images/puff_bkg.gif diff --git a/content/images/search_bkg.gif b/images/search_bkg.gif similarity index 100% rename from content/images/search_bkg.gif rename to images/search_bkg.gif diff --git a/content/images/search_button_bkg.gif b/images/search_button_bkg.gif similarity index 100% rename from content/images/search_button_bkg.gif rename to images/search_button_bkg.gif diff --git a/content/images/waiter.gif b/images/waiter.gif similarity index 100% rename from content/images/waiter.gif rename to images/waiter.gif diff --git a/content/images/wykatz_at_mwrc2008.png b/images/wykatz_at_mwrc2008.png similarity index 100% rename from content/images/wykatz_at_mwrc2008.png rename to images/wykatz_at_mwrc2008.png diff --git a/index.markdown b/index.markdown new file mode 100644 index 0000000..0e95400 --- /dev/null +++ b/index.markdown @@ -0,0 +1,56 @@ +--- +layout: default +body_id: home +title: DataMapper +--- + +

    DataMapper is a [Object Relational Mapper](http://en.wikipedia.org/wiki/Object-relational_mapping) written in [Ruby](http://ruby-lang.org/). +The goal is to create an ORM which is fast, thread-safe and feature rich.

    + +

    To learn a little more about this project and +why you should be interested,
    read the [Why Datamapper?](/why.html) page.

    + +

    Recent News

    + +{% for post in site.categories.important limit:1 %} +

    {{ post.title }}
    + {{ post.summary }}
    + Read more +

    +{% endfor %} + +
    +Help +---- + +If you're having trouble, don't forget to check the documentation, which has +both references and step by step tutorials.
    +[Read documentation](/docs) +
    + +
    +Issues +------ + +If you're still having trouble, or you think you came across something you think +might be a bug, let us know.
    +[Log a ticket](http://datamapper.lighthouseapp.com/projects/20609-datamapper/overview) +
    + +News +---- + +
    +{% for post in site.posts limit:20 %} + +
    {{ post.title }}
    +
    +

    {{ post.summary }}

    +

    {{ post.date | date_to_long_string }} by {{ post.author }}

    +
    + +{% endfor %} +
    + +{{ paginator.previous_page }} +{{ paginator.next_page }} diff --git a/lib/breadcrumbs.rb b/lib/breadcrumbs.rb deleted file mode 100644 index 39c3439..0000000 --- a/lib/breadcrumbs.rb +++ /dev/null @@ -1,28 +0,0 @@ -# breadcrumbs.rb - -module BreadcrumbsHelper - # call-seq: - # breadcrumbs( page ) => html - # - # Create breadcrumb links for the current page. This will return an HTML - #
      object. - # - def breadcrumbs( page ) - list = [ "
    • #{h(page.title)}
    • " ] - loop do - page = @pages.parent_of(page) - break if page.nil? - list << "
    • #{link_to_page(page)}
    • " - end - list.reverse! - - html = "
        \n" - html << list.join("\n") - html << "\n
      \n" - html - end -end # module Breadcrumbs - -# Webby::Helpers.register(BreadcrumbsHelper) - -# EOF diff --git a/lib/dochelper.rb b/lib/dochelper.rb deleted file mode 100644 index 18613dc..0000000 --- a/lib/dochelper.rb +++ /dev/null @@ -1,41 +0,0 @@ -module DocHelper - - ## - # Translates a Namespace::Constant::Class#Method into a YARD documentation link: - # doc('Adapters::AbstractAdapter', 'transaction_primitive') - # # => http://datamapper.rubyforge.org/DataMapper/Adapters/AbstractAdapter.html#transaction_primitive-instance_method - # - # doc('DataMapper::Adapters') - # # => http://datamapper.rubyforge.org/DataMapper/Adapters.html - # - # @param [String] klass the class name with namespacing to link to. - # 'DataMapper::' is prepended to the string if it doesn't already start with it - # - # @param [String] method (optional) method name to append to the link - # - # @param [Symbol(:instance, :class)] method_level the method-level (instance, class) - # of the method. defaults to :instance - # - # @raise [ArgumentError] if you supply an invalid method_level - # - def doc(klass, method = nil, method_level = :instance) - # - # TODO: make this recognize DM:: as well - # TODO: make this ALOT smarter...perhaps dynamically store the whole DM namespace, if that's possible? - # - raise ArgumentError, 'method_level must be :instance or :class' unless [ :instance, :class ].include?(method_level) - - link = %Q{http://datamapper.rubyforge.org/} - - link << "DataMapper/" unless klass[/^DataMapper::/] - - link << klass.gsub("::","/") << ".html" - if method - link << "##{method.downcase.underscore}" if method - link << "-#{method_level}_method" - end - out = %Q{#{klass}} - end -end - -Webby::Helpers.register(DocHelper) diff --git a/news.markdown b/news.markdown new file mode 100644 index 0000000..a091890 --- /dev/null +++ b/news.markdown @@ -0,0 +1,27 @@ +--- +layout: default +body_id: news +title: DataMapper News and Notes +--- + +{{ page.title }} +================ + +
      + + {% for post in site.posts limit:20 %} + +
      {{ post.title }}
      +
      +

      {{ post.summary }}

      +

      {{ post.date | date_to_long_string }} by {{ post.author }}

      +
      + + {% endfor %} + +
      + +

      + {{ paginator.previous_page }} + {{ paginator.next_page }} +

      diff --git a/content/news.rss b/news.rss similarity index 100% rename from content/news.rss rename to news.rss diff --git a/templates/_partial.erb b/templates/_partial.erb deleted file mode 100644 index 6c515b1..0000000 --- a/templates/_partial.erb +++ /dev/null @@ -1,10 +0,0 @@ ---- -filter: erb ---- -A partial has access to the page from which it was called. The title below will be the title of the page in which this partial is rendered. - -<%%= h(@page.title) %> - -A partial does not have access to it's own meta-data. The partial meta-data is used primarily for finding partials or for use in other pages. The filter(s) specified in the meta-data will be applied to the partial text when it is rendered. - -A partial does not require meta-data at all. They can contain just text. diff --git a/templates/atom_feed.erb b/templates/atom_feed.erb deleted file mode 100644 index ab9d8ef..0000000 --- a/templates/atom_feed.erb +++ /dev/null @@ -1,34 +0,0 @@ ---- -extension: xml -layout: false -dirty: true -filter: -- erb ---- - - - - A New Atom Feed - a really swell blog - - - <%%= Time.now.xmlschema %> - - Author's Name - author@fakesite.nil - - http://fakesite.nil/ - <%% @pages.find(:limit => 10, - :in_directory => 'articles', - :recursive => true, - :sort_by => 'created_at', - :reverse => true).each do |article| %> - - <%%= h(article.title) %> - - tag:fakesite.nil,<%%= article.created_at.strftime('%Y-%m-%d') %>:<%%= article.created_at.to_i %> - <%%= article.created_at.xmlschema %> - <%%= h(article.render) %> - - <%% end %> - diff --git a/templates/news.erb b/templates/news.erb deleted file mode 100644 index 93adec0..0000000 --- a/templates/news.erb +++ /dev/null @@ -1,16 +0,0 @@ ---- -body_id: news -title: Title shall go here -created_at: <%= Time.now.iso8601 %> -summary: Summary Goes Here -release_type: important -author: <%= ENV['USER'] %> -filter: - - erb - - textile ---- - -h1. <%%= @page.title %> - - -Dramatically conceptualize installed base meta-services vis-a-vis long-term high-impact imperatives. Completely enhance next-generation processes and B2C imperatives. Assertively whiteboard principle-centered vortals whereas compelling total linkage. Progressively evisculate leading-edge methods of empowerment whereas turnkey niches. Enthusiastically transform out-of-the-box deliverables via market-driven niche markets. Holisticly disintermediate holistic e-commerce vis-a-vis superior e-commerce. Quickly matrix world-class action items vis-a-vis cross-unit channels. Monotonectally disintermediate user-centric bandwidth and reliable niches. Enthusiastically embrace inexpensive potentialities for empowered total linkage. diff --git a/templates/page.erb b/templates/page.erb deleted file mode 100644 index 24d1df7..0000000 --- a/templates/page.erb +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: New Page -created_at: <%= Time.now.to_y %> -filter: - - erb - - textile ---- -p(title). <%%= h(@page.title) %> - -Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc congue ipsum vestibulum libero. Aenean vitae justo. Nam eget tellus. Etiam convallis, est eu lobortis mattis, lectus tellus tempus felis, a ultricies erat ipsum at metus. - -h2. Litora Sociis - -Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi et risus. Aliquam nisl. Nulla facilisi. Cras accumsan vestibulum ante. Vestibulum sed tortor. Praesent tempus fringilla elit. Ut elit diam, sagittis in, nonummy in, gravida non, nunc. Ut orci. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Nam egestas, orci eu imperdiet malesuada, nisl purus fringilla odio, quis commodo est orci vitae justo. Aliquam placerat odio tincidunt nulla. Cras in libero. Aenean rutrum, magna non tristique posuere, erat odio eleifend nisl, non convallis est tortor blandit ligula. Nulla id augue. - -bq. Nullam mattis, odio ut tempus facilisis, metus nisl facilisis metus, auctor consectetuer felis ligula nec mauris. Vestibulum odio erat, fermentum at, commodo vitae, ultrices et, urna. Mauris vulputate, mi pulvinar sagittis condimentum, sem nulla aliquam velit, sed imperdiet mi purus eu magna. Nulla varius metus ut eros. Aenean aliquet magna eget orci. Class aptent taciti sociosqu ad litora. - -Vivamus euismod. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Suspendisse vel nibh ut turpis dictum sagittis. Aliquam vel velit a elit auctor sollicitudin. Nam vel dui vel neque lacinia pretium. Quisque nunc erat, venenatis id, volutpat ut, scelerisque sed, diam. Mauris ante. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec mattis. Morbi dignissim sollicitudin libero. Nulla lorem. diff --git a/templates/spotlight.erb b/templates/spotlight.erb deleted file mode 100644 index af6af24..0000000 --- a/templates/spotlight.erb +++ /dev/null @@ -1,22 +0,0 @@ ---- -body_id: news -title: Spotlight on... Topic Here -created_at: <%= Time.now.iso8601 %> -summary: Summary Goes Here -release_type: blog -author: <%= ENV['USER'] %> -filter: - - erb - - textile ---- - -h1. <%%= @page.title %> - - - -h2(newRelease). Contribute a "Spotlight On..." Article - -p(newRelease). Got something important to say? Want something explained a little
      -better or demonstrated? Contribute or request a "Spotlight On..."
      article! -Email the "DataMapper Mailing List":http://groups.google.com/group/datamapper with the request or
      -contribution and we'll post it here. diff --git a/content/transcripts.txt b/transcripts.markdown similarity index 98% rename from content/transcripts.txt rename to transcripts.markdown index 425b3fd..a8f9080 100644 --- a/content/transcripts.txt +++ b/transcripts.markdown @@ -1,11 +1,10 @@ --- +layout: default body_id: logger title: Chat logs for #datamapper -filter: - - erb - - textile --- -h1. <%= @page.title %> +{{ page.title }} +================
      diff --git a/using-git.markdown b/using-git.markdown new file mode 100644 index 0000000..f51add5 --- /dev/null +++ b/using-git.markdown @@ -0,0 +1,222 @@ +--- +layout: default +title: Using Git +--- + +{{ page.title }} +================ + +The DataMapper project uses the Git SCM. Committers +need to use git to commit their code directly to the main repository. + +This page contains information on getting Git installed, getting source code +with Git, and steps for working with Git. + +Also, see these references: Git +– SVN Crash Course and Everyday +GIT With 20 Commands Or So + +Getting Git for Your System +--------------------------- + +You can use an earlier version, but 1.5.x is definitely recommended. + +* MacPorts has `git-core` +* Debian has `git-core`; (If you're using Etch, you can get a recent Git version from Backports http://backports.org/dokuwiki/do…) +* Get the source at http://git.or.cz/. + +Setup +----- + +Configure Git with your proper name and email. This will display when you submit +changes to the DataMapper repository. + +{% highlight bash %} +git config --global user.name "My Name" +git config --global user.email "my@email" +{% endhighlight %} + +If you prefer to use different credentials for different projects, you can also +configure the above for a single repository only. See the git documentation. + +Formatting Git Commit Messages +------------------------------ + +In general, use an editor to create your commit messages rather than passing +them on the command line. The format should be: + +* A hard wrap at 72 characters +* A single, short, summary of the commit +* Followed by a single blank line +* Followed by supporting details + +The supporting details could be a bulleted enumeration or an explanatory +paragraph. The single summary line helps folks reviewing commits. An example +commit: + +{% highlight bash lineos %} +Fixes for Module#make_my_day return values. + +* Return nil when passed ':(' +* Return true when passed ':)' +* Updated specs for #make_my_day for nil argument case +* Updated CI excludes. +{% endhighlight %} + +Getting the Code +---------------- + +DataMapper is hosted at GitHub. +Getting the code is easy once you have git installed but is slightly different +depending on your access. In both cases that exact command will put the +repository in a local directory called dm. You can give it a different name just +by appending it to the command. + +### New Users and Developers + +{% highlight bash lineos %} +git clone git://github.com/datamapper/dm-core.git +{% endhighlight %} + +### Committers with Commit Bit + +{% highlight bash lineos %} +git clone git@github.com/datamapper/dm-core.git +{% endhighlight %} + +Git Workflow +------------ + +Working with Git is significantly different than working with SVN. In particular, although similar, git pull is not svn +update, git push is not svn commit, and git add is not svn add. If you are a +SVN user, be sure to read the man pages for the +different git commands.

      + +The following workflow is recommended by Rein and is the guideline for +contributing code to DataMapper. + +
        +
      1. +

        Create a local working copy of the source code (we did this earlier.)

        + {% highlight bash %} + # See above for the exact invocation + {% endhighlight %} +
      2. + +
      3. +

        Change to the newly created directory that contains the local working copy. (Substitute the directory if you created it with a different name, obviously.)

        + {% highlight bash %} + cd dm + {% endhighlight %} +
      4. + +
      5. +

        Create a branch for your work. It is important that you do your work in a local branch, rather than master.

        + {% highlight bash %} + git checkout -b new_feature + {% endhighlight %} +
      6. + +
      7. +

        Edit the code and test your changes. Then commit to your local working copy.

        + {% highlight bash %} + git add . + git commit + {% endhighlight %} +
      8. + +
      9. +

        When you are ready to send your local changes back to the DataMapper repository, you first need to ensure that your local copy is up-to-date. First, ensure you have committed your local changes. Then switch from your topic branch to the master branch.

        + {% highlight bash %} + git checkout master + {% endhighlight %} +
      10. + +
      11. +

        Update your local copy with changes from the DataMapper repository

        + {% highlight bash %} + git pull origin master --rebase + {% endhighlight %} +
      12. + +
      13. +

        Switch back to your topic branch and integrate any new changes. The git rebase command will save your changes away, update the topic branch, and then reapply them.

        + {% highlight bash %} + git checkout new_feature + git rebase master + {% endhighlight %} +

        Warning! If you have shared the topic branch publicly, you must use

        + + {% highlight bash %} + git merge master + {% endhighlight %} +

        Rebase causes the commit layout to change and will confuse anyone + you’ve shared this branch with.

      14. + +
      15. +

        If there are conflicts applying your changes during the git rebase command, fix them and use the following to finish applying them

        + {% highlight bash %} + git rebase --continue + {% endhighlight %} +
      16. + +
      17. +

        Now, switch back to the master branch and merge your changes from the topic branch

        + {% highlight bash %} + git checkout master + git merge new_feature + {% endhighlight %} +
      18. + +
      19. +

        You might want to check that your commits ended up as you intended. To do so, you can have a look at the log

        + {% highlight bash %} + git log + {% endhighlight %} +
      20. + +
      21. +

        Get your changes in the main repository. If you have commit rights, you can just use the git push command. Otherwise, see the section below for information on creating a set of patches to send.

        + {% highlight bash %} + git push origin master + {% endhighlight %} +
      22. + +
      23. +

        At this point, you can delete the branch if you like.

        + {% highlight bash %} + git branch -d new_feature + {% endhighlight %} +
      24. +
      + +Patches: git-format-patch +------------------------- + +If you are a new committer (or want to create a patch instead of directly +pushing the code for some other reason) you should create a patch file for your +commits. The patch file should be then attached to a ticket on Lighthouse. You +can also send the patch to the mailing list but please use the ticket tracker if +at all possible. Either way, the patch file(s) should be created using Git. + +First, make your changes as detailed below and then use the git format-patch +command to create the patch files. Usually using the command is as simple as +specifying the commits you want to create patches for, and that is done in one +of two ways: by giving a range of commits or a starting point. + +For our purposes, the simplest way to create a patch is to begin at the end of +step 8 above (after you have rebased your branch) and then, instead of +merging: + +{% highlight bash %} +git format-patch master.. +{% endhighlight %} + +This will create a separate patch file for each commit in your working branch +that is not in master, named `[number]-[first line of commit message].patch`. You +can then attach these to a ticket (or e-mail them). + +You can also inspect your changes using `git log master..` or `git diff master..` +to ensure that the patches will be generated correctly if you are uncertain. diff --git a/why.markdown b/why.markdown new file mode 100644 index 0000000..2fbd634 --- /dev/null +++ b/why.markdown @@ -0,0 +1,185 @@ +--- +layout: default +body_id: why +title: Why DataMapper? +--- + +Why DataMapper? +=============== + +DataMapper differentiates itself from other Ruby Object/Relational Mappers in a +number of ways: + +Identity Map +------------ + +One row in the database should equal one object reference. Pretty simple idea. +Pretty profound impact. If you run the following code in ActiveRecord you'll see +all `false` results. Do the same in DataMapper and it's `true` all the way down. + +{% highlight ruby linenos %} +@parent = Tree.first(:conditions => { :name => 'bob' }) + +@parent.children.each do |child| + puts @parent.object_id == child.parent.object_id +end +{% endhighlight %} + +This makes DataMapper faster and allocate less resources to get things done. + +Plays Well With Others +---------------------- + +With DataMapper you define your mappings in your model. Your data-store can +develop independently of your models using Migrations. + +To support data-stores which you don't have the ability to manage yourself, it's +simply a matter of telling DataMapper where to look. + +{% highlight ruby linenos %} +class Fruit + include DataMapper::Resource + + storage_names[:default] = 'frt' # equivalent to set_table_name in AR + + property :id, Serial + property :name, String, :field => 'col2' +end +{% endhighlight %} + +DataMapper only issues updates or creates for the properties it knows about. So +it plays well with others. You can use it in an Integration Database without +worrying that your application will be a bad actor causing trouble for all of +your other processes. + +Laziness Can Be A Virtue +------------------------ + +Columns of potentially infinite length, like Text columns, are expensive in +data-stores. They're generally stored in a different place from the rest of your +data. So instead of a fast sequential read from your hard-drive, your data-store +has to hop around all over the place to get what it needs. + +With DataMapper, these fields are treated like in-row associations by default, +meaning they are loaded if and only if you access them. If you want more control +you can enable or disable this feature for any column (not just text-fields) by +passing a `lazy` option to your column mapping with a value of `true` or +`false`. + +{% highlight ruby linenos %} +class Animal + include DataMapper::Resource + + property :id, Serial + property :name, String + property :notes, Text # lazy-loads by default +end +{% endhighlight %} + +Plus, lazy-loading of Text property happens automatically and intelligently when +working with associations. The following only issues 2 queries to load up all of +the notes fields on each animal: + +{% highlight ruby linenos %} +animals = Animal.all +animals.each do |pet| + pet.notes +end +{% endhighlight %} + +Strategic Eager Loading +----------------------- + +DataMapper will only issue the very bare minimums of queries to your data-store +that it needs to. For example, the following example will only issue 2 queries. +Notice how we don't supply any extra `:include` information. + +{% highlight ruby linenos %} +zoos = Zoo.all +zoos.each do |zoo| + # on first iteration, DM loads up all of the exhibits for all of the items in zoos + # in 1 query to the data-store. + + zoo.exhibits.each do |exhibit| + # n+1 queries in other ORMs, not in DataMapper + puts "Zoo: #{zoo.name}, Exhibit: #{exhibit.name}" + end +end +{% endhighlight %} + +The idea is that you aren't going to load a set of objects and use only an +association in just one of them. This should hold up pretty well against a 99% +rule. + +When you don't want it to work like this, just load the item you want in it's +own set. So DataMapper thinks ahead. We like to call it "performant by default". +*This feature single-handedly wipes out the "N+1 Query Problem".* + +DataMapper also waits until the very last second to actually issue the query to +your data-store. For example, `zoos = Zoo.all` won't run the query until you +start iterating over `zoos` or call one of the 'kicker' methods like `#length`. +If you never do anything with the results of a query, DataMapper won't incur the +latency of talking to your data-store. + +All Ruby, All The Time +---------------------- + +DataMapper goes further than most Ruby ORMs in letting you avoid writing raw +query fragments yourself. It provides more helpers and a unique hash-based +conditions syntax to cover more of the use-cases where issuing your own SQL +would have been the only way to go. + +For example, any finder option that are non-standard is considered a condition. +So you can write `Zoo.all(:name => 'Dallas')` and DataMapper will look for zoos +with the name of 'Dallas'. + +It's just a little thing, but it's so much nicer than writing +`Zoo.find(:all, :conditions => [ 'name = ?', 'Dallas' ])` and won't incur the +Ruby overhead of +`Zoo.find_by_name('Dallas')`, nor is it more difficult to understand once the +number of parameters increases. + +What if you need other comparisons though? Try these: + +{% highlight ruby linenos %} +Zoo.first(:name => 'Galveston') + +# 'gt' means greater-than. 'lt' is less-than. +Person.all(:age.gt => 30) + +# 'gte' means greather-than-or-equal-to. 'lte' is also available +Person.all(:age.gte => 30) + +Person.all(:name.not => 'bob') + +# If the value of a pair is an Array, we do an IN-clause for you. +Person.all(:name.like => 'S%', :id => [ 1, 2, 3, 4, 5 ]) + +# Does a NOT IN () clause for you. +Person.all(:name.not => [ 'bob', 'rick', 'steve' ]) + +# Ordering +Person.all(:order => [ :age.desc ]) +# .asc is the default +{% endhighlight %} + +To query a model by it's associations, you can use a QueryPath: + +{% highlight ruby linenos %} + Person.all(:links => [ :pets ], Person.pets.name => 'Pixel') +{% endhighlight %} + +You can even chain calls to `all` or `first` to continue refining your query or +search within a scope. See [Finders](/docs/find.html) for more information. + +Open Development +---------------- + +DataMapper sports a very accessible code-base and a welcoming community. Outside +contributions and feedback are welcome and encouraged, especially constructive +criticism. Go ahead, fork DataMapper, we'd love to see what you come up with! + +Make your voice heard! [Submit a ticket or patch](http://datamapper.lighthouseapp.com/projects/20609-datamapper/), +speak up on our [mailing-list](http://groups.google.com/group/datamapper/), +chat with us on [irc](irc://irc.freenode.net/#datamapper), write a spec, +get it reviewed, ask for commit rights. It's as easy as that to become a contributor.