paulcarey-relaxdb 0.2.8 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.textile +100 -108
- data/Rakefile +13 -2
- data/docs/spec_results.html +609 -154
- data/lib/more/atomic_bulk_save_support.rb +18 -0
- data/lib/relaxdb/all_delegator.rb +15 -39
- data/lib/relaxdb/design_doc.rb +8 -6
- data/lib/relaxdb/document.rb +140 -74
- data/lib/relaxdb/has_many_proxy.rb +6 -5
- data/lib/relaxdb/has_one_proxy.rb +2 -5
- data/lib/relaxdb/paginate_params.rb +3 -4
- data/lib/relaxdb/paginator.rb +12 -16
- data/lib/relaxdb/query.rb +17 -13
- data/lib/relaxdb/references_many_proxy.rb +3 -5
- data/lib/relaxdb/relaxdb.rb +51 -45
- data/lib/relaxdb/server.rb +4 -4
- data/lib/relaxdb/view_uploader.rb +10 -8
- data/lib/relaxdb/views.rb +88 -28
- data/lib/relaxdb.rb +0 -1
- data/readme.rb +79 -0
- data/spec/belongs_to_spec.rb +1 -6
- data/spec/callbacks_spec.rb +19 -3
- data/spec/derived_properties_spec.rb +2 -7
- data/spec/design_doc_spec.rb +3 -3
- data/spec/document_spec.rb +19 -57
- data/spec/has_many_spec.rb +11 -14
- data/spec/has_one_spec.rb +1 -6
- data/spec/query_spec.rb +26 -22
- data/spec/references_many_spec.rb +1 -6
- data/spec/relaxdb_spec.rb +88 -29
- data/spec/spec_helper.rb +34 -0
- data/spec/spec_models.rb +163 -118
- metadata +7 -3
- data/lib/relaxdb/sorted_by_view.rb +0 -65
    
        data/README.textile
    CHANGED
    
    | @@ -1,44 +1,12 @@ | |
| 1 1 | 
             
            h3. What's New?
         | 
| 2 2 |  | 
| 3 | 
            -
             | 
| 3 | 
            +
            RelaxDB 0.3 released - compatible with CouchDB 0.9. 
         | 
| 4 4 |  | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 7 | 
            -
            * Potentially breaking change. @load@ now returns an array if passed an array of size one. Previously it would have returned a single object.
         | 
| 8 | 
            -
             | 
| 9 | 
            -
            * Update conflict hook and property
         | 
| 10 | 
            -
             | 
| 11 | 
            -
            * Semantic consistency for bulk_save and bulk_save! wrt to save and save!
         | 
| 12 | 
            -
             | 
| 13 | 
            -
            * Multiple exception handling improvements
         | 
| 14 | 
            -
             | 
| 15 | 
            -
            * @save_all@ that issues a bulk_save for an object and its has_one and has_many children
         | 
| 16 | 
            -
             | 
| 17 | 
            -
            * assignment of @has_many@ relationships
         | 
| 18 | 
            -
             | 
| 19 | 
            -
            * Validations may be skipped by passing the attribute symbol(s) to @save@ or @save!@.
         | 
| 20 | 
            -
             | 
| 21 | 
            -
            * Denormalisation via derived properties. Examples in spec/derived_properties_spec.rb.
         | 
| 22 | 
            -
             | 
| 23 | 
            -
            * Semantic changes for @ has_many#<< @. The parent object is now assigned to the child object *prior* to validation. This potentially breaking change was made to allow child objects to derive properties from a parent object.
         | 
| 24 | 
            -
             | 
| 25 | 
            -
            * Semantic consistency for load, load!, save and save!. The bang versions raise an exception when their more relaxed siblings would simply return nil.
         | 
| 26 | 
            -
             | 
| 27 | 
            -
            * Minimal support for CouchDB validation 
         | 
| 28 | 
            -
             | 
| 29 | 
            -
            * Time storage changes. All Time objects are now converted to UTC and formatted as @ %Y/%m/%d %H:%M:%S +0000 @. Storing all Times as UTC should have been happening anyway. Formatting Times as above (as opposed to ISO 8601 as was done prior to 0.2.3) allows the Time strings to be passed directly to Date.new in a JavaScript interpreter. 
         | 
| 30 | 
            -
             | 
| 31 | 
            -
            * Pagination! CouchDB offers great support for retrieving a subset of data, but the housekeeping is tricky. RelaxDB takes care of it.
         | 
