Class: Sequel::Model

Inherits:
Object show all
Defined in:
lib/sequel/model.rb,
lib/sequel/model.rb,
lib/sequel/model/base.rb,
lib/sequel/model/hooks.rb,
lib/sequel/model/record.rb,
lib/sequel/model/schema.rb,
lib/sequel/model/caching.rb,
lib/sequel/model/plugins.rb,
lib/sequel/model/relations.rb

Overview

Sequel Models

Models in Sequel are based on the Active Record pattern described by Martin Fowler (www.martinfowler.com/eaaCatalog/activeRecord.html). A model class corresponds to a table or a dataset, and an instance of that class wraps a single record in the model’s underlying dataset.

Model classes are defined as regular Ruby classes:

DB = Sequel('sqlite:/blog.db')
class Post < Sequel::Model
  set_dataset DB[:posts]
end

You can also use the shorthand form:

DB = Sequel('sqlite:/blog.db')
class Post < Sequel::Model(:posts)
end

Model instances

Model instance are identified by a primary key. By default, Sequel assumes the primary key column to be :id. The Model#[] method can be used to fetch records by their primary key:

post = Post[123]

The Model#pk method is used to retrieve the record’s primary key value:

post.pk #=> 123

Sequel models allow you to use any column as a primary key, and even composite keys made from multiple columns:

class Post < Sequel::Model(:posts)
  set_primary_key [:category, :title]
end

post = Post['ruby', 'hello world']
post.pk #=> ['ruby', 'hello world']

You can also define a model class that does not have a primary key, but then you lose the ability to update records.

A model instance can also be fetched by specifying a condition:

post = Post[:title => 'hello world']
post = Post.find {:stamp < 10.days.ago}

Iterating over records

A model class lets you iterate over specific records by acting as a proxy to the underlying dataset. This means that you can use the entire Dataset API to create customized queries that return model instances, e.g.:

Post.filter(:category => 'ruby').each {|post| p post}

You can also manipulate the records in the dataset:

Post.filter {:stamp < 7.days.ago}.delete
Post.filter {:title =~ /ruby/}.update(:category => 'ruby')

Accessing record values

A model instances stores its values as a hash:

post.values #=> {:id => 123, :category => 'ruby', :title => 'hello world'}

You can read the record values as object attributes:

post.id #=> 123
post.title #=> 'hello world'

You can also change record values:

post.title = 'hey there'
post.save

Another way to change values by using the #set method:

post.set(:title => 'hey there')

Creating new records

New records can be created by calling Model.create:

post = Post.create(:title => 'hello world')

Another way is to construct a new instance and save it:

post = Post.new
post.title = 'hello world'
post.save

You can also supply a block to Model.new and Model.create:

post = Post.create {|p| p.title = 'hello world'}

post = Post.new do |p|
  p.title = 'hello world'
  p.save
end

Hooks

You can execute custom code when creating, updating, or deleting records by using hooks. The before_create and after_create hooks wrap record creation. The before_update and after_update wrap record updating. The before_save and after_save wrap record creation and updating. The before_destroy and after_destroy wrap destruction.

Hooks are defined by supplying a block:

class Post < Sequel::Model(:posts)
  after_create do
    set(:created_at => Time.now)
  end

  after_destroy
    author.update_post_count
  end
end

Deleting records

You can delete individual records by calling #delete or #destroy. The only difference between the two methods is that #destroy invokes before_destroy and after_destroy hooks, while #delete does not:

post.delete #=> bypasses hooks
post.destroy #=> runs hooks

Records can also be deleted en-masse by invoking Model.delete and Model.destroy. As stated above, you can specify filters for the deleted records:

Post.filter(:category => 32).delete #=> bypasses hooks
Post.filter(:category => 32).destroy #=> runs hooks

Please note that if Model.destroy is called, each record is deleted separately, but Model.delete deletes all relevant records with a single SQL statement.

Associations

The most straightforward way to define an association in a Sequel model is as a regular instance method:

class Post < Sequel::Model(:posts)
  def author; Author[author_id]; end
end

class Author < Sequel::Model(:authors)
  def posts; Post.filter(:author_id => pk); end
end

Sequel also provides two macros to assist with common types of associations. The one_to_one macro is roughly equivalent to ActiveRecord?‘s belongs_to macro. It defines both getter and setter methods for the association:

class Post < Sequel::Model(:posts)
  one_to_one :author, :from => Author
end

post = Post.create(:name => 'hi!')
post.author = Author[:name => 'Sharon']

