Class: Sequel::Model

Inherits:
Object
  • Object
show all
Extended by:
Associations
Includes:
Validation
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/validations.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
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
  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
  after_create do
    set(:created_at => Time.now)
  end

  after_destroy do
    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

Sequel provides macros for the three most common types of associations: many_to_one, one_to_many and many_to_many (equivalent to ActiveRecord’s belongs_to, has_many and has_and_belongs_to_many).

Associations are defined in similar fashion to ActiveRecord:

class Post < Sequel::Model
  belongs_to :author
end

class Author < Sequel::Model
  has_many :posts
end

Another way to define an association in a Sequel model is as a regular instance method:

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

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

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
  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
  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
  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
  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
  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

Modules: Associations

Constant Summary collapse

HOOKS =
[
  :after_initialize,
  :before_create,
  :after_create,
  :before_update,
  :after_update,
  :before_save,
  :after_save,
  :before_destroy,
  :after_destroy
]
HOOK_METHOD_STR =

Some fancy code generation here in order to define the hook class methods…

%Q{
  def self.%s(method = nil, &block)
    unless block
      (raise SequelError, 'No hook method specified') unless method
      block = proc {send method}
    end
    add_hook(%s, &block)
  end
}
ATTR_RE =
/^([a-zA-Z_]\w*)(=)?$/.freeze
EQUAL_SIGN =
'='.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Associations

all_association_reflections, associate, association_reflection, associations, many_to_many, many_to_one, one_to_many, one_to_one

Constructor Details

#initialize(values = nil, from_db = 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.



190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/sequel_model/record.rb', line 190

def initialize(values = nil, from_db = false, &block)
  @changed_columns = []
  unless from_db
    @values = {}
    if values
      values.each do |k, v| m = :"#{k}="
        if respond_to?(m)
          send(m, v)
          values.delete(k)
        end
      end
      values.inject(@values) {|m, kv| m[kv[0].to_sym] = kv[1]; m}
      # @values.merge!(values)
    end
  else
    @values = values || {}
  end

  k = primary_key
  @new = !from_db
  
  block[self] if block
  after_initialize
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(m, *args) ⇒ Object

:nodoc:



304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
# File 'lib/sequel_model/record.rb', line 304