| 32 | 
            -
            ** Note that if you invoke paginate_by on an already created view, the necessary reduce function won't be automatically created. Take a look at SortedByView and create the reduce func by hand.
         | 
| 33 | 
            -
            * Support for multi key post
         | 
| 34 | 
            -
            ** For example, @ Numbers.all.sorted_by(:val) { |q| q.keys([1,2,3,5]) } @
         | 
| 35 | 
            -
            * Works with CouchDB 0.9 trunk as of 2009/01/02. Note that pagination won't work correctly on trunk until issue "COUCHDB-135":http://issues.apache.org/jira/browse/COUCHDB-135 is fixed.
         | 
| 36 | 
            -
             | 
| 37 | 
            -
            *Note*: Current versions require CouchDB 0.9 trunk. If you're working with CouchDB 0.8 or 0.8.1, please build from commit @ a8a2d496462 @.
         | 
| 5 | 
            +
            Version 0.3 includes many breaking changes. Most notable are simplified view syntax changes and the requirement that a design doc be specified up front.
         | 
| 38 6 |  | 
| 39 7 | 
             
            h2. Overview
         | 
| 40 8 |  | 
| 41 | 
            -
            RelaxDB provides a Ruby interface to CouchDB. It offers a simple idiom for specifying object relationships. The underlying objects are persisted to  | 
| 9 | 
            +
            RelaxDB provides a Ruby interface to CouchDB. It offers a simple idiom for specifying object relationships. The underlying objects are persisted to CouchDB and are retreived using CouchDB idioms.
         | 
| 42 10 |  | 
| 43 11 | 
             
            A few facilities are provided including pretty printing of GET requests and uploading of JavaScript views.
         | 
| 44 12 |  | 
| @@ -46,14 +14,21 @@ A basic merb plugin, "merb_relaxdb":http://github.com/paulcarey/merb_relaxdb/tre | |
| 46 14 |  | 
| 47 15 | 
             
            For more complete documentation take a look at docs/spec_results.html and the corresponding specs.
         | 
| 48 16 |  | 
| 49 | 
            -
             | 
| 17 | 
            +
            *Note*: While RelaxDB 0.3 is explicitly compatible with CouchDB 0.9, HEAD typically tracks CouchDB HEAD.
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            h2. Details
         | 
| 50 20 |  | 
| 51 21 | 
             
            h3. Getting started
         | 
| 52 22 |  | 
| 53 23 | 
             
            <pre>
         | 
| 54 24 | 
             
            <code>
         | 
| 55 | 
            -
               | 
| 56 | 
            -
               | 
| 25 | 
            +
              require 'rubygems'
         | 
| 26 | 
            +
              require 'relaxdb'
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              RelaxDB.configure :host => "localhost", :port => 5984, :design_doc => "app"
         | 
| 29 | 
            +
              RelaxDB.use_db "relaxdb_scratch"
         | 
| 30 | 
            +
              
         | 
| 31 | 
            +
              RelaxDB.enable_view_creation # creates views when class definition is executed
         | 
| 57 32 | 
             
            </code>
         | 
| 58 33 | 
             
            </pre>
         | 
| 59 34 |  | 
| @@ -61,27 +36,37 @@ h3. Defining models | |
| 61 36 |  | 
| 62 37 | 
             
            <pre>
         | 
| 63 38 | 
             
            <code>
         | 
| 64 | 
            -
              class Writer < RelaxDB::Document
         | 
| 65 | 
            -
                property :name, :default => "anon"
         | 
| 66 | 
            -
                
         | 
| 67 | 
            -
                has_many :posts, :class => "Post"
         | 
| 68 | 
            -
                has_many :ratings, :class => "Post", :known_as => :critic
         | 
| 69 | 
            -
              end
         | 
| 70 39 |  | 
| 71 | 
            -
             | 
| 72 | 
            -
             | 
| 73 | 
            -
             | 
| 40 | 
            +
            class User < RelaxDB::Document
         | 
| 41 | 
            +
              property :name
         | 
| 42 | 
            +
            end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            class Invite < RelaxDB::Document
         | 
| 45 | 
            +
              
         | 
| 46 | 
            +
              property :created_at
         | 
| 47 | 
            +
              
         | 
| 48 | 
            +
              property :event_name
         | 
| 49 | 
            +
              
         | 
| 50 | 
            +
              property :state, :default => "awaiting_response",
         | 
| 51 | 
            +
                :validator => lambda { |s| %w(accepted rejected awaiting_response).include? s }
         | 
