sequel 0.2.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,36 +1,41 @@
1
1
  module Sequel
2
2
  class Model
3
- # returns the database associated with the model class
3
+ # Returns the database associated with the Model class.
4
4
  def self.db
5
- @db ||= ((superclass != Object) && (superclass.db)) || \
6
- raise(SequelError, "No database associated with #{self}.")
5
+ @db ||= (superclass != Object) && superclass.db or
6
+ raise SequelError, "No database associated with #{self}"
7
7
  end
8
8
 
9
- # sets the database associated with the model class
10
- def self.db=(db); @db = db; end
9
+ # Sets the database associated with the Model class.
10
+ def self.db=(db)
11
+ @db = db
12
+ end
11
13
 
12
- # called when a database is opened in order to automatically associate the
14
+ # Called when a database is opened in order to automatically associate the
13
15
  # first opened database with model classes.
14
16
  def self.database_opened(db)
15
17
  @db = db if self == Model && !@db
16
18
  end
17
19
 
18
- # returns the dataset associated with the model class.
20
+ # Returns the dataset associated with the Model class.
19
21
  def self.dataset
20
- @dataset || super_dataset || raise(SequelError, "No dataset associated with #{self}.")
22
+ @dataset || super_dataset or
23
+ raise SequelError, "No dataset associated with #{self}"
21
24
  end
22
25
 
23
- def self.super_dataset
24
- if superclass && superclass.respond_to?(:dataset) && ds = superclass.dataset
25
- ds
26
- end
26
+ def self.super_dataset # :nodoc:
27
+ superclass.dataset if superclass and superclass.respond_to? :dataset
27
28
  end
28
29
 
30
+ # Returns the columns in the result set in their original order.
31
+ #
32
+ # See Dataset#columns for more information.
29
33
  def self.columns
30
- @columns ||= @dataset.columns || raise(SequelError, "Could not fetch columns for #{self}")
34
+ @columns ||= @dataset.columns or
35
+ raise SequelError, "Could not fetch columns for #{self}"
31
36
  end
32
37
 
33
- # Sets the dataset associated with the model class.
38
+ # Sets the dataset associated with the Model class.
34
39
  def self.set_dataset(ds)
35
40
  @db = ds.db
36
41
  @dataset = ds
@@ -38,42 +43,48 @@ module Sequel
38
43
  @dataset.transform(@transform) if @transform
39
44
  end
40
45
 
41
- def model
42
- @model ||= self.class
43
- end
44
-
45
- # Returns the dataset assoiated with the object's model class.
46
+ # Returns the database assoiated with the object's Model class.
46
47
  def db
47
48
  @db ||= model.db
48
49
  end
49
50
 
50
- # Returns the dataset assoiated with the object's model class.
51
+ # Returns the dataset assoiated with the object's Model class.
52
+ #
53
+ # See Dataset for more information.
51
54
  def dataset
52
- @dataset ||= model.dataset
55
+ model.dataset
53
56
  end
54
57
 
58
+ # Returns the columns associated with the object's Model class.
55
59
  def columns
56
- @columns ||= model.columns
60
+ model.columns
57
61
  end
58
62
 
59
- SERIALIZE_FORMATS = {
60
- :yaml => [proc {|v| YAML.load v if v}, proc {|v| v.to_yaml}],
61
- :marshal => [proc {|v| Marshal.load(v) if v}, proc {|v| Marshal.dump(v)}]
62
- }
63
-
63
+ # Serializes column with YAML or through marshalling.
64
64
  def self.serialize(*columns)
65
65
  format = columns.pop[:format] if Hash === columns.last
66
- filters = SERIALIZE_FORMATS[format || :yaml]
67
- # add error handling here
66
+ format ||= :yaml
68
67
 
69
68
  @transform = columns.inject({}) do |m, c|
70
- m[c] = filters
69
+ m[c] = format
71
70
  m
72
71
  end
73
72
  @dataset.transform(@transform) if @dataset
74
73
  end