The one_to_many macro is roughly equivalent to ActiveRecord’s has_many macro:

class Author < Sequel::Model(:authors)
  one_to_many :posts, :from => Post, :key => :author_id
end

You will have noticed that in some cases the association macros are actually more verbose than hand-coding instance methods. The one_to_one and one_to_many macros also make assumptions (just like ActiveRecord macros) about the database schema which may not be relevant in many cases.

Caching model instances with memcached

Sequel models can be cached using memcached based on their primary keys. The use of memcached can significantly reduce database load by keeping model instances in memory. The set_cache method is used to specify caching:

require 'memcache'
CACHE = MemCache.new 'localhost:11211', :namespace => 'blog'

class Author < Sequel::Model(:authors)
  set_cache CACHE, :ttl => 3600
end

Author[333] # database hit
Author[333] # cache hit

Extending the underlying dataset

The obvious way to add table-wide logic is to define class methods to the model class definition. That way you can define subsets of the underlying dataset, change the ordering, or perform actions on multiple records:

class Post < Sequel::Model(:posts)
  def self.old_posts
    filter {:stamp < 30.days.ago}
  end

  def self.clean_old_posts
    old_posts.delete
  end
end

You can also implement table-wide logic by defining methods on the dataset:

class Post < Sequel::Model(:posts)
  def dataset.old_posts
    filter {:stamp < 30.days.ago}
  end

  def dataset.clean_old_posts
    old_posts.delete
  end
end

This is the recommended way of implementing table-wide operations, and allows you to have access to your model API from filtered datasets as well:

Post.filter(:category => 'ruby').clean_old_posts

Sequel models also provide a short hand notation for filters:

class Post < Sequel::Model(:posts)
  subset(:old_posts) {:stamp < 30.days.ago}
  subset :invisible, :visible => false
end

Defining the underlying schema

Model classes can also be used as a place to define your table schema and control it. The schema DSL is exactly the same provided by Sequel::Schema::Generator:

class Post < Sequel::Model(:posts)
  set_schema do
    primary_key :id
    text :title
    text :category
    foreign_key :author_id, :table => authors
  end
end

You can then create the underlying table, drop it, or recreate it:

Post.table_exists?
Post.create_table
Post.drop_table
Post.create_table! # drops the table if it exists and then recreates it

Defined Under Namespace

Classes: ChainBroken

Constant Summary collapse

VERB_TO_METHOD =

This Hash translates verbs to methodnames used in chain manipulation methods.

{:prepend => :unshift, :append => :push}
ATTR_RE =
/^([a-zA-Z_]\w*)(=)?$/.freeze
ID_POSTFIX =
'_id'.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(values = {}, new_record = false, &block) ⇒ Model

Creates new instance with values set to passed-in Hash.

This method guesses whether the record exists when new_record is set to false.



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/sequel/model/record.rb', line 174

def initialize(values = {}, new_record = false, &block)
  @values = values
  @changed_columns = []

  @new = new_record
  unless @new # determine if it's a new record
    k = self.class.primary_key
    # if there's no primary key for the model class, or
    # @values doesn't contain a primary key value, then 
    # we regard this instance as new.
    @new = (k == nil) || (!(Array === k) && !@values[k])
  end
  
  block[self] if block
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(m, *args) ⇒ Object

:nodoc:



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
# File 'lib/sequel/model/record.rb', line 268

def method_missing(m, *args) #:nodoc:
  if m.to_s =~ ATTR_RE
    att = $1.to_sym
    write = $2 == '='
    
    # check whether the column is legal
    unless columns.include?(att)
      # if read accessor and a value exists for the column, we return it
      if !write && @values.has_key?(att)
        return @values[att]
      end
      
      # otherwise, raise an error
      raise SequelError, "Invalid column (#{att.inspect}) for #{self}"
    end

    # define the column accessor
    Thread.exclusive do
      if write
        model.class_def(m) do |v|
          @values[att] = v
          @changed_columns << att unless @changed_columns.include?(att)
        end
      else
        model.class_def(m) {@values[att]}
      end
    end
    
    # call the accessor
    respond_to?(m) ? send(m, *args) : super(m, *args)
  else
    super(m, *args)
  end
end

Instance Attribute Details

#changed_columnsObject (readonly)

Returns the value of attribute changed_columns.



4
5
6
# File 'lib/sequel/model/record.rb', line 4

def changed_columns
  @changed_columns
end

#valuesObject (readonly)

Returns the value of attribute values.



3
4
5
# File 'lib/sequel/model/record.rb', line 3

def values
  @values
end