| 74 52 |  | 
| 75 | 
            -
             | 
| 76 | 
            -
             | 
| 53 | 
            +
              references :sender, :validator => :required
         | 
| 54 | 
            +
              
         | 
| 55 | 
            +
              references :recipient, :validator => :required
         | 
| 56 | 
            +
              
         | 
| 57 | 
            +
              property :sender_name,
         | 
| 58 | 
            +
               :derived => [:sender, lambda { |p, o| o.sender.name } ]
         | 
| 59 | 
            +
              
         | 
| 60 | 
            +
              view_by :sender_name
         | 
| 61 | 
            +
              view_by :sender_id
         | 
| 62 | 
            +
              view_by :recipient_id, :created_at, :descending => true
         | 
| 63 | 
            +
              
         | 
| 64 | 
            +
              def on_update_conflict
         | 
| 65 | 
            +
                puts "conflict!"
         | 
| 77 66 | 
             
              end
         | 
| 67 | 
            +
              
         | 
| 68 | 
            +
            end
         | 
| 78 69 |  | 
| 79 | 
            -
              class Rating < RelaxDB::Document
         | 
| 80 | 
            -
                property :thumbs_up, :validator => lambda { |tu| tu >= 0 && tu < 3 }, :validation_msg => "No no"
         | 
| 81 | 
            -
             | 
| 82 | 
            -
                belongs_to :post
         | 
| 83 | 
            -
                belongs_to :critic
         | 
| 84 | 
            -
              end
         | 
| 85 70 | 
             
            </code>
         | 
| 86 71 | 
             
            </pre>
         | 
| 87 72 |  | 
| @@ -89,16 +74,46 @@ h3. Exploring models | |
| 89 74 |  | 
| 90 75 | 
             
            <pre>
         | 
| 91 76 | 
             
            <code>
         | 
| 92 | 
            -
             | 
| 77 | 
            +
            # Saving objects
         | 
| 78 | 
            +
             | 
| 79 | 
            +
            sofa = User.new(:name => "sofa").save!
         | 
| 80 | 
            +
            futon = User.new(:name => "futon").save!
         | 
| 81 | 
            +
             | 
| 82 | 
            +
            i = Invite.new :sender => sofa, :recipient => futon, :event_name => "CouchCamp"
         | 
| 83 | 
            +
            i.save!
         | 
| 84 | 
            +
             | 
| 85 | 
            +
            # Loading and querying
         | 
| 86 | 
            +
             | 
| 87 | 
            +
            il = RelaxDB.load i._id
         | 
| 88 | 
            +
            puts i == il # true
         | 
| 89 | 
            +
             | 
| 90 | 
            +
            ir = Invite.by_sender_name "sofa"
         | 
| 91 | 
            +
            puts i == ir # true
         | 
| 92 | 
            +
             | 
| 93 | 
            +
            ix = Invite.by_sender_name(:key => "sofa").first
         | 
| 94 | 
            +
            puts i == ix # true
         | 
| 95 | 
            +
             | 
| 96 | 
            +
            # Denormalization
         | 
| 93 97 |  | 
| 94 | 
            -
             | 
| 95 | 
            -
             | 
| 96 | 
            -
             | 
| 97 | 
            -
             | 
| 98 | 
            -
             | 
| 98 | 
            +
            puts ix.sender_name # prints sofa, no requests to CouchDB made
         | 
| 99 | 
            +
            puts ix.sender.name # prints sofa, a single CouchDB request made
         | 
| 100 | 
            +
             | 
| 101 | 
            +
            # Saving with conflicts
         | 
| 102 | 
            +
             | 
| 103 | 
            +
            idup = i.dup
         | 
| 104 | 
            +
            i.save!
         | 
| 105 | 
            +
            idup.save # conflict printed
         | 
| 106 | 
            +
             | 
| 107 | 
            +
            # Saving with and without validations
         | 
| 108 | 
            +
             | 
| 109 | 
            +
            i = Invite.new :sender => sofa, :name => "daily show"
         | 
| 110 | 
            +
            i.save! rescue :ok # save! throws an exception on validation failure or conflict
         | 
| 111 | 
            +
            i.save # returns false rather than throwing an exception
         | 
| 112 | 
            +
            puts i.errors.inspect # {:recipient=>"invalid:"}
         | 
| 113 | 
            +
             | 
| 114 | 
            +
            i.validation_skip_list << :recipient # Any and all validations may be skipped
         | 
| 115 | 
            +
            i.save # succeeds
         | 