75
74
  end
76
75
 
76
+ # Lets you create a Model class with its table name already set or reopen
77
+ # an existing Model.
78
+ #
79
+ # Makes given dataset inherited.
80
+ #
81
+ # === Example:
82
+ # class Comment < Sequel::Model(:comments)
83
+ # table_name # => :comments
84
+ #
85
+ # # ...
86
+ #
87
+ # end
77
88
  def self.Model(source)
78
89
  @models ||= {}
79
90
  @models[source] ||= Class.new(Sequel::Model) do
@@ -0,0 +1,42 @@
1
+ module Sequel
2
+ class Model
3
+ def self.set_cache(store, opts = {})
4
+ @cache_store = store
5
+ if (ttl = opts[:ttl])
6
+ set_cache_ttl(ttl)
7
+ end
8
+
9
+ meta_def(:[]) do |*args|
10
+ if (args.size == 1) && (Hash === (h = args.first))
11
+ return dataset[h]
12
+ end
13
+
14
+ unless obj = @cache_store.get(cache_key_from_values(args))
15
+ obj = dataset[primary_key_hash((args.size == 1) ? args.first : args)]
16
+ @cache_store.set(cache_key_from_values(args), obj, cache_ttl)
17
+ end
18
+ obj
19
+ end
20
+
21
+ class_def(:set) {|v| store.delete(cache_key); super}
22
+ class_def(:save) {store.delete(cache_key); super}
23
+ class_def(:delete) {store.delete(cache_key); super}
24
+ end
25
+
26
+ def self.set_cache_ttl(ttl)
27
+ @cache_ttl = ttl
28
+ end
29
+
30
+ def self.cache_store
31
+ @cache_store
32
+ end
33
+
34
+ def self.cache_ttl
35
+ @cache_ttl ||= 3600
36
+ end
37
+
38
+ def self.cache_key_from_values(values)
39
+ "#{self}:#{values.join(',')}"
40
+ end
41
+ end
42
+ end
@@ -1,40 +1,126 @@
1
1
  module Sequel
2
2
  class Model
3
- def self.get_hooks(key)
4
- @hooks ||= {}
5
- @hooks[key] ||= []
3
+
4
+ class ChainBroken < RuntimeError # :nodoc:
6
5
  end
7
-
8
- def self.has_hooks?(key)
9
- !get_hooks(key).empty?
6
+
7
+ # This Hash translates verbs to methodnames used in chain manipulation
8
+ # methods.
9
+ VERB_TO_METHOD = {:prepend => :unshift, :append => :push}
10
+
11
+ # Returns @hooks which is an instance of Hash with its hook identifier
12
+ # (Symbol) as key and the chain of hooks (Array) as value.
13
+ #
14
+ # If it is not already set it'll be with an empty set of hooks.
15
+ # This behaviour will change in the future to allow inheritance.
16
+ #
17
+ # For the time being, you should be able to do:
18
+ #
19
+ # class A < Sequel::Model(:a)
20
+ # before_save { 'Do something...' }
21
+ # end
22
+ #
23
+ # class B < A
24
+ # @hooks = superclass.hooks.clone
25
+ # before_save # => [#<Proc:0x0000c6e8@(example.rb):123>]
26
+ # end
27
+ #
28
+ # In this case you should remember that the clone doesn't create any new
29
+ # instances of your chains, so if you change the chain here it changes in
30
+ # its superclass, too.
31
+ def self.hooks
32
+ @hooks ||= Hash.new { |h, k| h[k] = [] }
10
33
  end
11
-
12
- def run_hooks(key)
13
- self.class.get_hooks(key).each {|h| instance_eval(&h)}
34
+
35
+ # Adds block to chain of Hooks for <tt>:before_save</tt>.
36
+ # It can either be prepended (default) or appended.
37
+ #
38
+ # Returns the chain itself.
39
+ #
40
+ # Valid verbs are <tt>:prepend</tt> and <tt>:append</tt>.
41
+ def self.before_save(verb = :prepend, &block)
42
+ hooks[:before_save].send VERB_TO_METHOD.fetch(verb), block if block
43
+ hooks[:before_save]
14
44
  end