Class Method Details

.[](*args) ⇒ Object



257
258
259
260
# File 'lib/sequel/model.rb', line 257

def self.[](*args)
  args = args.first if (args.size == 1)
  dataset[(Hash === args) ? args : primary_key_hash(args)]
end

.after_create(verb = :append, &block) ⇒ Object

Adds block to chain of Hooks for :after_create. It can either be prepended or appended (default).

Returns the chain itself.

Valid verbs are :prepend and :append.



92
93
94
95
# File 'lib/sequel/model/hooks.rb', line 92

def self.after_create(verb = :append, &block)
  hooks[:after_create].send VERB_TO_METHOD.fetch(verb), block if block
  hooks[:after_create]
end

.after_destroy(verb = :append, &block) ⇒ Object

Adds block to chain of Hooks for :after_destroy. It can either be prepended or appended (default).

Returns the chain itself.

Valid verbs are :prepend and :append.



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

def self.after_destroy(verb = :append, &block)
  hooks[:after_destroy].send VERB_TO_METHOD.fetch(verb), block if block
  hooks[:after_destroy]
end

.after_save(verb = :append, &block) ⇒ Object

Adds block to chain of Hooks for :after_save. It can either be prepended or appended (default).

Returns the chain itself.

Valid verbs are :prepend and :append.



82
83
84
85
# File 'lib/sequel/model/hooks.rb', line 82

def self.after_save(verb = :append, &block)
  hooks[:after_save].send VERB_TO_METHOD.fetch(verb), block if block
  hooks[:after_save]
end

.after_update(verb = :append, &block) ⇒ Object

Adds block to chain of Hooks for :after_update. It can either be prepended or appended (default).

Returns the chain itself.

Valid verbs are :prepend and :append.



102
103
104
105
# File 'lib/sequel/model/hooks.rb', line 102

def self.after_update(verb = :append, &block)
  hooks[:after_update].send VERB_TO_METHOD.fetch(verb), block if block
  hooks[:after_update]
end

.allObject

Returns an array containing all model records



304
305
306
# File 'lib/sequel/model.rb', line 304

def self.all
  dataset.all
end

.before_create(verb = :prepend, &block) ⇒ Object

Adds block to chain of Hooks for :before_create. It can either be prepended (default) or appended.

Returns the chain itself.

Valid verbs are :prepend and :append.



51
52
53
54
# File 'lib/sequel/model/hooks.rb', line 51

def self.before_create(verb = :prepend, &block)
  hooks[:before_create].send VERB_TO_METHOD.fetch(verb), block if block
  hooks[:before_create]
end

.before_destroy(verb = :prepend, &block) ⇒ Object

Adds block to chain of Hooks for :before_destroy. It can either be prepended (default) or appended.

Returns the chain itself.

Valid verbs are :prepend and :append.



71
72
73
74
# File 'lib/sequel/model/hooks.rb', line 71

def self.before_destroy(verb = :prepend, &block)
  hooks[:before_destroy].send VERB_TO_METHOD.fetch(verb), block if block
  hooks[:before_destroy]
end

.before_save(verb = :prepend, &block) ⇒ Object

Adds block to chain of Hooks for :before_save. It can either be prepended (default) or appended.

Returns the chain itself.

Valid verbs are :prepend and :append.



41
42
43
44
# File 'lib/sequel/model/hooks.rb', line 41

def self.before_save(verb = :prepend, &block)
  hooks[:before_save].send VERB_TO_METHOD.fetch(verb), block if block
  hooks[:before_save]
end

.before_update(verb = :prepend, &block) ⇒ Object

Adds block to chain of Hooks for :before_update. It can either be prepended (default) or appended.

Returns the chain itself.

Valid verbs are :prepend and :append.



61
62
63
64
# File 'lib/sequel/model/hooks.rb', line 61

def self.before_update(verb = :prepend, &block)
  hooks[:before_update].send VERB_TO_METHOD.fetch(verb), block if block
  hooks[:before_update]
end

.cache_key_from_values(values) ⇒ Object



38
39
40
# File 'lib/sequel/model/caching.rb', line 38

def self.cache_key_from_values(values)
  "#{self}:#{values.join(',')}"
end

.cache_storeObject



30
31
32
# File 'lib/sequel/model/caching.rb', line 30

def self.cache_store
  @cache_store
end

.cache_ttlObject



34
35
36
# File 'lib/sequel/model/caching.rb', line 34

def self.cache_ttl
  @cache_ttl ||= 3600
end

.columnsObject

Returns the columns in the result set in their original order.