def method_missing(m, *args) #:nodoc:
  if m.to_s =~ ATTR_RE
    att = $1.to_sym
    write = $2 == EQUAL_SIGN
    
    # check whether the column is legal
    unless @values.has_key?(att) || columns.include?(att)
      raise Error, "Invalid column (#{att.inspect}) for #{self}"
    end

    # define the column accessor
    Thread.exclusive do
      if write
        model.class_def(m) {|v| self[att] = v}
      else
        model.class_def(m) {self[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

TODO: doc



264
265
266
267
268
269
270
# File 'lib/sequel_model.rb', line 264

def self.[](*args)
  args = args.first if (args.size == 1)
  if args === true || args === false
    raise Error::InvalidFilter, "Did you mean to supply a hash?"
  end
  dataset[(Hash === args) ? args : primary_key_hash(args)]
end

.add_hook(hook, &block) ⇒ Object

:nodoc:



38
39
40
41
42
43
44
45
# File 'lib/sequel_model/hooks.rb', line 38

def self.add_hook(hook, &block) #:nodoc:
  chain = hooks[hook]
  chain << block
  define_method(hook) do 
    return false if super == false
    chain.each {|h| break false if instance_eval(&h) == false}
  end
end

.allObject

Returns an array containing all of the models records.



319
320
321
# File 'lib/sequel_model.rb', line 319

def self.all
  dataset.all
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.



55
56
57
58
# File 'lib/sequel_model/base.rb', line 55

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

.create(values = {}, &block) ⇒ 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 = {}, &block)
  db.transaction do
    obj = new(values, &block)
    obj.save
    obj
  end
end

.create_tableObject

Creates table.



33
34
35
# File 'lib/sequel_model/schema.rb', line 33

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

.create_table!Object

Like create_table but invokes drop_table when table_exists? is true.



43
44
45
46
# File 'lib/sequel_model/schema.rb', line 43

def self.create_table!
  drop_table if table_exists?
  create_table
end

.create_with_params(params) ⇒ Object Also known as: create_with



150
151
152
153
154
# File 'lib/sequel_model/record.rb', line 150

def create_with_params(params)
  record = new
  record.update_with_params(params)
  record
end

.database_opened(db) ⇒ Object

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



19
20
21
# File 'lib/sequel_model/base.rb', line 19

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

.datasetObject

Returns the dataset associated with the Model class.



29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/sequel_model/base.rb', line 29

def self.dataset
  unless @dataset
    if ds = super_dataset
      set_dataset(ds.clone)
    elsif !name.empty?
      set_dataset(db[implicit_table_name])
    else
      raise Error, "No dataset associated with #{self}"
    end
  end
  @dataset
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 Error, "No database associated with #{self}"
end

.db=(db) ⇒ Object

Sets the database associated with the Model class.



10
11
12
13
14
15
# File 'lib/sequel_model/base.rb', line 10

def self.db=(db)
  @db = db
  if @dataset
    set_dataset(db[table_name])
  end
end

.def_hook_method(m) ⇒ Object

:nodoc:



26
27
28
# File 'lib/sequel_model/hooks.rb', line 26

def self.def_hook_method(m) #:nodoc:
  instance_eval(HOOK_METHOD_STR % [m.to_s, m.inspect])
end

.delete_allObject

Deletes all records in the model’s table.



286
287
288
# File 'lib/sequel_model.rb', line 286

def self.delete_all
  dataset.delete
end

.destroy_allObject

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



291
292
293
# File 'lib/sequel_model.rb', line 291

def self.destroy_all
  dataset.destroy
end

.drop_tableObject

Drops table.



38
39
40
# File 'lib/sequel_model/schema.rb', line 38

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

.fetch(*args) ⇒ Object

TODO: doc



273
274
275
# File 'lib/sequel_model.rb', line 273

def self.fetch(*args)
  db.fetch(*args).set_model(self)
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


259
260
261
# File 'lib/sequel_model.rb', line 259

def self.find(*args, &block)
  dataset.filter(*args, &block).first
end

.find_or_create(cond) ⇒ Object

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



279
280
281
# File 'lib/sequel_model.rb', line 279

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

.has_hooks?(key) ⇒ Boolean

Returns true if the model class or any of its ancestors have defined hooks for the given hook key. Notice that this method cannot detect hooks defined using overridden methods.

Returns:

  • (Boolean)


50
51
52
53
# File 'lib/sequel_model/hooks.rb', line 50

def self.has_hooks?(key)
  has = hooks[key] && !hooks[key].empty?
  has || ((self != Model) && superclass.has_hooks?(key))
end

.hooksObject

Returns the hooks hash for the model class.



34
35
36
# File 'lib/sequel_model/hooks.rb', line 34

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

.implicit_table_nameObject

Returns the implicit table name for the model class.



24
25
26
# File 'lib/sequel_model/base.rb', line 24

def self.implicit_table_name
  name.demodulize.underscore.pluralize.to_sym
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
26
27
28
# 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")
    unless @dataset
      raise Sequel::Error, "Plugin cannot be applied because the model class has no dataset"
    end
    dataset.meta_def(:"#{plugin}_opts") {args.first}
    dataset.metaclass.send(:include, m::DatasetMethods)
  end
end

.is_dataset_magic_method?(m) ⇒ Boolean

Returns:

  • (Boolean)


295
296
297
298
299
300
301
# File 'lib/sequel_model.rb', line 295

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

TODO: Comprehensive description goes here!



313
314
315
316
# File 'lib/sequel_model.rb', line 313

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

.load(values) ⇒ Object

Initializes a model instance as an existing record. This constructor is used by Sequel to initialize model instances when fetching records.



217
218
219
# File 'lib/sequel_model/record.rb', line 217

def self.load(values)
  new(values, true)
end

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

:nodoc:



303
304
305
306
307
308
309
310
# File 'lib/sequel_model.rb', line 303

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 Error, "#{self} does not have a primary key"}
  class_def(:this)      {raise Error, "No primary key is associated with this model"}
  class_def(:pk)        {raise Error, "No primary key is associated with this model"}
  class_def(:pk_hash)   {raise Error, "No primary key is associated with this model"}
  class_def(:cache_key) {raise Error, "No primary key is associated with this model"}
end

.plugin_gem(plugin) ⇒ Object

Returns the gem name for the given plugin.



42
43
44
# File 'lib/sequel_model/plugins.rb', line 42

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.



33
34
35
36
37
38
39
# File 'lib/sequel_model/plugins.rb', line 33

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

.schemaObject

Returns table schema for direct descendant of Model.



18
19
20
# File 'lib/sequel_model/schema.rb', line 18

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

.serialize(*columns) ⇒ Object

Serializes column with YAML or through marshalling.



86
87
88
89
90
91
92
93
94
95
# File 'lib/sequel_model/base.rb', line 86

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.



61
62
63
64
65
66
# File 'lib/sequel_model/base.rb', line 61

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
  # composite key
  set_primary_key :taggable_id, :tag_id
end

class Person < Sequel::Model
  # 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 Error, '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
