sequel_model 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ === 0.1 (2007-12-30)
2
+
3
+ * Moved model code from sequel into separate model sub-project.
data/COPYING ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2006-2007 Sharon Rosner
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to
5
+ deal in the Software without restriction, including without limitation the
6
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7
+ sell copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16
+ THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,251 @@
1
+ == Sequel: Concise ORM for Ruby
2
+
3
+ Sequel is an ORM framework for Ruby. Sequel provides thread safety, connection pooling, and a concise DSL for constructing queries and table schemas.
4
+
5
+ Sequel makes it easy to deal with multiple records without having to break your teeth on SQL.
6
+
7
+ == Resources
8
+
9
+ * {Project page}[http://code.google.com/p/ruby-sequel/]
10
+ * {Source code}[http://ruby-sequel.googlecode.com/svn/]
11
+ * {Bug tracking}[http://code.google.com/p/ruby-sequel/issues/list]
12
+ * {Google group}[http://groups.google.com/group/sequel-talk]
13
+ * {RubyForge page}[http://rubyforge.org/projects/sequel/]
14
+
15
+ To check out the source code:
16
+
17
+ svn co http://ruby-sequel.googlecode.com/svn/trunk
18
+
19
+ === Contact
20
+
21
+ If you have any comments or suggestions please send an email to ciconia at gmail.com and I'll get back to you.
22
+
23
+ == Installation
24
+
25
+ sudo gem install sequel
26
+
27
+ == Supported Databases
28
+
29
+ Sequel currently supports:
30
+
31
+ * ADO (on Windows)
32
+ * DBI
33
+ * Informix
34
+ * MySQL
35
+ * ODBC
36
+ * Oracle
37
+ * PostgreSQL
38
+ * SQLite 3
39
+
40
+ There are also experimental adapters for DB2, OpenBase and JDBC (on JRuby).
41
+
42
+ == The Sequel Console
43
+
44
+ Sequel includes an IRB console for quick'n'dirty access to databases. You can use it like this:
45
+
46
+ sequel sqlite:///test.db
47
+
48
+ You get an IRB session with the database object stored in DB.
49
+
50
+ == An Introduction
51
+
52
+ Sequel was designed to take the hassle away from connecting to databases and manipulating them. Sequel deals with all the boring stuff like maintaining connections, formatting SQL correctly and fetching records so you can concentrate on your application.
53
+
54
+ Sequel uses the concept of datasets to retrieve data. A Dataset object encapsulates an SQL query and supports chainability, letting you fetch data using a convenient Ruby DSL that is both concise and infinitely flexible.
55
+
56
+ For example, the following one-liner returns the average GDP for the five biggest countries in the middle east region:
57
+
58
+ DB[:countries].filter(:region => 'Middle East').reverse_order(:area).limit(5).avg(:GDP)
59
+
60
+ Which is equivalent to:
61
+
62
+ SELECT avg(GDP) FROM countries WHERE region = 'Middle East' ORDER BY area DESC LIMIT 5
63
+
64
+ Since datasets retrieve records only when needed, they can be stored and later reused. Records are fetched as hashes (they can also be fetched as custom model objects), and are accessed using an Enumerable interface:
65
+
66
+ middle_east = DB[:countries].filter(:region => 'Middle East')
67
+ middle_east.order(:name).each {|r| puts r[:name]}
68
+
69
+ Sequel also offers convenience methods for extracting data from Datasets, such as an extended map method:
70
+
71
+ middle_east.map(:name) #=> ['Egypt', 'Greece', 'Israel', ...]
72
+
73
+ Or getting results as a transposed hash, with one column as key and another as value:
74
+
75
+ middle_east.to_hash(:name, :area) #=> {'Israel' => 20000, 'Greece' => 120000, ...}
76
+
77
+ Much of Sequel is still undocumented (especially the part relating to model classes). The following section provides examples of common usage. Feel free to explore...
78
+
79
+ == Getting Started
80
+
81
+ === Connecting to a database
82
+
83
+ To connect to a database you simply provide Sequel with a URL:
84
+
85
+ require 'sequel'
86
+ DB = Sequel.open 'sqlite:///blog.db'
87
+
88
+ The connection URL can also include such stuff as the user name and password:
89
+
90
+ DB = Sequel.open 'postgres://cico:12345@localhost:5432/mydb'
91
+
92
+ You can also specify optional parameters, such as the connection pool size, or a logger for logging SQL queries:
93
+
94
+ DB = Sequel.open("postgres://postgres:postgres@localhost/my_db",
95
+ :max_connections => 10, :logger => Logger.new('log/db.log'))
96
+
97
+ === Arbitrary SQL queries
98
+
99
+ DB.execute("create table t (a text, b text)")
100
+ DB.execute("insert into t values ('a', 'b')")
101
+
102
+ Or more succinctly:
103
+
104
+ DB << "create table t (a text, b text)"
105
+ DB << "insert into t values ('a', 'b')"
106
+
107
+ === Getting Dataset Instances
108
+
109
+ Dataset is the primary means through which records are retrieved and manipulated. You can create an blank dataset by using the dataset method:
110
+
111
+ dataset = DB.dataset
112
+
113
+ Or by using the from methods:
114
+
115
+ posts = DB.from(:posts)
116
+
117
+ You can also use the equivalent shorthand:
118
+
119
+ posts = DB[:posts]
120
+
121
+ Note: the dataset will only fetch records when you explicitly ask for them, as will be shown below. Datasets can be manipulated to filter through records, change record order and even join tables, as will also be shown below.
122
+
123
+ === Retrieving Records
124
+
125
+ You can retrieve records by using the all method:
126
+
127
+ posts.all
128
+
129
+ The all method returns an array of hashes, where each hash corresponds to a record.
130
+
131
+ You can also iterate through records one at a time:
132
+
133
+ posts.each {|row| p row}
134
+
135
+ Or perform more advanced stuff:
136
+
137
+ posts.map(:id)
138
+ posts.inject({}) {|h, r| h[r[:id]] = r[:name]}
139
+
140
+ You can also retrieve the first record in a dataset:
141
+
142
+ posts.first
143
+
144
+ Or retrieve a single record with a specific value:
145
+
146
+ posts[:id => 1]
147
+
148
+ If the dataset is ordered, you can also ask for the last record:
149
+
150
+ posts.order(:stamp).last
151
+
152
+ === Filtering Records
153
+
154
+ The simplest way to filter records is to provide a hash of values to match:
155
+
156
+ my_posts = posts.filter(:category => 'ruby', :author => 'david')
157
+
158
+ You can also specify ranges:
159
+
160
+ my_posts = posts.filter(:stamp => (2.weeks.ago)..(1.week.ago))
161
+
162
+ Or lists of values:
163
+
164
+ my_posts = posts.filter(:category => ['ruby', 'postgres', 'linux'])
165
+
166
+ Sequel now also accepts expressions as closures, AKA block filters:
167
+
168
+ my_posts = posts.filter {:category == ['ruby', 'postgres', 'linux']}
169
+
170
+ Which also lets you do stuff like:
171
+
172
+ my_posts = posts.filter {:stamp > 1.month.ago}
173
+
174
+ Some adapters (like postgresql) will also let you specify Regexps:
175
+
176
+ my_posts = posts.filter(:category => /ruby/i)
177
+
178
+ You can also use an inverse filter:
179
+
180
+ my_posts = posts.exclude(:category => /ruby/i)
181
+
182
+ You can then retrieve the records by using any of the retrieval methods:
183
+
184
+ my_posts.each {|row| p row}
185
+
186
+ You can also specify a custom WHERE clause:
187
+
188
+ posts.filter('(stamp < ?) AND (author <> ?)', 3.days.ago, author_name)
189
+
190
+ Datasets can also be used as subqueries:
191
+
192
+ DB[:items].filter('price > ?', DB[:items].select('AVG(price) + 100'))
193
+
194
+ === Summarizing Records
195
+
196
+ Counting records is easy:
197
+ posts.filter(:category => /ruby/i).count
198
+
199
+ And you can also query maximum/minimum values:
200
+ max_value = DB[:history].max(:value)
201
+
202
+ Or calculate a sum:
203
+ total = DB[:items].sum(:price)
204
+
205
+ === Ordering Records
206
+
207
+ posts.order(:stamp)
208
+
209
+ You can also specify descending order
210
+
211
+ posts.order(:stamp.DESC)
212
+
213
+ === Deleting Records
214
+
215
+ posts.filter('stamp < ?', 3.days.ago).delete
216
+
217
+ === Inserting Records
218
+
219
+ posts.insert(:category => 'ruby', :author => 'david')
220
+
221
+ Or alternatively:
222
+
223
+ posts << {:category => 'ruby', :author => 'david'}
224
+
225
+ === Updating Records
226
+
227
+ posts.filter('stamp < ?', 3.days.ago).update(:state => 'archived')
228
+
229
+ === Joining Tables
230
+
231
+ Joining is very useful in a variety of scenarios, for example many-to-many relationships. With Sequel it's really easy:
232
+
233
+ order_items = DB[:items].join(:order_items, :item_id => :id).
234
+ filter(:order_items__order_id => 1234)
235
+
236
+ This is equivalent to the SQL:
237
+
238
+ SELECT * FROM items LEFT OUTER JOIN order_items
239
+ ON order_items.item_id = items.id
240
+ WHERE order_items.order_id = 1234
241
+
242
+ You can then do anything you like with the dataset:
243
+
244
+ order_total = order_items.sum(:price)
245
+
246
+ Which is equivalent to the SQL:
247
+
248
+ SELECT sum(price) FROM items LEFT OUTER JOIN order_items
249
+ ON order_items.item_id = items.id
250
+ WHERE order_items.order_id = 1234
251
+
@@ -0,0 +1,146 @@
1
+ require "rake"
2
+ require "rake/clean"
3
+ require "rake/gempackagetask"
4
+ require "rake/rdoctask"
5
+ require "fileutils"
6
+ include FileUtils
7
+
8
+ ##############################################################################
9
+ # Configuration
10
+ ##############################################################################
11
+ NAME = "sequel_model"
12
+ VERS = "0.1"
13
+ CLEAN.include ["**/.*.sw?", "pkg/*", ".config", "doc/*", "coverage/*"]
14
+ RDOC_OPTS = [
15
+ "--quiet",
16
+ "--title", "Sequel Model: Lightweight ORM for Ruby",
17
+ "--opname", "index.html",
18
+ "--line-numbers",
19
+ "--main", "README",
20
+ "--inline-source"
21
+ ]
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: Lightweight ORM for Ruby"
33
+ rdoc.rdoc_files.add ["README", "COPYING", "lib/sequel.rb", "lib/**/*.rb"]
34
+ end
35
+
36
+ ##############################################################################
37
+ # Gem packaging
38
+ ##############################################################################
39
+ desc "Packages up Sequel."
40
+ task :default => [:package]
41
+ task :package => [:clean]
42
+
43
+ spec = Gem::Specification.new do |s|
44
+ s.name = NAME
45
+ s.rubyforge_project = NAME
46
+ s.version = VERS
47
+ s.platform = Gem::Platform::RUBY
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.rb"]
52
+ s.summary = "Lightweight ORM for Ruby"
53
+ s.description = s.summary
54
+ s.author = "Sharon Rosner"
55
+ s.email = "ciconia@gmail.com"
56
+ s.homepage = "http://sequel.rubyforge.org"
57
+ s.required_ruby_version = ">= 1.8.4"
58
+
59
+ case RUBY_PLATFORM
60
+ when /mswin/
61
+ s.platform = Gem::Platform::CURRENT
62
+ when /java/
63
+ s.platform = "jruby"
64
+ else
65
+ s.platform = Gem::Platform::RUBY
66
+ end
67
+
68
+ s.add_dependency("sequel", '>= 0.5')
69
+ s.add_dependency("validatable")
70
+
71
+ s.files = %w(COPYING README Rakefile) + Dir.glob("{doc,spec,lib}/**/*")
72
+
73
+ s.require_path = "lib"
74
+ end
75
+
76
+ Rake::GemPackageTask.new(spec) do |p|
77
+ p.need_tar = true
78
+ p.gem_spec = spec
79
+ end
80
+
81
+ ##############################################################################
82
+ # installation & removal
83
+ ##############################################################################
84
+ task :install do
85
+ sh %{rake package}
86
+ sh %{sudo gem install pkg/#{NAME}-#{VERS}}
87
+ end
88
+
89
+ task :install_no_docs do
90
+ sh %{rake package}
91
+ sh %{sudo gem install pkg/#{NAME}-#{VERS} --no-rdoc --no-ri}
92
+ end
93
+
94
+ task :uninstall => [:clean] do
95
+ sh %{sudo gem uninstall #{NAME}}
96
+ end
97
+
98
+ task :tag do
99
+ cwd = FileUtils.pwd
100
+ sh %{cd ../.. && svn copy #{cwd} tags/#{NAME}-#{VERS} && svn commit -m "#{NAME}-#{VERS} tag." tags}
101
+ end
102
+
103
+ ##############################################################################
104
+ # gem and rdoc release
105
+ ##############################################################################
106
+ task :release => [:package] do
107
+ sh %{rubyforge login}
108
+ sh %{rubyforge add_release sequel #{NAME} #{VERS} pkg/#{NAME}-#{VERS}.tgz}
109
+ sh %{rubyforge add_file sequel #{NAME} #{VERS} pkg/#{NAME}-#{VERS}.gem}
110
+ end
111
+
112
+ ##############################################################################
113
+ # specs
114
+ ##############################################################################
115
+ require "spec/rake/spectask"
116
+
117
+ desc "Run specs with coverage"
118
+ Spec::Rake::SpecTask.new("spec") do |t|
119
+ t.spec_files = FileList["spec/*_spec.rb"]
120
+ t.spec_opts = File.read("spec/spec.opts").split("\n")
121
+ t.rcov_opts = File.read("spec/rcov.opts").split("\n")
122
+ t.rcov = true
123
+ end
124
+
125
+ desc "Run specs without coverage"
126
+ Spec::Rake::SpecTask.new("spec_no_cov") do |t|
127
+ t.spec_files = FileList["spec/*_spec.rb"]
128
+ t.spec_opts = File.read("spec/spec.opts").split("\n")
129
+ end
130
+
131
+ ##############################################################################
132
+ # Statistics
133
+ ##############################################################################
134
+
135
+ STATS_DIRECTORIES = [
136
+ %w(Code lib/),
137
+ %w(Spec spec/)
138
+ ].collect { |name, dir| [ name, "./#{dir}" ] }.select { |name, dir| File.directory?(dir) }
139
+
140
+ desc "Report code statistics (KLOCs, etc) from the application"
141
+ task :stats do
142
+ require "extra/stats"
143
+ verbose = true
144
+ CodeStatistics.new(*STATS_DIRECTORIES).to_s
145
+ end
146
+
@@ -0,0 +1,326 @@
1
+ gem 'sequel', '>= 0.5'
2
+ require 'sequel'
3
+
4
+ module Sequel
5
+ # == Sequel Models
6
+ #
7
+ # 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.
8
+ #
9
+ # Model classes are defined as regular Ruby classes:
10
+ #
11
+ # DB = Sequel('sqlite:/blog.db')
12
+ # class Post < Sequel::Model
13
+ # set_dataset DB[:posts]
14
+ # end
15
+ #
16
+ # You can also use the shorthand form:
17
+ #
18
+ # DB = Sequel('sqlite:/blog.db')
19
+ # class Post < Sequel::Model(:posts)
20
+ # end
21
+ #
22
+ # === Model instances
23
+ #
24
+ # 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:
25
+ #
26
+ # post = Post[123]
27
+ #
28
+ # The Model#pk method is used to retrieve the record's primary key value:
29
+ #
30
+ # post.pk #=> 123
31
+ #
32
+ # Sequel models allow you to use any column as a primary key, and even composite keys made from multiple columns:
33
+ #
34
+ # class Post < Sequel::Model(:posts)
35
+ # set_primary_key [:category, :title]
36
+ # end
37
+ #
38
+ # post = Post['ruby', 'hello world']
39
+ # post.pk #=> ['ruby', 'hello world']
40
+ #
41
+ # You can also define a model class that does not have a primary key, but then you lose the ability to update records.
42
+ #
43
+ # A model instance can also be fetched by specifying a condition:
44
+ #
45
+ # post = Post[:title => 'hello world']
46
+ # post = Post.find {:stamp < 10.days.ago}
47
+ #
48
+ # === Iterating over records
49
+ #
50
+ # 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.:
51
+ #
52
+ # Post.filter(:category => 'ruby').each {|post| p post}
53
+ #
54
+ # You can also manipulate the records in the dataset:
55
+ #
56
+ # Post.filter {:stamp < 7.days.ago}.delete
57
+ # Post.filter {:title =~ /ruby/}.update(:category => 'ruby')
58
+ #
59
+ # === Accessing record values
60
+ #
61
+ # A model instances stores its values as a hash:
62
+ #
63
+ # post.values #=> {:id => 123, :category => 'ruby', :title => 'hello world'}
64
+ #
65
+ # You can read the record values as object attributes:
66
+ #
67
+ # post.id #=> 123
68
+ # post.title #=> 'hello world'
69
+ #
70
+ # You can also change record values:
71
+ #
72
+ # post.title = 'hey there'
73
+ # post.save
74
+ #
75
+ # Another way to change values by using the #set method:
76
+ #
77
+ # post.set(:title => 'hey there')
78
+ #
79
+ # === Creating new records
80
+ #
81
+ # New records can be created by calling Model.create:
82
+ #
83
+ # post = Post.create(:title => 'hello world')
84
+ #
85
+ # Another way is to construct a new instance and save it:
86
+ #
87
+ # post = Post.new
88
+ # post.title = 'hello world'
89
+ # post.save
90
+ #
91
+ # You can also supply a block to Model.new and Model.create:
92
+ #
93
+ # post = Post.create {|p| p.title = 'hello world'}
94
+ #
95
+ # post = Post.new do |p|
96
+ # p.title = 'hello world'
97
+ # p.save
98
+ # end
99
+ #
100
+ # === Hooks
101
+ #
102
+ # 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.
103
+ #
104
+ # Hooks are defined by supplying a block:
105
+ #
106
+ # class Post < Sequel::Model(:posts)
107
+ # after_create do
108
+ # set(:created_at => Time.now)
109
+ # end
110
+ #
111
+ # after_destroy
112
+ # author.update_post_count
113
+ # end
114
+ # end
115
+ #
116
+ # === Deleting records
117
+ #
118
+ # 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:
119
+ #
120
+ # post.delete #=> bypasses hooks
121
+ # post.destroy #=> runs hooks
122
+ #
123
+ # 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:
124
+ #
125
+ # Post.filter(:category => 32).delete #=> bypasses hooks
126
+ # Post.filter(:category => 32).destroy #=> runs hooks
127
+ #
128
+ # 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.
129
+ #
130
+ # === Associations
131
+ #
132
+ # The most straightforward way to define an association in a Sequel model is as a regular instance method:
133
+ #
134
+ # class Post < Sequel::Model(:posts)
135
+ # def author; Author[author_id]; end
136
+ # end
137
+ #
138
+ # class Author < Sequel::Model(:authors)
139
+ # def posts; Post.filter(:author_id => pk); end
140
+ # end
141
+ #
142
+ # 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:
143
+ #
144
+ # class Post < Sequel::Model(:posts)
145
+ # one_to_one :author, :from => Author
146
+ # end
147
+ #
148
+ # post = Post.create(:name => 'hi!')
149
+ # post.author = Author[:name => 'Sharon']
150
+ #
151
+ # The one_to_many macro is roughly equivalent to ActiveRecord's has_many macro:
152
+ #
153
+ # class Author < Sequel::Model(:authors)
154
+ # one_to_many :posts, :from => Post, :key => :author_id
155
+ # end
156
+ #
157
+ # 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.
158
+ #
159
+ # === Caching model instances with memcached
160
+ #
161
+ # 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:
162
+ #
163
+ # require 'memcache'
164
+ # CACHE = MemCache.new 'localhost:11211', :namespace => 'blog'
165
+ #
166
+ # class Author < Sequel::Model(:authors)
167
+ # set_cache CACHE, :ttl => 3600
168
+ # end
169
+ #
170
+ # Author[333] # database hit
171
+ # Author[333] # cache hit
172
+ #
173
+ # === Extending the underlying dataset
174
+ #
175
+ # 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:
176
+ #
177
+ # class Post < Sequel::Model(:posts)
178
+ # def self.old_posts
179
+ # filter {:stamp < 30.days.ago}
180
+ # end
181
+ #
182
+ # def self.clean_old_posts
183
+ # old_posts.delete
184
+ # end
185
+ # end
186
+ #
187
+ # You can also implement table-wide logic by defining methods on the dataset:
188
+ #
189
+ # class Post < Sequel::Model(:posts)
190
+ # def dataset.old_posts
191
+ # filter {:stamp < 30.days.ago}
192
+ # end
193
+ #
194
+ # def dataset.clean_old_posts
195
+ # old_posts.delete
196
+ # end
197
+ # end
198
+ #
199
+ # 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:
200
+ #
201
+ # Post.filter(:category => 'ruby').clean_old_posts
202
+ #
203
+ # Sequel models also provide a short hand notation for filters:
204
+ #
205
+ # class Post < Sequel::Model(:posts)
206
+ # subset(:old_posts) {:stamp < 30.days.ago}
207
+ # subset :invisible, :visible => false
208
+ # end
209
+ #
210
+ # === Defining the underlying schema
211
+ #
212
+ # 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:
213
+ #
214
+ # class Post < Sequel::Model(:posts)
215
+ # set_schema do
216
+ # primary_key :id
217
+ # text :title
218
+ # text :category
219
+ # foreign_key :author_id, :table => authors
220
+ # end
221
+ # end
222
+ #
223
+ # You can then create the underlying table, drop it, or recreate it:
224
+ #
225
+ # Post.table_exists?
226
+ # Post.create_table
227
+ # Post.drop_table
228
+ # Post.create_table! # drops the table if it exists and then recreates it
229
+ #
230
+ class Model
231
+ alias_method :model, :class
232
+ end
233
+
234
+ end
235
+
236
+ files = %w[
237
+ base hooks record schema relations
238
+ caching plugins validations
239
+ ]
240
+ dir = File.join(File.dirname(__FILE__), "sequel_model")
241
+ files.each {|f| require(File.join(dir, f))}
242
+
243
+ module Sequel
244
+
245
+ class Model
246
+
247
+ # Defines a method that returns a filtered dataset.
248
+ def self.subset(name, *args, &block)
249
+ dataset.meta_def(name) {filter(*args, &block)}
250
+ end
251
+
252
+ # Finds a single record according to the supplied filter, e.g.:
253
+ #
254
+ # Ticket.find :author => 'Sharon' # => record
255
+ # Ticket.find {:price == 17} # => Dataset
256
+ #
257
+ def self.find(*args, &block)
258
+ dataset.filter(*args, &block).first
259
+ end
260
+
261
+ # TODO: doc
262
+ def self.[](*args)
263
+ args = args.first if (args.size == 1)
264
+ if args === true || args === false
265
+ raise Error::InvalidFilter, "Did you mean to supply a hash?"
266
+ end
267
+ dataset[(Hash === args) ? args : primary_key_hash(args)]
268
+ end
269
+
270
+ # TODO: doc
271
+ def self.fetch(*args)
272
+ db.fetch(*args).set_model(self)
273
+ end
274
+
275
+ # Like find but invokes create with given conditions when record does not
276
+ # exists.
277
+ def self.find_or_create(cond)
278
+ find(cond) || create(cond)
279
+ end
280
+
281
+ ############################################################################
282
+
283
+ # Deletes all records in the model's table.
284
+ def self.delete_all
285
+ dataset.delete
286
+ end
287
+
288
+ # Like delete_all, but invokes before_destroy and after_destroy hooks if used.
289
+ def self.destroy_all
290
+ if has_hooks?(:before_destroy) || has_hooks?(:after_destroy)
291
+ dataset.destroy
292
+ else
293
+ dataset.delete
294
+ end
295
+ end
296
+
297
+ def self.is_dataset_magic_method?(m)
298
+ method_name = m.to_s
299
+ Sequel::Dataset::MAGIC_METHODS.each_key do |r|
300
+ return true if method_name =~ r
301
+ end
302
+ false
303
+ end
304
+
305
+ def self.method_missing(m, *args, &block) #:nodoc:
306
+ Thread.exclusive do
307
+ if dataset.respond_to?(m) || is_dataset_magic_method?(m)
308
+ instance_eval("def #{m}(*args, &block); dataset.#{m}(*args, &block); end")
309
+ end
310
+ end
311
+ respond_to?(m) ? send(m, *args, &block) : super(m, *args)
312
+ end
313
+
314
+ # TODO: Comprehensive description goes here!
315
+ def self.join(*args)
316
+ table_name = dataset.opts[:from].first
317
+ dataset.join(*args).select(table_name.to_sym.ALL)
318
+ end
319
+
320
+ # Returns an array containing all of the models records.
321
+ def self.all
322
+ dataset.all
323
+ end
324
+ end
325
+
326
+ end