See Dataset#columns for more information.



33
34
35
36
# File 'lib/sequel/model/base.rb', line 33

def self.columns
  @columns ||= @dataset.columns or
  raise SequelError, "Could not fetch columns for #{self}"
end

.create(values = {}) ⇒ Object

Creates new instance with values set to passed-in Hash ensuring that new? returns true.



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

def self.create(values = {})
  db.transaction do
    obj = new(values, true)
    obj.save
    obj
  end
end

.create_tableObject

Creates table.



31
32
33
# File 'lib/sequel/model/schema.rb', line 31

def self.create_table
  db.create_table_sql_list(*schema.create_info).each {|s| db << s} 
end

.create_table!Object

Like create_table but invokes drop_table when table_exists? is true.



41
42
43
44
# File 'lib/sequel/model/schema.rb', line 41

def self.create_table!
  drop_table if table_exists?
  create_table
end

.database_opened(db) ⇒ Object

Called when a database is opened in order to automatically associate the first opened database with model classes.



16
17
18
# File 'lib/sequel/model/base.rb', line 16

def self.database_opened(db)
  @db = db if (self == Model) && !@db
end

.datasetObject

Returns the dataset associated with the Model class.



21
22
23
24
# File 'lib/sequel/model/base.rb', line 21

def self.dataset
  @dataset || super_dataset or
  raise SequelError, "No dataset associated with #{self}"
end

.dbObject

Returns the database associated with the Model class.



4
5
6
7
# File 'lib/sequel/model/base.rb', line 4

def self.db
  @db ||= (superclass != Object) && superclass.db or
  raise SequelError, "No database associated with #{self}"
end

.db=(db) ⇒ Object

Sets the database associated with the Model class.



10
11
12
# File 'lib/sequel/model/base.rb', line 10

def self.db=(db)
  @db = db
end

.delete_allObject

Deletes all records.



276
277
278
# File 'lib/sequel/model.rb', line 276

def self.delete_all
  dataset.delete
end

.destroy_allObject

Like delete_all, but invokes before_destroy and after_destroy hooks if used.



271
272
273
274
# File 'lib/sequel/model.rb', line 271

def self.destroy_all
  has_hooks?(:before_destroy) || has_hooks?(:after_destroy) ? \
    dataset.destroy : dataset.delete
end

.drop_tableObject

Drops table.



36
37
38
# File 'lib/sequel/model/schema.rb', line 36

def self.drop_table
  db.execute db.drop_table_sql(table_name)
end

.find(*args, &block) ⇒ Object

Finds a single record according to the supplied filter, e.g.:

Ticket.find :author => 'Sharon' # => record
Ticket.find {:price == 17} # => Dataset


252
253
254
255
# File 'lib/sequel/model.rb', line 252

def self.find(*args, &block)
  dataset.filter(*args, &block).limit(1).first
  # dataset[cond.is_a?(Hash) ? cond : primary_key_hash(cond)]
end

.find_or_create(cond) ⇒ Object

Like find but invokes create with given conditions when record does not exists.



264
265
266
# File 'lib/sequel/model.rb', line 264

def self.find_or_create(cond)
  find(cond) || create(cond)
end

.has_hooks?(key) ⇒ Boolean

Returns:

  • (Boolean)


122
123
124
# File 'lib/sequel/model/hooks.rb', line 122

def self.has_hooks?(key)
  hooks[key] && !hooks[key].empty?
end

.hooksObject

Returns @hooks which is an instance of Hash with its hook identifier (Symbol) as key and the chain of hooks (Array) as value.

If it is not already set it’ll be with an empty set of hooks. This behaviour will change in the future to allow inheritance.

For the time being, you should be able to do:

class A < Sequel::Model(:a)
  before_save { 'Do something...' }
end

class B < A
  @hooks = superclass.hooks.clone
  before_save # => [#<Proc:0x0000c6e8@(example.rb):123>]
end

In this case you should remember that the clone doesn’t create any new instances of your chains, so if you change the chain here it changes in its superclass, too.



31
32
33
# File 'lib/sequel/model/hooks.rb', line 31

def self.hooks
  @hooks ||= Hash.new { |h, k| h[k] = [] }
end

.is(plugin, *args) ⇒ Object Also known as: is_a

Loads a plugin for use with the model class, passing optional arguments to the plugin.



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/sequel/model/plugins.rb', line 8