14
15
# File 'lib/sequel_model/schema.rb', line 7

def self.set_schema(name = nil, &block)
  if name
    set_dataset(db[name])
  end
  @schema = Schema::Generator.new(db, &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.



250
251
252
# File 'lib/sequel_model.rb', line 250

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

.super_datasetObject

def self.dataset

@dataset ||= super_dataset ||
  (!(n = name).empty? && db[n.underscore.pluralize.to_sym]) ||
  (raise Error, "No dataset associated with #{self}")

end



48
49
50
# File 'lib/sequel_model/base.rb', line 48

def self.super_dataset # :nodoc:
  superclass.dataset if (superclass != Sequel::Model) && superclass.respond_to?(:dataset)
end

.table_exists?Boolean

Returns true if table exists, false otherwise.

Returns:

  • (Boolean)


28
29
30
# File 'lib/sequel_model/schema.rb', line 28

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

.table_nameObject

Returns name of table.



23
24
25
# File 'lib/sequel_model/schema.rb', line 23

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)
  @changed_columns << column unless @changed_columns.include?(column)
  @values[column] = value
end

#cache_keyObject

Returns a key unique to the underlying record for caching



164
165
166
167
# File 'lib/sequel_model/record.rb', line 164

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

#columnsObject

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



81
82
83
# File 'lib/sequel_model/base.rb', line 81

def columns
  model.columns
end

#datasetObject

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

See Dataset for more information.



76
77
78
# File 'lib/sequel_model/base.rb', line 76

def dataset
  model.dataset
end

#dbObject

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



69
70
71
# File 'lib/sequel_model/base.rb', line 69

def db
  @db ||= model.db
end

#deleteObject

Deletes and returns self.



296
297
298
299
# File 'lib/sequel_model/record.rb', line 296

def delete
  this.delete
  self
end

#destroyObject

Like delete but runs hooks before and after delete.



287
288
289
290
291
292
293
# File 'lib/sequel_model/record.rb', line 287

def destroy
  db.transaction do
    before_destroy
    delete
    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)


228
229
230
# File 'lib/sequel_model/record.rb', line 228

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

#inspectObject

Returns a string representation of the model instance including the class name and values.



245
246
247
# File 'lib/sequel_model.rb', line 245

def inspect
  "#<%s @values=%s>" % [self.class.name, @values.inspect]
end

#keysObject

Returns attribute names.



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

def keys
  @values.keys
end

#new?Boolean Also known as: new_record?

Returns true if the current instance represents a new record.

Returns:

  • (Boolean)


222
223
224
# File 'lib/sequel_model/record.rb', line 222

def new?
  @new
end

#pkObject

Returns the primary key value identifying the model instance. If the model’s primary key is changed (using #set_primary_key or #no_primary_key) this method is redefined accordingly.



177
178
179
# File 'lib/sequel_model/record.rb', line 177

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

#pk_hashObject

Returns a hash identifying the model instance. Stock implementation.



182
183
184
# File 'lib/sequel_model/record.rb', line 182

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

#primary_keyObject

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



170
171
172
# File 'lib/sequel_model/record.rb', line 170

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

#refreshObject Also known as: reload

Reloads values from database and returns self.



280
281
282
283
# File 'lib/sequel_model/record.rb', line 280

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

#save(*args) ⇒ Object

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



234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/sequel_model/record.rb', line 234

def save(*columns)
  before_save
  if @new
    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
    after_create
  else
    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
    after_update
  end
  after_save
  self
end

#save!Object

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



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/sequel_model/validations.rb', line 9

def save(*columns)
  before_save
  if @new
    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
    after_create
  else
    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
    after_update
  end
  after_save
  self
end

#save_changesObject

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



267
268
269
# File 'lib/sequel_model/record.rb', line 267

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

#set(values) ⇒ Object Also known as: update

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



272
273
274
275
276
# File 'lib/sequel_model/record.rb', line 272

def set(values)
  v = values.inject({}) {|m, kv| m[kv[0].to_sym] = kv[1]; m}
  this.update(v)
  v.each {|k, v| @values[k] = v}
end

#thisObject

Returns (naked) dataset bound to current instance.



159
160
161
# File 'lib/sequel_model/record.rb', line 159

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

#update_with_params(values) ⇒ Object Also known as: update_with

Updates the instance with the supplied values with support for virtual attributes, ignoring any values for which no setter method is available.



140
141
142
143
144
145
146
# File 'lib/sequel_model/record.rb', line 140

def update_with_params(values)
  c = columns
  values.each do |k, v| m = :"#{k}="
    send(m, v) if c.include?(k) || respond_to?(m)
  end
  save_changes
end