Module: CouchSurfer::Model::ClassMethods

Defined in:
lib/couch_surfer/model.rb

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(m, *args) ⇒ Object



260
261
262
263
264
265
266
267
# File 'lib/couch_surfer/model.rb', line 260

def method_missing m, *args
  if has_view?(m)
    query = args.shift || {}
    view(m, query, *args)
  else
    CouchRest::Document.send(:method_missing, m, *args)
  end
end

Instance Method Details

#all(opts = {}, &block) ⇒ Object

Load all documents that have the “couchrest-type” field equal to the name of the current class. Take the standard set of CouchRest::Database#view options.



47
48
49
50
51
52
53
# File 'lib/couch_surfer/model.rb', line 47

def all opts = {}, &block
  self.design_doc ||= CouchRest::Design.new(default_design_doc)
  unless design_doc_fresh
    refresh_design_doc
  end
  view :all, opts, &block
end

#all_design_doc_versionsObject



285
286
287
288
# File 'lib/couch_surfer/model.rb', line 285

def all_design_doc_versions
  database.documents :startkey => "_design/#{self.to_s}-",
    :endkey => "_design/#{self.to_s}-\u9999"
end

#cast(field, opts = {}) ⇒ Object

Cast a field as another class. The class must be happy to have the field’s primitive type as the argument to it’s constuctur. Classes which inherit from CouchRest::Model are happy to act as sub-objects for any fields that are stored in JSON as object (and therefore are parsed from the JSON as Ruby Hashes).

Example:

class Post < CouchRest::Model

  key_accessor :title, :body, :author

  cast :author, :as => 'Author'

end

post.author.class #=> Author

Using the same example, if a Post should have many Comments, we would declare it like this:

class Post < CouchRest::Model

  key_accessor :title, :body, :author, comments

  cast :author, :as => 'Author'
  cast :comments, :as => ['Comment']

end

post.author.class #=> Author
post.comments.class #=> Array
post.comments.first #=> Comment


105
106
107
108
# File 'lib/couch_surfer/model.rb', line 105

def cast field, opts = {}
  self.casts ||= {}
  self.casts[field.to_s] = opts
end

#cleanup_design_docs!Object

Deletes any non-current design docs that were created by this class. Running this when you’re deployed version of your application is steadily and consistently using the latest code, is the way to clear out old design docs. Running it too early could mean that live code has to regenerate potentially large indexes.



295
296
297
298
299
300
301
302
303
304
305
# File 'lib/couch_surfer/model.rb', line 295

def cleanup_design_docs!
  ddocs = all_design_doc_versions
  ddocs["rows"].each do |row|
    if (row['id'] != design_doc_id)
      database.delete_doc({
        "_id" => row['id'],
        "_rev" => row['value']['rev']
      })
    end
  end
end

#create(attrs = {}) ⇒ Object



38
39
40
41
42
# File 'lib/couch_surfer/model.rb', line 38

def create(attrs = {})
  instance = new(attrs)
  instance.save
  instance
end

#databaseObject

returns the CouchRest::Database instance that this class uses



25
26
27
# File 'lib/couch_surfer/model.rb', line 25

def database
  self.class_database || CouchSurfer::Model.default_database
end

#defaultObject



143
144
145
# File 'lib/couch_surfer/model.rb', line 143

def default
  self.default_obj
end

#first(opts = {}) ⇒ Object

Load the first document that have the “couchrest-type” field equal to the name of the current class.

Returns

Object

The first object instance available

or

Nil

if no instances available

Parameters

opts<Hash>

View options, see CouchRest::Database#view options for more info.



66
67
68
69
# File 'lib/couch_surfer/model.rb', line 66

def first opts = {}
  first_instance = self.all(opts.merge!(:limit => 1))
  first_instance.empty? ? nil : first_instance.first
end

#get(id) ⇒ Object

Load a document from the database by id



30
31
32
33
34
35
36
# File 'lib/couch_surfer/model.rb', line 30

def get id
  doc = database.get id
  new(doc)
rescue
  RestClient::ResourceNotFound
  raise CouchSurfer::RecordNotFound
end

#has_view?(view) ⇒ Boolean

returns stored defaults if the there is a view named this in the design doc

Returns:

  • (Boolean)


270
271
272
273
# File 'lib/couch_surfer/model.rb', line 270

def has_view?(view)
  view = view.to_s
  design_doc && design_doc['views'] && design_doc['views'][view]
end

#key_accessor(*keys) ⇒ Object

Defines methods for reading and writing from fields in the document. Uses key_writer and key_reader internally.



112
113
114
115
# File 'lib/couch_surfer/model.rb', line 112

def key_accessor *keys
  key_writer *keys
  key_reader *keys
end

#key_reader(*keys) ⇒ Object

For each argument key, define a method key that reads the corresponding field on the CouchDB document.



130
131
132
133
134
135
136
137
# File 'lib/couch_surfer/model.rb', line 130

def key_reader *keys
  keys.each do |method|
    key = method.to_s
    define_method method do
      @attributes[method]
    end
  end
end

#key_writer(*keys) ⇒ Object

For each argument key, define a method key= that sets the corresponding field on the CouchDB document.