15
-
16
- def self.before_save(&block)
17
- get_hooks(:before_save).unshift(block)
45
+ # Adds block to chain of Hooks for <tt>:before_create</tt>.
46
+ # It can either be prepended (default) or appended.
47
+ #
48
+ # Returns the chain itself.
49
+ #
50
+ # Valid verbs are <tt>:prepend</tt> and <tt>:append</tt>.
51
+ def self.before_create(verb = :prepend, &block)
52
+ hooks[:before_create].send VERB_TO_METHOD.fetch(verb), block if block
53
+ hooks[:before_create]
18
54
  end
19
-
20
- def self.before_create(&block)
21
- get_hooks(:before_create).unshift(block)
55
+ # Adds block to chain of Hooks for <tt>:before_update</tt>.
56
+ # It can either be prepended (default) or appended.
57
+ #
58
+ # Returns the chain itself.
59
+ #
60
+ # Valid verbs are <tt>:prepend</tt> and <tt>:append</tt>.
61
+ def self.before_update(verb = :prepend, &block)
62
+ hooks[:before_update].send VERB_TO_METHOD.fetch(verb), block if block
63
+ hooks[:before_update]
22
64
  end
23
-
24
- def self.before_destroy(&block)
25
- get_hooks(:before_destroy).unshift(block)
65
+ # Adds block to chain of Hooks for <tt>:before_destroy</tt>.
66
+ # It can either be prepended (default) or appended.
67
+ #
68
+ # Returns the chain itself.
69
+ #
70
+ # Valid verbs are <tt>:prepend</tt> and <tt>:append</tt>.
71
+ def self.before_destroy(verb = :prepend, &block)
72
+ hooks[:before_destroy].send VERB_TO_METHOD.fetch(verb), block if block
73
+ hooks[:before_destroy]
26
74
  end
27
-
28
- def self.after_save(&block)
29
- get_hooks(:after_save) << block
75
+
76
+ # Adds block to chain of Hooks for <tt>:after_save</tt>.
77
+ # It can either be prepended or appended (default).
78
+ #
79
+ # Returns the chain itself.
80
+ #
81
+ # Valid verbs are <tt>:prepend</tt> and <tt>:append</tt>.
82
+ def self.after_save(verb = :append, &block)
83
+ hooks[:after_save].send VERB_TO_METHOD.fetch(verb), block if block
84
+ hooks[:after_save]
30
85
  end
31
-
32
- def self.after_create(&block)
33
- get_hooks(:after_create) << block
86
+ # Adds block to chain of Hooks for <tt>:after_create</tt>.
87
+ # It can either be prepended or appended (default).
88
+ #
89
+ # Returns the chain itself.
90
+ #
91
+ # Valid verbs are <tt>:prepend</tt> and <tt>:append</tt>.
92
+ def self.after_create(verb = :append, &block)
93
+ hooks[:after_create].send VERB_TO_METHOD.fetch(verb), block if block
94
+ hooks[:after_create]
95
+ end
96
+ # Adds block to chain of Hooks for <tt>:after_update</tt>.
97
+ # It can either be prepended or appended (default).
98
+ #
99
+ # Returns the chain itself.
100
+ #
101
+ # Valid verbs are <tt>:prepend</tt> and <tt>:append</tt>.
102
+ def self.after_update(verb = :append, &block)
103
+ hooks[:after_update].send VERB_TO_METHOD.fetch(verb), block if block
104
+ hooks[:after_update]
105
+ end
106
+ # Adds block to chain of Hooks for <tt>:after_destroy</tt>.
107
+ # It can either be prepended or appended (default).
108
+ #
109
+ # Returns the chain itself.
110
+ #
111
+ # Valid verbs are <tt>:prepend</tt> and <tt>:append</tt>.
112
+ def self.after_destroy(verb = :append, &block)
113
+ hooks[:after_destroy].send VERB_TO_METHOD.fetch(verb), block if block
114
+ hooks[:after_destroy]
115
+ end
116
+
117
+ # Evaluates specified chain of Hooks through <tt>instance_eval</tt>.
118
+ def run_hooks(key)
119
+ model.hooks[key].each { |h| instance_eval &h }
34
120
  end