| 99 116 |  | 
| 100 | 
            -
              # Simple views are auto created
         | 
| 101 | 
            -
              Rating.all.sorted_by(:thumbs_up) { |q| q.key(2).limit(1) }  # query params map directly to CouchDB
         | 
| 102 117 | 
             
            </code>
         | 
| 103 118 | 
             
            </pre>
         | 
| 104 119 |  | 
| @@ -106,80 +121,57 @@ h3. Paginating models | |
| 106 121 |  | 
| 107 122 | 
             
            <pre>
         | 
| 108 123 | 
             
            <code>
         | 
| 109 | 
            -
              # Controller | 
| 110 | 
            -
             | 
| 111 | 
            -
              def action(page_params={})
         | 
| 112 | 
            -
                u_id = @user._id
         | 
| 124 | 
            +
              # Controller
         | 
| 113 125 |  | 
| 114 | 
            -
             | 
| 115 | 
            -
             | 
| 116 | 
            -
                 | 
| 126 | 
            +
              def show(page_params={})
         | 
| 127 | 
            +
                uid = @user._id
         | 
| 128 | 
            +
                @invites = Invite.paginate_by_sender_name :startkey => [uid, {}], 
         | 
| 129 | 
            +
                    :endkey => [uid], :descending => true, :limit => 5, :page_params => page_params
         | 
| 117 130 | 
             
                render
         | 
| 118 131 | 
             
              end
         | 
| 119 132 |  | 
| 120 133 | 
             
              # In your view
         | 
| 121 134 |  | 
| 122 | 
            -
              <% @ | 
| 123 | 
            -
                <%=  | 
| 135 | 
            +
              <% @invites.each do |i| %>
         | 
| 136 | 
            +
                <%= i.event_name %>
         | 
| 124 137 | 
             
              <% end %>
         | 
| 125 138 |  | 
| 126 | 
            -
              <%= link_to "prev", "/ | 
| 127 | 
            -
              <%= link_to "next", "/ | 
| 139 | 
            +
              <%= link_to "prev", "/invites/?#{@invites.prev_query}" if @invites.prev_query %>
         | 
| 140 | 
            +
              <%= link_to "next", "/invites/?#{@invites.next_query}" if @invites.next_query %>  
         | 
| 128 141 | 
             
            </code>
         | 
| 129 142 | 
             
            </pre>
         | 
| 130 143 |  | 
| 131 | 
            -
             | 
| 132 | 
            -
             | 
| 133 | 
            -
            <pre>
         | 
| 134 | 
            -
            <code>
         | 
| 135 | 
            -
             | 
| 136 | 
            -
            RelaxDB.paginate_view(page_params, "Letter", "by_letter_and_number", :letter, :number) do |p|
         | 
| 137 | 
            -
              p.startkey(["b"]).endkey(["b", {}]).limit(2)
         | 
| 138 | 
            -
            end
         | 
| 139 | 
            -
             | 
| 140 | 
            -
            </code>
         | 
| 141 | 
            -
            </pre>
         | 
| 142 | 
            -
             | 
| 143 | 
            -
            A more illustrative example is listed in the .paginate_view spec in spec/paginate_spec.rb
         | 
| 144 | 
            +
            More illustrative examples are listed in the .paginate_view spec in spec/paginate_spec.rb
         | 
| 144 145 |  | 
| 145 146 | 
             
            h3. Creating views by hand
         | 
| 146 147 |  | 
| 147 148 | 
             
            <pre>
         | 
| 148 149 | 
             
            <code>
         | 
| 149 150 | 
             
              $ cat view.js 
         | 
| 150 | 
            -
              function  | 