def is(plugin, *args)
  m = plugin_module(plugin)
  if m.respond_to?(:apply)
    m.apply(self, *args)
  end
  if m.const_defined?("InstanceMethods")
    class_def(:"#{plugin}_opts") {args.first}
    include(m::InstanceMethods)
  end
  if m.const_defined?("ClassMethods")
    meta_def(:"#{plugin}_opts") {args.first}
    metaclass.send(:include, m::ClassMethods)
  end
  if m.const_defined?("DatasetMethods")
    dataset.meta_def(:"#{plugin}_opts") {args.first}
    dataset.metaclass.send(:include, m::DatasetMethods)
  end
end

.is_dataset_magic_method?(m) ⇒ Boolean

Returns:

  • (Boolean)


280
281
282
283
284
285
286
# File 'lib/sequel/model.rb', line 280

def self.is_dataset_magic_method?(m)
  method_name = m.to_s
  Sequel::Dataset::MAGIC_METHODS.each_key do |r|
    return true if method_name =~ r
  end
  false
end

.join(*args) ⇒ Object

Comprehensive description goes here!



298
299
300
301
# File 'lib/sequel/model.rb', line 298

def self.join(*args)
  table_name = dataset.opts[:from].first
  dataset.join(*args).select(table_name.to_sym.ALL)
end

.method_missing(m, *args, &block) ⇒ Object

:nodoc:



288
289
290
291
292
293
294
295
# File 'lib/sequel/model.rb', line 288

def self.method_missing(m, *args, &block) #:nodoc:
  Thread.exclusive do
    if dataset.respond_to?(m) || is_dataset_magic_method?(m)
      instance_eval("def #{m}(*args, &block); dataset.#{m}(*args, &block); end")
    end
  end
  respond_to?(m) ? send(m, *args, &block) : super(m, *args)
end

.no_primary_keyObject

:nodoc:



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

def self.no_primary_key #:nodoc:
  meta_def(:primary_key) {nil}
  meta_def(:primary_key_hash) {|v| raise SequelError, "#{self} does not have a primary key"}
  class_def(:this)      {raise SequelError, "No primary key is associated with this model"}
  class_def(:pk)        {raise SequelError, "No primary key is associated with this model"}
  class_def(:pk_hash)   {raise SequelError, "No primary key is associated with this model"}
  class_def(:cache_key) {raise SequelError, "No primary key is associated with this model"}
end

.one_to_many(name, opts) ⇒ Object

Creates a 1-N relationship by defining an association method, e.g.:

class Book < Sequel::Model(:books)
end

class Author < Sequel::Model(:authors)
  one_to_many :books, :from => Book
  # which is equivalent to
  def books
    Book.filter(:author_id => id)
  end
end

You can also set the foreign key explicitly by including a :key option:

one_to_many :books, :from => Book, :key => :author_id


82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/sequel/model/relations.rb', line 82

def self.one_to_many(name, opts)
  # deprecation
  if opts[:class]
    warn "The :class option has been deprecated. Please use :from instead."
    opts[:from] = opts[:class]
  end
  # deprecation
  if opts[:on]
    warn "The :on option has been deprecated. Please use :key instead."
    opts[:key] = opts[:on]
  end
  
  
  from = opts[:from]
  from || (raise SequelError, "No association source defined (use :from option)")
  key = opts[:key] || (self.to_s + ID_POSTFIX).to_sym
  
  case from
  when Symbol
    class_def(name) {db[from].filter(key => pk)}
  else
    class_def(name) {from.filter(key => pk)}
  end
end

.one_to_one(name, opts) ⇒ Object

Creates a 1-1 relationship by defining an association method, e.g.:

class Session < Sequel::Model(:sessions)
end

class Node < Sequel::Model(:nodes)
  one_to_one :producer, :from => Session
  # which is equivalent to
  def producer
    Session[producer_id] if producer_id
  end
end

You can also set the foreign key explicitly by including a :key option:

one_to_one :producer, :from => Session, :key => :producer_id

The one_to_one macro also creates a setter, which accepts nil, a hash or a model instance, e.g.:

p = Producer[1234]
node = Node[:path => '/']
node.producer = p
node.producer_id #=> 1234


30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/sequel/model/relations.rb', line 30