35
121
 
36
- def self.after_destroy(&block)
37
- get_hooks(:after_destroy) << block
122
+ def self.has_hooks?(key)
123
+ hooks[key] && !hooks[key].empty?
38
124
  end
39
125
  end
40
- end
126
+ end
@@ -2,41 +2,75 @@ module Sequel
2
2
  class Model
3
3
  attr_reader :values
4
4
 
5
- def self.primary_key; :id; end
6
- def self.primary_key_hash(v); {:id => v}; end
5
+ # Returns key for primary key.
6
+ def self.primary_key
7
+ :id
8
+ end
9
+
10
+ # Returns primary key attribute hash.
11
+ def self.primary_key_hash(value)
12
+ {:id => value}
13
+ end
7
14
 
8
- def self.set_primary_key(key)
15
+ # Sets primary key, regular and composite are possible.
16
+ #
17
+ # == Example:
18
+ # class Tagging < Sequel::Model(:taggins)
19
+ # # composite key
20
+ # set_primary_key :taggable_id, :tag_id
21
+ # end
22
+ #
23
+ # class Person < Sequel::Model(:person)
24
+ # # regular key
25
+ # set_primary_key :person_id
26
+ # end
27
+ #
28
+ # <i>You can even set it to nil!</i>
29
+ def self.set_primary_key(*key)
9
30
  # if k is nil, we go to no_primary_key
10
31
  return no_primary_key unless key
11
32
 
33
+ # backwards compat
34
+ key = (key.length == 1) ? key[0] : key.flatten
35
+
12
36
  # redefine primary_key
13
37
  meta_def(:primary_key) {key}
14
38
 
15
- if key.is_a?(Array) # composite key
39
+ unless key.is_a? Array # regular primary key
16
40
  class_def(:this) do
17
- @this ||= self.class.dataset.filter( \
18
- @values.reject {|k, v| !key.include?(k)} \
19
- ).naked
41
+ @this ||= dataset.filter(key => @values[key]).limit(1).naked
20
42
  end
21
- meta_def(:primary_key_hash) do |v|
22
- key.inject({}) {|m, i| m[i] = v.shift; m}
23
- end
24
- else # regular key
25
- class_def(:this) do
26
- @this ||= self.class.dataset.filter(key => @values[key]).naked
43
+ class_def(:cache_key) do
44
+ pk = @values[key] || (raise SequelError, 'no primary key for this record')
45
+ @cache_key ||= "#{self.class}:#{pk}"
27
46
  end
28
47
  meta_def(:primary_key_hash) do |v|
29
48
  {key => v}
30
49
  end
50
+ else # composite key
51
+ exp_list = key.map {|k| "#{k.inspect} => @values[#{k.inspect}]"}
52
+ block = eval("proc {@this ||= self.class.dataset.filter(#{exp_list.join(',')}).limit(1).naked}")
53
+ class_def(:this, &block)
54
+
55
+ exp_list = key.map {|k| '#{@values[%s]}' % k.inspect}.join(',')
56
+ block = eval('proc {@cache_key ||= "#{self.class}:%s"}' % exp_list)
57
+ class_def(:cache_key, &block)
58
+
59
+ meta_def(:primary_key_hash) do |v|
60
+ key.inject({}) {|m, i| m[i] = v.shift; m}
61
+ end
31
62
  end
32
63
  end
33
64
 
34
- def self.no_primary_key
65
+ def self.no_primary_key #:nodoc:
35
66
  meta_def(:primary_key) {nil}