119
120
121
122
123
124
125
126
# File 'lib/couch_surfer/model.rb', line 119

def key_writer *keys
  keys.each do |method|
    key = method.to_s
    define_method "#{method}=" do |value|
      @attributes[method] = value# ||= nil
    end
  end
end

#list(*args) ⇒ Object



311
312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/couch_surfer/model.rb', line 311

def list(*args)
  unless design_doc_fresh
    refresh_design_doc
  end
  list_name = args.first
  view_name = args.last[:using]
  options = args.last[:options]
  response = design_doc.list(list_name, view_name, options)
  if response['rows']
    response['rows'].collect{|r|new(r['doc'])}
  else
    response
  end
end

#lists(lists_hash) ⇒ Object



307
308
309
# File 'lib/couch_surfer/model.rb', line 307

def lists(lists_hash)
  design_doc['lists'] = lists_hash
end

#set_default(hash) ⇒ Object



139
140
141
# File 'lib/couch_surfer/model.rb', line 139

def set_default hash
  self.default_obj = hash
end

#timestamps!Object

Automatically set updated_at and created_at fields on the document whenever saving occurs. Save in a format parseable by Time and including milliseconds



151
152
153
154
155
156
157
158
159
160
161
# File 'lib/couch_surfer/model.rb', line 151

def timestamps!
  %w(updated_at created_at).each do |method|
    define_method method do
      Time.parse(@attributes[method])
    end
  end
  before(:save) do
    self['updated_at'] = Time.now
    self['created_at'] = self['updated_at'] if new_document?
  end
end

#unique_id(method = nil, &block) ⇒ Object

Name a method that will be called before the document is first saved, which returns a string to be used for the document’s _id. Because CouchDB enforces a constraint that each id must be unique, this can be used to enforce eg: uniq usernames. Note that this id must be globally unique across all document types which share a database, so if you’d like to scope uniqueness to this class, you should use the class name as part of the unique id.



170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/couch_surfer/model.rb', line 170

def unique_id method = nil, &block
  if method
    define_method :set_unique_id do
      self['_id'] ||= self.send(method)
    end
  elsif block
    define_method :set_unique_id do
      uniqid = block.call(self)
      raise ArgumentError, "unique_id block must not return nil" if uniqid.nil?
      self['_id'] ||= uniqid
    end
  end
end

#use_database(db) ⇒ Object

override the CouchSurfer::Model-wide default_database



20
21
22
# File 'lib/couch_surfer/model.rb', line 20

def use_database db
  self.class_database = db
end

#view(name, query = {}, &block) ⇒ Object

Dispatches to any named view.



276
277
278
279
280
281
282
283
# File 'lib/couch_surfer/model.rb', line 276

def view name, query={}, &block
  unless design_doc_fresh
    refresh_design_doc
  end
  query[:raw] = true if query[:reduce]
  raw = query.delete(:raw)
  fetch_view_with_docs(name, query, raw, &block)
end

#view_by(*keys) ⇒ Object

Define a CouchDB view. The name of the view will be the concatenation of by and the keys joined by and

Example views:

class Post
  # view with default options
  # query with Post.by_date
  view_by :date, :descending => true

  # view with compound sort-keys
  # query with Post.by_user_id_and_date
  view_by :user_id, :date

  # view with custom map/reduce functions
  # query with Post.by_tags :reduce => true
  view_by :tags,
    :map =>
      "function(doc) {
        if (doc['couchrest-type'] == 'Post' && doc.tags) {
          doc.tags.forEach(function(tag){
            emit(doc.tag, 1);
          });
        }
      }",
    :reduce =>
      "function(keys, values, rereduce) {
        return sum(values);
      }"
end

view_by :date will create a view defined by this Javascript function:

function(doc) {
  if (doc['couchrest-type'] == 'Post' && doc.date) {
    emit(doc.date, null);
  }
}

It can be queried by calling Post.by_date which accepts all valid options for CouchRest::Database#view. In addition, calling with the :raw => true option will return the view rows themselves. By default Post.by_date will return the documents included in the generated view.

CouchRest::Database#view options can be applied at view definition time as defaults, and they will be curried and used at view query time. Or they can be overridden at query time.

Custom views can be queried with :reduce => true to return reduce results. The default for custom views is to query with :reduce => false.

Views are generated (on a per-model basis) lazily on first-access. This means that if you are deploying changes to a view, the views for that model won’t be available until generation is complete. This can take some time with large databases. Strategies are in the works.

To understand the capabilities of this view system more compeletly, it is recommended that you read the RSpec file at spec/core/model_spec.rb.



246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/couch_surfer/model.rb', line 246

def view_by *keys
  self.design_doc ||= CouchRest::Design.new(default_design_doc)
  opts = keys.pop if keys.last.is_a?(Hash)
  opts ||= {}
  ducktype = opts.delete(:ducktype)
  unless ducktype || opts[:map]
    opts[:guards] ||= []
    opts[:guards].push "(doc['couchrest-type'] == '#{self.to_s}')"
  end
  keys.push opts
  self.design_doc.view_by(*keys)
  self.design_doc_fresh = false
end