def self.one_to_one(name, opts)
  # deprecation
  if opts[:class]
    warn "The :class option has been deprecated. Please use :from instead."
    opts[:from] = opts[:class]
  end
  
  from = opts[:from]
  from || (raise SequelError, "No association source defined (use :from option)")
  key = opts[:key] || (name.to_s + ID_POSTFIX).to_sym
  
  setter_name = "#{name}=".to_sym
  
  case from
  when Symbol
    class_def(name) {(k = @values[key]) ? db[from][:id => k] : nil}
  when Sequel::Dataset
    class_def(name) {(k = @values[key]) ? from[:id => k] : nil}
  else
    class_def(name) {(k = @values[key]) ? from[k] : nil}
  end
  class_def(setter_name) do |v|
    case v
    when nil
      set(key => nil)
    when Sequel::Model
      set(key => v.pk)
    when Hash
      set(key => v[:id])
    end
  end

  # define_method name, &eval(ONE_TO_ONE_PROC % [key, from])
end

.plugin_gem(plugin) ⇒ Object

Returns the gem name for the given plugin.



39
40
41
# File 'lib/sequel/model/plugins.rb', line 39

def plugin_gem(plugin)
  "sequel_#{plugin}"
end

.plugin_module(plugin) ⇒ Object

Returns the module for the specified plugin. If the module is not defined, the corresponding plugin gem is automatically loaded.



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

def plugin_module(plugin)
  module_name = plugin.to_s.gsub(/(^|_)(.)/) {$2.upcase}
  if not Sequel::Plugins.const_defined?(module_name)
    require plugin_gem(plugin)
  end
  Sequel::Plugins.const_get(module_name)
end

.primary_keyObject

Returns key for primary key.



44
45
46
# File 'lib/sequel/model/record.rb', line 44

def self.primary_key
  :id
end

.primary_key_hash(value) ⇒ Object

Returns primary key attribute hash.



49
50
51
# File 'lib/sequel/model/record.rb', line 49

def self.primary_key_hash(value)
  {:id => value}
end

.recreate_tableObject

Deprecated, use create_table! instead.



47
48
49
50
# File 'lib/sequel/model/schema.rb', line 47

def self.recreate_table
  warn "Model.recreate_table is deprecated. Please use Model.create_table! instead."
  create_table!
end

.schemaObject

Returns table schema for direct descendant of Model.



16
17
18
# File 'lib/sequel/model/schema.rb', line 16

def self.schema
  @schema || ((superclass != Model) && (superclass.schema))
end

.serialize(*columns) ⇒ Object

Serializes column with YAML or through marshalling.



64
65
66
67
68
69
70
71
72
73
# File 'lib/sequel/model/base.rb', line 64

def self.serialize(*columns)
  format = columns.pop[:format] if Hash === columns.last
  format ||= :yaml
  
  @transform = columns.inject({}) do |m, c|
    m[c] = format
    m
  end
  @dataset.transform(@transform) if @dataset
end

.set_cache(store, opts = {}) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# File 'lib/sequel/model/caching.rb', line 3

def self.set_cache(store, opts = {})
  @cache_store = store
  if (ttl = opts[:ttl])
    set_cache_ttl(ttl)
  end
  
  meta_def(:[]) do |*args|
    if (args.size == 1) && (Hash === (h = args.first))
      return dataset[h]
    end
    
    unless obj = @cache_store.get(cache_key_from_values(args))
      obj = dataset[primary_key_hash((args.size == 1) ? args.first : args)]
      @cache_store.set(cache_key_from_values(args), obj, cache_ttl)
    end
    obj
  end
  
  class_def(:set) {|v| store.delete(cache_key); super}
  class_def(:save) {store.delete(cache_key); super}
  class_def(:delete) {store.delete(cache_key); super}
end

.set_cache_ttl(ttl) ⇒ Object



26
27
28
# File 'lib/sequel/model/caching.rb', line 26

def self.set_cache_ttl(ttl)
  @cache_ttl = ttl
end

.set_dataset(ds) ⇒ Object

Sets the dataset associated with the Model class.



39
40
41
42
43
44
# File 'lib/sequel/model/base.rb', line 39

def self.set_dataset(ds)
  @db = ds.db
  @dataset = ds
  @dataset.set_model(self)
  @dataset.transform(@transform) if @transform
end

.set_primary_key(*key) ⇒ Object

Sets primary key, regular and composite are possible.

Example:

class Tagging < Sequel::Model(:taggins)
  # composite key
  set_primary_key :taggable_id, :tag_id
end

class Person < Sequel::Model(:person)
  # regular key
  set_primary_key :person_id
end

You can even set it to nil!



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/sequel/model/record.rb', line 67

