sequel 0.2.1.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 == '='