sequel 1.3 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +127 -0
- data/COPYING +1 -0
- data/README +5 -4
- data/Rakefile +78 -25
- data/lib/sequel.rb +1 -2
- data/lib/sequel_model.rb +324 -0
- data/lib/sequel_model/associations.rb +351 -0
- data/lib/sequel_model/base.rb +120 -0
- data/lib/sequel_model/caching.rb +42 -0
- data/lib/sequel_model/eager_loading.rb +169 -0
- data/lib/sequel_model/hooks.rb +55 -0
- data/lib/sequel_model/plugins.rb +47 -0
- data/lib/sequel_model/pretty_table.rb +73 -0
- data/lib/sequel_model/record.rb +336 -0
- data/lib/sequel_model/schema.rb +48 -0
- data/lib/sequel_model/validations.rb +15 -0
- data/spec/associations_spec.rb +712 -0
- data/spec/base_spec.rb +239 -0
- data/spec/caching_spec.rb +150 -0
- data/spec/deprecated_relations_spec.rb +153 -0
- data/spec/eager_loading_spec.rb +260 -0
- data/spec/hooks_spec.rb +269 -0
- data/spec/model_spec.rb +543 -0
- data/spec/plugins_spec.rb +74 -0
- data/spec/rcov.opts +4 -0
- data/spec/record_spec.rb +593 -0
- data/spec/schema_spec.rb +69 -0
- data/spec/spec.opts +5 -0
- data/spec/spec_helper.rb +43 -0
- data/spec/validations_spec.rb +246 -0
- metadata +90 -56
data/CHANGELOG
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
=== 1.4.0 (2008-04-08)
|
2
|
+
|
3
|
+
* Don't mark a column as changed unless the new value is different from the current value (tamas.denes, jeremyevans) (#203).
|
4
|
+
|
5
|
+
* Switch gem name from "sequel_model" to just "sequel", which required large version bump (jeremyevans).
|
6
|
+
|
7
|
+
* Add :select option to many_to_many associations, default to selecting only the associated model table and not the join table (jeremyevans) (#208).
|
8
|
+
|
9
|
+
* Add :reciprocal one_to_many association option, for setting corresponding many_to_one instance variable (jeremyevans).
|
10
|
+
|
11
|
+
* Add eager loading implementation (jeremyevans).
|
12
|
+
|
13
|
+
* Change *_to_many associations so that the all associations are considered :cache=>true (jeremyevans).
|
14
|
+
|
15
|
+
* Fix associations with block arguments and :cache=>true (jeremyevans).
|
16
|
+
|
17
|
+
=== 0.5.0.2 (2008-03-12)
|
18
|
+
|
19
|
+
* More fixes for Model.associate to accept strings and symbols as class references.
|
20
|
+
|
21
|
+
=== 0.5.0.1 (2008-03-09)
|
22
|
+
|
23
|
+
* Fixed Model.associate to accept class and class name in :class option.
|
24
|
+
|
25
|
+
=== 0.5 (2008-03-08)
|
26
|
+
|
27
|
+
* Merged new associations branch into trunk.
|
28
|
+
|
29
|
+
* Rewrote RDoc for associations.
|
30
|
+
|
31
|
+
* Added has_and_belongs_to_many alias for many_to_many.
|
32
|
+
|
33
|
+
* Added support for optional dataset block.
|
34
|
+
|
35
|
+
* Added :order option to order association datasets.
|
36
|
+
|
37
|
+
* Added :cache option to return and cache array of objects for association.
|
38
|
+
|
39
|
+
* Changed one_to_many, many_to_many associations to return dataset by default.
|
40
|
+
|
41
|
+
* Added has_many, belongs_to aliases.
|
42
|
+
|
43
|
+
* Refactored associations code.
|
44
|
+
|
45
|
+
* Added deprecations for old-style relations.
|
46
|
+
|
47
|
+
* Completed specs for new associations code.
|
48
|
+
|
49
|
+
* New associations code by Jeremy Evans (replaces relations code.)
|
50
|
+
|
51
|
+
=== 0.4.2 (2008-02-29)
|
52
|
+
|
53
|
+
* Fixed one_to_many implicit key to work correctly for namespaced classes (#167).
|
54
|
+
|
55
|
+
* Fixed Model.db= to affect the underlying dataset (#183).
|
56
|
+
|
57
|
+
* Fixed Model.implicit_table_name to disregard namespaces.
|
58
|
+
|
59
|
+
=== 0.4.1 (2008-02-10)
|
60
|
+
|
61
|
+
* Implemented Model#inspect (#151).
|
62
|
+
|
63
|
+
* Changed Model#method_missing to short-circuit and bypass checking #columns if the values hash already contains the relevant column (#150).
|
64
|
+
|
65
|
+
* Updated to reflect changes in sequel_core (Dataset#clone_merge renamed to Dataset#clone).
|
66
|
+
|
67
|
+
=== 0.4 (2008-02-05)
|
68
|
+
|
69
|
+
* Fixed Model#set to work with string keys (#143).
|
70
|
+
|
71
|
+
* Fixed Model.create to correctly initialize instances marked as new (#135).
|
72
|
+
|
73
|
+
* Fixed Model#initialize to convert string keys into symbol keys. This also fixes problem with validating objects initialized with string keys (#136).
|
74
|
+
|
75
|
+
=== 0.3.3 (2008-01-25)
|
76
|
+
|
77
|
+
* Finalized support for virtual attributes.
|
78
|
+
|
79
|
+
=== 0.3.2.1 (2008-01-24)
|
80
|
+
|
81
|
+
* Fixed Model.dataset to correctly set the dataset if using implicit naming or inheriting the superclass dataset (thanks celldee).
|
82
|
+
|
83
|
+
=== 0.3.2 (2008-01-24)
|
84
|
+
|
85
|
+
* Added Model#update_with_params method with support for virtual attributes and auto-filtering of unrelated parameters, and changed Model.create_with_params to support virtual attributes (#128).
|
86
|
+
|
87
|
+
* Cleaned up gem spec (#132).
|
88
|
+
|
89
|
+
* Removed validations code. Now relying on validations in assistance gem.
|
90
|
+
|
91
|
+
=== 0.3.1 (2008-01-21)
|
92
|
+
|
93
|
+
* Changed Model.dataset to use inflector to pluralize the class name into the table name. Works in similar fashion to table names in AR or DM.
|
94
|
+
|
95
|
+
=== 0.3 (2008-01-18)
|
96
|
+
|
97
|
+
* Implemented Validatable::Errors class.
|
98
|
+
|
99
|
+
* Added Model#reload as alias to Model#refresh.
|
100
|
+
|
101
|
+
* Changed Model.create to accept a block (#126).
|
102
|
+
|
103
|
+
* Rewrote validations.
|
104
|
+
|
105
|
+
* Fixed Model#initialize to accept nil values (#115).
|
106
|
+
|
107
|
+
=== 0.2 (2008-01-02)
|
108
|
+
|
109
|
+
* Removed deprecated Model.recreate_table method.
|
110
|
+
|
111
|
+
* Removed deprecated :class and :on options from one_to_many macro.
|
112
|
+
|
113
|
+
* Removed deprecated :class option from one_to_one macro.
|
114
|
+
|
115
|
+
* Removed deprecated Model#pkey method.
|
116
|
+
|
117
|
+
* Changed dependency to sequel_core.
|
118
|
+
|
119
|
+
* Removed examples from sequel core.
|
120
|
+
|
121
|
+
* Additional specs. We're now at 100% coverage.
|
122
|
+
|
123
|
+
* Refactored hooks code. Hooks are now inheritable, and can be defined by supplying a block or a method name, or by overriding the hook instance method. Hook chains can now be broken by returning false (#111, #112).
|
124
|
+
|
125
|
+
=== 0.1 (2007-12-30)
|
126
|
+
|
127
|
+
* Moved model code from sequel into separate model sub-project.
|
data/COPYING
CHANGED
data/README
CHANGED
@@ -7,18 +7,19 @@ Sequel makes it easy to deal with multiple records without having to break your
|
|
7
7
|
== Resources
|
8
8
|
|
9
9
|
* {Project page}[http://code.google.com/p/ruby-sequel/]
|
10
|
-
* {Source code}[http://
|
10
|
+
* {Source code}[http://github.com/jeremyevans/sequel]
|
11
11
|
* {Bug tracking}[http://code.google.com/p/ruby-sequel/issues/list]
|
12
12
|
* {Google group}[http://groups.google.com/group/sequel-talk]
|
13
13
|
* {RubyForge page}[http://rubyforge.org/projects/sequel/]
|
14
|
+
* {API RDoc}[http://sequel.rubyforge.org]
|
14
15
|
|
15
16
|
To check out the source code:
|
16
17
|
|
17
|
-
|
18
|
+
git clone git://github.com/jeremyevans/sequel.git
|
18
19
|
|
19
20
|
=== Contact
|
20
21
|
|
21
|
-
If you have any comments or suggestions please
|
22
|
+
If you have any comments or suggestions please post to the Google group.
|
22
23
|
|
23
24
|
== Installation
|
24
25
|
|
@@ -208,7 +209,7 @@ Or calculate a sum:
|
|
208
209
|
|
209
210
|
You can also specify descending order
|
210
211
|
|
211
|
-
posts.order(:stamp.
|
212
|
+
posts.order(:stamp.desc)
|
212
213
|
|
213
214
|
=== Deleting Records
|
214
215
|
|
data/Rakefile
CHANGED
@@ -9,17 +9,30 @@ include FileUtils
|
|
9
9
|
# Configuration
|
10
10
|
##############################################################################
|
11
11
|
NAME = "sequel"
|
12
|
-
VERS = "1.
|
12
|
+
VERS = "1.4.0"
|
13
13
|
CLEAN.include ["**/.*.sw?", "pkg/*", ".config", "doc/*", "coverage/*"]
|
14
14
|
RDOC_OPTS = [
|
15
15
|
"--quiet",
|
16
|
-
"--title", "Sequel: Database
|
16
|
+
"--title", "Sequel: The Database Toolkit for Ruby",
|
17
17
|
"--opname", "index.html",
|
18
18
|
"--line-numbers",
|
19
19
|
"--main", "README",
|
20
20
|
"--inline-source"
|
21
21
|
]
|
22
22
|
|
23
|
+
##############################################################################
|
24
|
+
# RDoc
|
25
|
+
##############################################################################
|
26
|
+
task :doc => [:rdoc]
|
27
|
+
|
28
|
+
Rake::RDocTask.new do |rdoc|
|
29
|
+
rdoc.rdoc_dir = "doc/rdoc"
|
30
|
+
rdoc.options += RDOC_OPTS
|
31
|
+
rdoc.main = "README"
|
32
|
+
rdoc.title = "Sequel: The Database Toolkit for Ruby"
|
33
|
+
rdoc.rdoc_files.add ["README", "COPYING", "lib/sequel_model.rb", "lib/**/*.rb"]
|
34
|
+
end
|
35
|
+
|
23
36
|
##############################################################################
|
24
37
|
# Gem packaging
|
25
38
|
##############################################################################
|
@@ -27,34 +40,34 @@ desc "Packages up Sequel."
|
|
27
40
|
task :default => [:package]
|
28
41
|
task :package => [:clean]
|
29
42
|
|
30
|
-
RDOC_OPTS = [
|
31
|
-
"--quiet",
|
32
|
-
"--title", "Sequel Model: The Database Toolkit for Ruby",
|
33
|
-
"--opname", "index.html",
|
34
|
-
"--line-numbers",
|
35
|
-
"--main", "README",
|
36
|
-
"--inline-source"
|
37
|
-
]
|
38
|
-
|
39
43
|
spec = Gem::Specification.new do |s|
|
40
44
|
s.name = NAME
|
41
45
|
s.rubyforge_project = 'sequel'
|
42
46
|
s.version = VERS
|
43
47
|
s.platform = Gem::Platform::RUBY
|
44
|
-
s.
|
48
|
+
s.has_rdoc = true
|
49
|
+
s.extra_rdoc_files = ["README", "CHANGELOG", "COPYING"]
|
50
|
+
s.rdoc_options += RDOC_OPTS +
|
51
|
+
["--exclude", "^(examples|extras)\/", "--exclude", "lib/sequel_model.rb"]
|
52
|
+
s.summary = "The Database Toolkit for Ruby: Model Classes"
|
45
53
|
s.description = s.summary
|
46
|
-
s.author = "
|
47
|
-
s.email = "
|
54
|
+
s.author = "Jeremy Evans"
|
55
|
+
s.email = "code@jeremyevans.net"
|
48
56
|
s.homepage = "http://sequel.rubyforge.org"
|
49
57
|
s.required_ruby_version = ">= 1.8.4"
|
50
58
|
|
51
|
-
|
52
|
-
|
59
|
+
case RUBY_PLATFORM
|
60
|
+
when /java/
|
61
|
+
s.platform = "jruby"
|
62
|
+
else
|
63
|
+
s.platform = Gem::Platform::RUBY
|
64
|
+
end
|
53
65
|
|
54
|
-
s.add_dependency("
|
55
|
-
s.add_dependency("
|
66
|
+
s.add_dependency("assistance", '>= 0.1.2')
|
67
|
+
s.add_dependency("sequel_core", '= 1.4.0')
|
56
68
|
|
57
|
-
s.files = %w(Rakefile) + Dir.glob("{lib}/**/*")
|
69
|
+
s.files = %w(COPYING README Rakefile) + Dir.glob("{doc,spec,lib}/**/*")
|
70
|
+
|
58
71
|
s.require_path = "lib"
|
59
72
|
end
|
60
73
|
|
@@ -80,16 +93,56 @@ task :uninstall => [:clean] do
|
|
80
93
|
sh %{sudo gem uninstall #{NAME}}
|
81
94
|
end
|
82
95
|
|
83
|
-
task :tag do
|
84
|
-
cwd = FileUtils.pwd
|
85
|
-
sh %{cd ../.. && svn copy #{cwd} tags/#{NAME}-#{VERS} && svn commit -m "#{NAME}-#{VERS} tag." tags}
|
86
|
-
end
|
87
|
-
|
88
96
|
##############################################################################
|
89
|
-
# gem release
|
97
|
+
# gem and rdoc release
|
90
98
|
##############################################################################
|
91
99
|
task :release => [:package] do
|
92
100
|
sh %{rubyforge login}
|
93
101
|
sh %{rubyforge add_release sequel #{NAME} #{VERS} pkg/#{NAME}-#{VERS}.tgz}
|
94
102
|
sh %{rubyforge add_file sequel #{NAME} #{VERS} pkg/#{NAME}-#{VERS}.gem}
|
95
103
|
end
|
104
|
+
|
105
|
+
##############################################################################
|
106
|
+
# specs
|
107
|
+
##############################################################################
|
108
|
+
require "spec/rake/spectask"
|
109
|
+
sqcdir = File.join(File.dirname(File.dirname(__FILE__)), 'sequel_core', 'lib')
|
110
|
+
fixRUBYLIB = Proc.new{ENV['RUBYLIB'] ? (ENV['RUBYLIB'] += ":#{sqcdir}") : (ENV['RUBYLIB'] = sqcdir)}
|
111
|
+
|
112
|
+
desc "Run specs with coverage"
|
113
|
+
Spec::Rake::SpecTask.new("spec") do |t|
|
114
|
+
fixRUBYLIB.call
|
115
|
+
t.spec_files = FileList["spec/**/*_spec.rb"]
|
116
|
+
t.spec_opts = File.read("spec/spec.opts").split("\n")
|
117
|
+
t.rcov_opts = File.read("spec/rcov.opts").split("\n")
|
118
|
+
t.rcov = true
|
119
|
+
end
|
120
|
+
|
121
|
+
desc "Run specs without coverage"
|
122
|
+
Spec::Rake::SpecTask.new("spec_no_cov") do |t|
|
123
|
+
fixRUBYLIB.call
|
124
|
+
t.spec_files = FileList["spec/**/*_spec.rb"]
|
125
|
+
t.spec_opts = File.read("spec/spec.opts").split("\n")
|
126
|
+
end
|
127
|
+
|
128
|
+
desc "check documentation coverage"
|
129
|
+
task :dcov do
|
130
|
+
sh "find lib -name '*.rb' | xargs dcov"
|
131
|
+
end
|
132
|
+
|
133
|
+
##############################################################################
|
134
|
+
# Statistics
|
135
|
+
##############################################################################
|
136
|
+
|
137
|
+
STATS_DIRECTORIES = [
|
138
|
+
%w(Code lib/),
|
139
|
+
%w(Spec spec/)
|
140
|
+
].collect { |name, dir| [ name, "./#{dir}" ] }.select { |name, dir| File.directory?(dir) }
|
141
|
+
|
142
|
+
desc "Report code statistics (KLOCs, etc) from the application"
|
143
|
+
task :stats do
|
144
|
+
require "extra/stats"
|
145
|
+
verbose = true
|
146
|
+
CodeStatistics.new(*STATS_DIRECTORIES).to_s
|
147
|
+
end
|
148
|
+
|
data/lib/sequel.rb
CHANGED
@@ -1,2 +1 @@
|
|
1
|
-
require '
|
2
|
-
require 'sequel_model'
|
1
|
+
require 'sequel_model'
|
data/lib/sequel_model.rb
ADDED
@@ -0,0 +1,324 @@
|
|
1
|
+
require 'sequel_core'
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
class Model
|
5
|
+
alias_method :model, :class
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
files = %w[
|
10
|
+
base hooks record schema associations
|
11
|
+
caching plugins validations eager_loading
|
12
|
+
]
|
13
|
+
dir = File.join(File.dirname(__FILE__), "sequel_model")
|
14
|
+
files.each {|f| require(File.join(dir, f))}
|
15
|
+
|
16
|
+
module Sequel
|
17
|
+
# == Sequel Models
|
18
|
+
#
|
19
|
+
# Models in Sequel are based on the Active Record pattern described by Martin Fowler (http://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.
|
20
|
+
#
|
21
|
+
# Model classes are defined as regular Ruby classes:
|
22
|
+
#
|
23
|
+
# DB = Sequel('sqlite:/blog.db')
|
24
|
+
# class Post < Sequel::Model
|
25
|
+
# set_dataset DB[:posts]
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# You can also use the shorthand form:
|
29
|
+
#
|
30
|
+
# DB = Sequel('sqlite:/blog.db')
|
31
|
+
# class Post < Sequel::Model
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# === Model instances
|
35
|
+
#
|
36
|
+
# 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:
|
37
|
+
#
|
38
|
+
# post = Post[123]
|
39
|
+
#
|
40
|
+
# The Model#pk method is used to retrieve the record's primary key value:
|
41
|
+
#
|
42
|
+
# post.pk #=> 123
|
43
|
+
#
|
44
|
+
# Sequel models allow you to use any column as a primary key, and even composite keys made from multiple columns:
|
45
|
+
#
|
46
|
+
# class Post < Sequel::Model
|
47
|
+
# set_primary_key [:category, :title]
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# post = Post['ruby', 'hello world']
|
51
|
+
# post.pk #=> ['ruby', 'hello world']
|
52
|
+
#
|
53
|
+
# You can also define a model class that does not have a primary key, but then you lose the ability to update records.
|
54
|
+
#
|
55
|
+
# A model instance can also be fetched by specifying a condition:
|
56
|
+
#
|
57
|
+
# post = Post[:title => 'hello world']
|
58
|
+
# post = Post.find {:stamp < 10.days.ago}
|
59
|
+
#
|
60
|
+
# === Iterating over records
|
61
|
+
#
|
62
|
+
# 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.:
|
63
|
+
#
|
64
|
+
# Post.filter(:category => 'ruby').each {|post| p post}
|
65
|
+
#
|
66
|
+
# You can also manipulate the records in the dataset:
|
67
|
+
#
|
68
|
+
# Post.filter {:stamp < 7.days.ago}.delete
|
69
|
+
# Post.filter {:title =~ /ruby/}.update(:category => 'ruby')
|
70
|
+
#
|
71
|
+
# === Accessing record values
|
72
|
+
#
|
73
|
+
# A model instances stores its values as a hash:
|
74
|
+
#
|
75
|
+
# post.values #=> {:id => 123, :category => 'ruby', :title => 'hello world'}
|
76
|
+
#
|
77
|
+
# You can read the record values as object attributes:
|
78
|
+
#
|
79
|
+
# post.id #=> 123
|
80
|
+
# post.title #=> 'hello world'
|
81
|
+
#
|
82
|
+
# You can also change record values:
|
83
|
+
#
|
84
|
+
# post.title = 'hey there'
|
85
|
+
# post.save
|
86
|
+
#
|
87
|
+
# Another way to change values by using the #set method:
|
88
|
+
#
|
89
|
+
# post.set(:title => 'hey there')
|
90
|
+
#
|
91
|
+
# === Creating new records
|
92
|
+
#
|
93
|
+
# New records can be created by calling Model.create:
|
94
|
+
#
|
95
|
+
# post = Post.create(:title => 'hello world')
|
96
|
+
#
|
97
|
+
# Another way is to construct a new instance and save it:
|
98
|
+
#
|
99
|
+
# post = Post.new
|
100
|
+
# post.title = 'hello world'
|
101
|
+
# post.save
|
102
|
+
#
|
103
|
+
# You can also supply a block to Model.new and Model.create:
|
104
|
+
#
|
105
|
+
# post = Post.create {|p| p.title = 'hello world'}
|
106
|
+
#
|
107
|
+
# post = Post.new do |p|
|
108
|
+
# p.title = 'hello world'
|
109
|
+
# p.save
|
110
|
+
# end
|
111
|
+
#
|
112
|
+
# === Hooks
|
113
|
+
#
|
114
|
+
# 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.
|
115
|
+
#
|
116
|
+
# Hooks are defined by supplying a block:
|
117
|
+
#
|
118
|
+
# class Post < Sequel::Model
|
119
|
+
# after_create do
|
120
|
+
# set(:created_at => Time.now)
|
121
|
+
# end
|
122
|
+
#
|
123
|
+
# after_destroy do
|
124
|
+
# author.update_post_count
|
125
|
+
# end
|
126
|
+
# end
|
127
|
+
#
|
128
|
+
# === Deleting records
|
129
|
+
#
|
130
|
+
# 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:
|
131
|
+
#
|
132
|
+
# post.delete #=> bypasses hooks
|
133
|
+
# post.destroy #=> runs hooks
|
134
|
+
#
|
135
|
+
# 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:
|
136
|
+
#
|
137
|
+
# Post.filter(:category => 32).delete #=> bypasses hooks
|
138
|
+
# Post.filter(:category => 32).destroy #=> runs hooks
|
139
|
+
#
|
140
|
+
# Please note that if Model.destroy is called, each record is deleted
|
141
|
+
# separately, but Model.delete deletes all relevant records with a single
|
142
|
+
# SQL statement.
|
143
|
+
#
|
144
|
+
# === Associations
|
145
|
+
#
|
146
|
+
# Sequel provides macros for the three most common types of associations:
|
147
|
+
# many_to_one, one_to_many and many_to_many (equivalent to ActiveRecord's
|
148
|
+
# belongs_to, has_many and has_and_belongs_to_many).
|
149
|
+
#
|
150
|
+
# Associations are defined in similar fashion to ActiveRecord:
|
151
|
+
#
|
152
|
+
# class Post < Sequel::Model
|
153
|
+
# belongs_to :author
|
154
|
+
# end
|
155
|
+
#
|
156
|
+
# class Author < Sequel::Model
|
157
|
+
# has_many :posts
|
158
|
+
# end
|
159
|
+
#
|
160
|
+
# Another way to define an association in a Sequel model is as a regular
|
161
|
+
# instance method:
|
162
|
+
#
|
163
|
+
# class Post < Sequel::Model
|
164
|
+
# def author; Author[author_id]; end
|
165
|
+
# end
|
166
|
+
#
|
167
|
+
# class Author < Sequel::Model
|
168
|
+
# def posts; Post.filter(:author_id => pk); end
|
169
|
+
# end
|
170
|
+
#
|
171
|
+
# === Caching model instances with memcached
|
172
|
+
#
|
173
|
+
# 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:
|
174
|
+
#
|
175
|
+
# require 'memcache'
|
176
|
+
# CACHE = MemCache.new 'localhost:11211', :namespace => 'blog'
|
177
|
+
#
|
178
|
+
# class Author < Sequel::Model
|
179
|
+
# set_cache CACHE, :ttl => 3600
|
180
|
+
# end
|
181
|
+
#
|
182
|
+
# Author[333] # database hit
|
183
|
+
# Author[333] # cache hit
|
184
|
+
#
|
185
|
+
# === Extending the underlying dataset
|
186
|
+
#
|
187
|
+
# 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:
|
188
|
+
#
|
189
|
+
# class Post < Sequel::Model
|
190
|
+
# def self.old_posts
|
191
|
+
# filter {:stamp < 30.days.ago}
|
192
|
+
# end
|
193
|
+
#
|
194
|
+
# def self.clean_old_posts
|
195
|
+
# old_posts.delete
|
196
|
+
# end
|
197
|
+
# end
|
198
|
+
#
|
199
|
+
# You can also implement table-wide logic by defining methods on the dataset:
|
200
|
+
#
|
201
|
+
# class Post < Sequel::Model
|
202
|
+
# def dataset.old_posts
|
203
|
+
# filter {:stamp < 30.days.ago}
|
204
|
+
# end
|
205
|
+
#
|
206
|
+
# def dataset.clean_old_posts
|
207
|
+
# old_posts.delete
|
208
|
+
# end
|
209
|
+
# end
|
210
|
+
#
|
211
|
+
# 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:
|
212
|
+
#
|
213
|
+
# Post.filter(:category => 'ruby').clean_old_posts
|
214
|
+
#
|
215
|
+
# Sequel models also provide a short hand notation for filters:
|
216
|
+
#
|
217
|
+
# class Post < Sequel::Model
|
218
|
+
# subset(:old_posts) {:stamp < 30.days.ago}
|
219
|
+
# subset :invisible, :visible => false
|
220
|
+
# end
|
221
|
+
#
|
222
|
+
# === Defining the underlying schema
|
223
|
+
#
|
224
|
+
# 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:
|
225
|
+
#
|
226
|
+
# class Post < Sequel::Model
|
227
|
+
# set_schema do
|
228
|
+
# primary_key :id
|
229
|
+
# text :title
|
230
|
+
# text :category
|
231
|
+
# foreign_key :author_id, :table => :authors
|
232
|
+
# end
|
233
|
+
# end
|
234
|
+
#
|
235
|
+
# You can then create the underlying table, drop it, or recreate it:
|
236
|
+
#
|
237
|
+
# Post.table_exists?
|
238
|
+
# Post.create_table
|
239
|
+
# Post.drop_table
|
240
|
+
# Post.create_table! # drops the table if it exists and then recreates it
|
241
|
+
#
|
242
|
+
class Model
|
243
|
+
extend Associations
|
244
|
+
# Returns a string representation of the model instance including
|
245
|
+
# the class name and values.
|
246
|
+
def inspect
|
247
|
+
"#<%s @values=%s>" % [self.class.name, @values.inspect]
|
248
|
+
end
|
249
|
+
|
250
|
+
# Defines a method that returns a filtered dataset.
|
251
|
+
def self.subset(name, *args, &block)
|
252
|
+
dataset.meta_def(name) {filter(*args, &block)}
|
253
|
+
end
|
254
|
+
|
255
|
+
# Finds a single record according to the supplied filter, e.g.:
|
256
|
+
#
|
257
|
+
# Ticket.find :author => 'Sharon' # => record
|
258
|
+
# Ticket.find {:price == 17} # => Dataset
|
259
|
+
#
|
260
|
+
def self.find(*args, &block)
|
261
|
+
dataset.filter(*args, &block).first
|
262
|
+
end
|
263
|
+
|
264
|
+
# TODO: doc
|
265
|
+
def self.[](*args)
|
266
|
+
args = args.first if (args.size == 1)
|
267
|
+
if args === true || args === false
|
268
|
+
raise Error::InvalidFilter, "Did you mean to supply a hash?"
|
269
|
+
end
|
270
|
+
dataset[(Hash === args) ? args : primary_key_hash(args)]
|
271
|
+
end
|
272
|
+
|
273
|
+
# TODO: doc
|
274
|
+
def self.fetch(*args)
|
275
|
+
db.fetch(*args).set_model(self)
|
276
|
+
end
|
277
|
+
|
278
|
+
# Like find but invokes create with given conditions when record does not
|
279
|
+
# exists.
|
280
|
+
def self.find_or_create(cond)
|
281
|
+
find(cond) || create(cond)
|
282
|
+
end
|
283
|
+
|
284
|
+
############################################################################
|
285
|
+
|
286
|
+
# Deletes all records in the model's table.
|
287
|
+
def self.delete_all
|
288
|
+
dataset.delete
|
289
|
+
end
|
290
|
+
|
291
|
+
# Like delete_all, but invokes before_destroy and after_destroy hooks if used.
|
292
|
+
def self.destroy_all
|
293
|
+
dataset.destroy
|
294
|
+
end
|
295
|
+
|
296
|
+
def self.is_dataset_magic_method?(m)
|
297
|
+
method_name = m.to_s
|
298
|
+
Sequel::Dataset::MAGIC_METHODS.each_key do |r|
|
299
|
+
return true if method_name =~ r
|
300
|
+
end
|
301
|
+
false
|
302
|
+
end
|
303
|
+
|
304
|
+
def self.method_missing(m, *args, &block) #:nodoc:
|
305
|
+
Thread.exclusive do
|
306
|
+
if dataset.respond_to?(m) || is_dataset_magic_method?(m)
|
307
|
+
instance_eval("def #{m}(*args, &block); dataset.#{m}(*args, &block); end")
|
308
|
+
end
|
309
|
+
end
|
310
|
+
respond_to?(m) ? send(m, *args, &block) : super(m, *args)
|
311
|
+
end
|
312
|
+
|
313
|
+
# TODO: Comprehensive description goes here!
|
314
|
+
def self.join(*args)
|
315
|
+
table_name = dataset.opts[:from].first
|
316
|
+
dataset.join(*args).select(table_name.to_sym.ALL)
|
317
|
+
end
|
318
|
+
|
319
|
+
# Returns an array containing all of the models records.
|
320
|
+
def self.all
|
321
|
+
dataset.all
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|