def self.set_primary_key(*key)
  # if k is nil, we go to no_primary_key
  if key.empty? || (key.size == 1 && key.first == nil)
    return no_primary_key
  end
  
  # backwards compat
  key = (key.length == 1) ? key[0] : key.flatten

  # redefine primary_key
  meta_def(:primary_key) {key}
  
  unless key.is_a? Array # regular primary key
    class_def(:this) do
      @this ||= dataset.filter(key => @values[key]).limit(1).naked
    end
    class_def(:pk) do
      @pk ||= @values[key]
    end
    class_def(:pk_hash) do
      @pk ||= {key => @values[key]}
    end
    class_def(:cache_key) do
      pk = @values[key] || (raise SequelError, 'no primary key for this record')
      @cache_key ||= "#{self.class}:#{pk}"
    end
    meta_def(:primary_key_hash) do |v|
      {key => v}
    end
  else # composite key
    exp_list = key.map {|k| "#{k.inspect} => @values[#{k.inspect}]"}
    block = eval("proc {@this ||= self.class.dataset.filter(#{exp_list.join(',')}).limit(1).naked}")
    class_def(:this, &block)
    
    exp_list = key.map {|k| "@values[#{k.inspect}]"}
    block = eval("proc {@pk ||= [#{exp_list.join(',')}]}")
    class_def(:pk, &block)
    
    exp_list = key.map {|k| "#{k.inspect} => @values[#{k.inspect}]"}
    block = eval("proc {@this ||= {#{exp_list.join(',')}}}")
    class_def(:pk_hash, &block)
    
    exp_list = key.map {|k| '#{@values[%s]}' % k.inspect}.join(',')
    block = eval('proc {@cache_key ||= "#{self.class}:%s"}' % exp_list)
    class_def(:cache_key, &block)

    meta_def(:primary_key_hash) do |v|
      key.inject({}) {|m, i| m[i] = v.shift; m}
    end
  end
end

.set_schema(name = nil, &block) ⇒ Object

Defines a table schema (see Schema::Generator for more information).

This is only needed if you want to use the create_table or drop_table methods.



7
8
9
10
11
12
13
# File 'lib/sequel/model/schema.rb', line 7

def self.set_schema(name = nil, &block)
  name ? set_dataset(db[name]) : name = table_name
  @schema = Schema::Generator.new(db, name, &block)
  if @schema.primary_key_name
    set_primary_key @schema.primary_key_name
  end
end

.subset(name, *args, &block) ⇒ Object

Defines a method that returns a filtered dataset.



243
244
245
# File 'lib/sequel/model.rb', line 243

def self.subset(name, *args, &block)
  dataset.meta_def(name) {filter(*args, &block)}
end

.super_datasetObject

:nodoc:



26
27
28
# File 'lib/sequel/model/base.rb', line 26

def self.super_dataset # :nodoc:
  superclass.dataset if superclass and superclass.respond_to? :dataset
end

.table_exists?Boolean

Returns true if table exists, false otherwise.

Returns:

  • (Boolean)


26
27
28
# File 'lib/sequel/model/schema.rb', line 26

def self.table_exists?
  db.table_exists?(table_name)
end

.table_nameObject

Returns name of table.



21
22
23
# File 'lib/sequel/model/schema.rb', line 21

def self.table_name
  dataset.opts[:from].first
end

Instance Method Details

#==(obj) ⇒ Object

Compares model instances by values.



34
35
36
# File 'lib/sequel/model/record.rb', line 34

def ==(obj)
  (obj.class == model) && (obj.values == @values)
end

#===(obj) ⇒ Object

Compares model instances by pkey.



39
40
41
# File 'lib/sequel/model/record.rb', line 39

def ===(obj)
  (obj.class == model) && (obj.pk == pk)
end

#[](column) ⇒ Object

Returns value of attribute.



7
8
9
# File 'lib/sequel/model/record.rb', line 7

def [](column)
  @values[column]
end

#[]=(column, value) ⇒ Object

Sets value of attribute and marks the column as changed.



11
12
13
14
# File 'lib/sequel/model/record.rb', line 11

def []=(column, value)
  @values[column] = value
  @changed_columns << column unless @changed_columns.include?(column)
end

#cache_keyObject

Returns a key unique to the underlying record for caching



144
145
146
147
# File 'lib/sequel/model/record.rb', line 144

def cache_key
  pk = @values[:id] || (raise SequelError, 'no primary key for this record')
  @cache_key ||= "#{self.class}:#{pk}"
end

#columnsObject

Returns the columns associated with the object’s Model class.



59
60
61
# File 'lib/sequel/model/base.rb', line 59

def columns
  model.columns
end

#datasetObject

Returns the dataset assoiated with the object’s Model class.

See Dataset for more information.



54
55
56
# File 'lib/sequel/model/base.rb', line 54