| 151 | 
            -
                if(doc. | 
| 152 | 
            -
                  emit( | 
| 151 | 
            +
              function Invites_by_state-map(doc) {
         | 
| 152 | 
            +
                if(doc.relaxdb_class === "Invite")
         | 
| 153 | 
            +
                  emit(doc.state, doc);
         | 
| 153 154 | 
             
              }
         | 
| 154 155 |  | 
| 155 | 
            -
              function  | 
| 156 | 
            -
                 | 
| 157 | 
            -
             | 
| 158 | 
            -
             | 
| 159 | 
            -
             | 
| 156 | 
            +
              function Invites_by_state-reduce(keys, values, rereduce) {
         | 
| 157 | 
            +
                if (rereduce) {
         | 
| 158 | 
            +
                  return sum(values);
         | 
| 159 | 
            +
                } else {
         | 
| 160 | 
            +
                  return values.length;
         | 
| 161 | 
            +
                }
         | 
| 160 162 | 
             
              }
         | 
| 161 163 | 
             
              $
         | 
| 162 164 |  | 
| 163 165 | 
             
              RelaxDB::ViewUploader.upload("view.js")
         | 
| 164 | 
            -
              RelaxDB.view | 
| 166 | 
            +
              RelaxDB.view "Invites_by_state", :key => "accepted", :reduce => true
         | 
| 165 167 | 
             
            </code>
         | 
| 166 168 | 
             
            </pre>
         | 
| 167 169 |  | 
| 168 170 | 
             
            h3. Visualise
         | 
| 169 171 |  | 
| 170 | 
            -
             | 
| 171 | 
            -
            <pre>
         | 
| 172 | 
            -
            <code>
         | 
| 173 | 
            -
            RelaxDB::GraphCreator.create
         | 
| 174 | 
            -
            </code>
         | 
| 175 | 
            -
            </pre>
         | 
| 176 | 
            -
             | 
| 177 | 
            -
            Requires graphviz. Useful for visualising relationships between a limited number of document e.g. test fixtures. "Description and example":http://dev.strawberrydiva.com/visually_explore_couchdb/.
         | 
| 172 | 
            +
            "Fuschia":http://github.com/paulcarey/fuschia/tree/master offers a web front end for visualising inter-document relationships.
         | 
| 178 173 |  | 
| 179 174 | 
             
            h2. Incomplete list of limitations
         | 
| 180 175 |  | 
| 181 | 
            -
            * Error handling is not robust
         | 
| 182 176 | 
             
            * Destroying an object results in non transactional nullification of child/peer references
         | 
| 183 | 
            -
            * Objects can talk to only one database at a time
         | 
| 184 | 
            -
            * No caching is used. Although adding an LRU cache would be fairly straightforward, this hasn't been done as it's not yet clear what caching strategies will be most effective. 
         | 
| 185 | 
            -
             | 
| 177 | 
            +
            * Objects can talk to only one database at a time. Similarly for design docs.
         | 
    
        data/Rakefile
    CHANGED
    
    | @@ -4,7 +4,7 @@ require 'spec/rake/spectask' | |
| 4 4 |  | 
| 5 5 | 
             
            PLUGIN = "relaxdb"
         | 
| 6 6 | 
             
            NAME = "relaxdb"
         | 
| 7 | 
            -
            GEM_VERSION = "0. | 
| 7 | 
            +
            GEM_VERSION = "0.3.0"
         | 
| 8 8 | 
             
            AUTHOR = "Paul Carey"
         | 
| 9 9 | 
             
            EMAIL = "paul.p.carey@gmail.com"
         | 
| 10 10 | 
             
            HOMEPAGE = "http://github.com/paulcarey/relaxdb/"
         | 
| @@ -28,7 +28,7 @@ spec = Gem::Specification.new do |s| | |
| 28 28 |  | 
| 29 29 | 
             
              s.require_path = 'lib'
         | 
| 30 30 | 
             
              s.autorequire = PLUGIN
         | 
| 31 | 
            -
              s.files = %w(LICENSE README.textile Rakefile) + Dir.glob("{docs,lib,spec}/**/*")
         | 
| 31 | 
            +
              s.files = %w(LICENSE README.textile readme.rb Rakefile) + Dir.glob("{docs,lib,spec}/**/*")
         | 
| 32 32 | 
             
            end
         | 
| 33 33 |  | 
| 34 34 | 
             
            Rake::GemPackageTask.new(spec) do |pkg|
         | 
| @@ -50,3 +50,14 @@ Spec::Rake::SpecTask.new('spec:html') do |t| | |
| 50 50 | 
             
              t.spec_files = FileList['spec/**/*.rb']
         | 
| 51 51 | 
             
              t.spec_opts = ["--format", "html:docs/spec_results.html"]
         | 
| 52 52 | 
             
            end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
            desc "Supports atomic bulk save"
         | 
| 55 | 
            +
            task :atomic_bulk_save_support do |t|
         | 
| 56 | 
            +
              require 'lib/more/atomic_bulk_save_support.rb'
         | 
| 57 | 
            +
            end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
            desc "Create base spec db"
         | 
| 60 | 
            +
            task :create_base_db do
         | 
| 61 | 
            +
              require 'spec/spec_helper'
         | 
| 62 | 
            +
              create_base_db
         | 
| 63 | 
            +
            end
         |