36
67
  meta_def(:primary_key_hash) {|v| raise SequelError, "#{self} does not have a primary key"}
37
68
  class_def(:this) {raise SequelError, "No primary key is associated with this model"}
69
+ class_def(:cache_key) {raise SequelError, "No primary key is associated with this model"}
38
70
  end
39
71
 
72
+ # Creates new instance with values set to passed-in Hash ensuring that
73
+ # new? returns true.
40
74
  def self.create(values = {})
41
75
  db.transaction do
42
76
  obj = new(values, true)
@@ -45,36 +79,55 @@ module Sequel
45
79
  end
46
80
  end
47
81
 
82
+ # Returns (naked) dataset bound to current instance.
48
83
  def this
49
- @this ||= self.class.dataset.filter(:id => @values[:id]).naked
84
+ @this ||= self.class.dataset.filter(:id => @values[:id]).limit(1).naked
50
85
  end
51
86
 
52
- # instance method
87
+ # Returns a key unique to the underlying record for caching
88
+ def cache_key
89
+ pk = @values[:id] || (raise SequelError, 'no primary key for this record')
90
+ @cache_key ||= "#{self.class}:#{pk}"
91
+ end
92
+
93
+ # Returns primary key column(s) for object's Model class.
53
94
  def primary_key
54
95
  @primary_key ||= self.class.primary_key
55
96
  end
56
97
 
98
+ # Returns value for primary key.
57
99
  def pkey
58
100
  @pkey ||= @values[primary_key]
59
101
  end
60
102
 
61
- def initialize(values = {}, new = false)
103
+ # Creates new instance with values set to passed-in Hash.
104
+ #
105
+ # This method guesses whether the record exists when
106
+ # <tt>new_record</tt> is set to false.
107
+ def initialize(values = {}, new_record = false)
62
108
  @values = values
63
- @new = new
64
- if !new # determine if it's a new record
109
+
110
+ @new = new_record
111
+ unless @new # determine if it's a new record
65
112
  pk = primary_key
113
+ # if there's no primary key for the model class, or
114
+ # @values doesn't contain a primary key value, then
115
+ # we regard this instance as new.
66
116
  @new = (pk == nil) || (!(Array === pk) && !@values[pk])
67
117
  end
68
118
  end
69
119
 
120
+ # Returns true if the current instance represents a new record.
70
121
  def new?
71
122
  @new
72
123
  end
73
124
 
125
+ # Returns true when current instance exists, false otherwise.
74
126
  def exists?
75
127
  this.count > 0
76
128
  end
77
129
 
130
+ # Creates or updates dataset for Model and runs hooks.
78
131
  def save
79
132
  run_hooks(:before_save)
80
133
  if @new
@@ -100,16 +153,19 @@ module Sequel
100
153
  self
101
154
  end
102
155
 
156
+ # Updates and saves values to database from the passed-in Hash.
103
157
  def set(values)
104
158
  this.update(values)
105
- @values.merge!(values)
159
+ values.each {|k, v| @values[k] = v}
106
160
  end
107
161
 
162
+ # Reloads values from database and returns self.
108
163
  def refresh
109
164
  @values = this.first || raise(SequelError, "Record not found")
110
165
  self
111
166
  end
112
167
 
168
+ # Like delete but runs hooks before and after delete.
113
169
  def destroy
114
170
  db.transaction do
115
171
  run_hooks(:before_destroy)
@@ -118,6 +174,7 @@ module Sequel
118
174
  end
119
175
  end
120
176
 
177
+ # Deletes and returns self.
121
178
  def delete
122
179
  this.delete
123
180
  self
@@ -125,7 +182,7 @@ module Sequel
125
182
 
126
183
  ATTR_RE = /^([a-zA-Z_]\w*)(=)?$/.freeze
127
184
 
128
- def method_missing(m, *args)
185
+ def method_missing(m, *args) #:nodoc:
129
186
  if m.to_s =~ ATTR_RE
130
187
  att = $1.to_sym
131
188
  write = $2 == '='