def dataset
  model.dataset
end

#dbObject

Returns the database assoiated with the object’s Model class.



47
48
49
# File 'lib/sequel/model/base.rb', line 47

def db
  @db ||= model.db
end

#deleteObject

Deletes and returns self.



261
262
263
264
# File 'lib/sequel/model/record.rb', line 261

def delete
  this.delete
  self
end

#destroyObject

Like delete but runs hooks before and after delete.



252
253
254
255
256
257
258
# File 'lib/sequel/model/record.rb', line 252

def destroy
  db.transaction do
    run_hooks(:before_destroy)
    delete
    run_hooks(:after_destroy)
  end
end

#each(&block) ⇒ Object

Enumerates through all attributes.

Example:

Ticket.find(7).each { |k, v| puts "#{k} => #{v}" }


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

def each(&block)
  @values.each(&block)
end

#exists?Boolean

Returns true when current instance exists, false otherwise.

Returns:

  • (Boolean)


196
197
198
# File 'lib/sequel/model/record.rb', line 196

def exists?
  this.count > 0
end

#idObject

Returns value for :id attribute.



29
30
31
# File 'lib/sequel/model/record.rb', line 29

def id
  @values[:id]
end

#keysObject

Returns attribute names.



24
25
26
# File 'lib/sequel/model/record.rb', line 24

def keys
  @values.keys
end

#new?Boolean

Returns true if the current instance represents a new record.

Returns:

  • (Boolean)


191
192
193
# File 'lib/sequel/model/record.rb', line 191

def new?
  @new
end

#pkObject

Returns the primary key value identifying the model instance. Stock implementation.



161
162
163
# File 'lib/sequel/model/record.rb', line 161

def pk
  @pk ||= @values[:id]
end

#pk_hashObject

Returns a hash identifying the model instance. Stock implementation.



166
167
168
# File 'lib/sequel/model/record.rb', line 166

def pk_hash
  @pk_hash ||= {:id => @values[:id]}
end

#pkeyObject

Returns value for primary key.



155
156
157
158
# File 'lib/sequel/model/record.rb', line 155

def pkey
  warn "Model#pkey is deprecated. Please use Model#pk instead."
  @pkey ||= @values[self.class.primary_key]
end

#primary_keyObject

Returns primary key column(s) for object’s Model class.



150
151
152
# File 'lib/sequel/model/record.rb', line 150

def primary_key
  @primary_key ||= self.class.primary_key
end

#refreshObject

Reloads values from database and returns self.



246
247
248
249
# File 'lib/sequel/model/record.rb', line 246

def refresh
  @values = this.first || raise(SequelError, "Record not found")
  self
end

#run_hooks(key) ⇒ Object

Evaluates specified chain of Hooks through instance_eval.



118
119
120
# File 'lib/sequel/model/hooks.rb', line 118

def run_hooks(key)
  model.hooks[key].each {|h| instance_eval(&h)}
end

#save(*columns) ⇒ Object

Creates or updates the associated record. This method can also accept a list of specific columns to update.



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/sequel/model/record.rb', line 202

def save(*columns)
  run_hooks(:before_save)
  if @new
    run_hooks(:before_create)
    iid = model.dataset.insert(@values)
    # if we have a regular primary key and it's not set in @values,
    # we assume it's the last inserted id
    if (pk = primary_key) && !(Array === pk) && !@values[pk]
      @values[pk] = iid
    end
    if pk
      @this = nil # remove memoized this dataset
      refresh
    end
    @new = false
    run_hooks(:after_create)
  else
    run_hooks(:before_update)
    if columns.empty?
      this.update(@values)
      @changed_columns = []
    else # update only the specified columns
      this.update(@values.reject {|k, v| !columns.include?(k)})
      @changed_columns.reject! {|c| columns.include?(c)}
    end
    run_hooks(:after_update)
  end
  run_hooks(:after_save)
  self
end

#save_changesObject

Saves only changed columns or does nothing if no columns are marked as chanaged.



235
236
237
# File 'lib/sequel/model/record.rb', line 235

def save_changes
  save(*@changed_columns) unless @changed_columns.empty?
end

#set(values) ⇒ Object

Updates and saves values to database from the passed-in Hash.



240
241
242
243
# File 'lib/sequel/model/record.rb', line 240

def set(values)
  this.update(values)
  values.each {|k, v| @values[k] = v}
end

#thisObject

Returns (naked) dataset bound to current instance.



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

def this
  @this ||= self.class.dataset.filter(:id => @values[:id]).limit(1).naked
end