couchrest 0.38 → 1.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. data/README.md +8 -8
  2. data/Rakefile +3 -4
  3. data/couchrest.gemspec +25 -105
  4. data/history.txt +5 -4
  5. data/lib/couchrest.rb +31 -52
  6. data/lib/couchrest/{core/database.rb → database.rb} +6 -11
  7. data/lib/couchrest/{core/design.rb → design.rb} +2 -2
  8. data/lib/couchrest/{core/document.rb → document.rb} +1 -1
  9. data/lib/couchrest/helper/attachments.rb +29 -0
  10. data/lib/couchrest/middlewares/logger.rb +3 -3
  11. data/lib/couchrest/monkeypatches.rb +1 -71
  12. data/lib/couchrest/{core/response.rb → response.rb} +0 -0
  13. data/lib/couchrest/{core/rest_api.rb → rest_api.rb} +8 -12
  14. data/lib/couchrest/{core/server.rb → server.rb} +0 -2
  15. data/spec/couchrest/{core/couchrest_spec.rb → couchrest_spec.rb} +15 -9
  16. data/spec/couchrest/{core/database_spec.rb → database_spec.rb} +4 -4
  17. data/spec/couchrest/{core/design_spec.rb → design_spec.rb} +2 -2
  18. data/spec/couchrest/{core/document_spec.rb → document_spec.rb} +1 -1
  19. data/spec/couchrest/{core/server_spec.rb → server_spec.rb} +2 -2
  20. data/spec/spec.opts +0 -1
  21. data/spec/spec_helper.rb +0 -4
  22. metadata +32 -133
  23. data/examples/model/example.rb +0 -144
  24. data/lib/couchrest/core/adapters/restclient.rb +0 -35
  25. data/lib/couchrest/core/http_abstraction.rb +0 -48
  26. data/lib/couchrest/core/view.rb +0 -4
  27. data/lib/couchrest/mixins.rb +0 -4
  28. data/lib/couchrest/mixins/attachments.rb +0 -31
  29. data/lib/couchrest/mixins/attribute_protection.rb +0 -74
  30. data/lib/couchrest/mixins/callbacks.rb +0 -532
  31. data/lib/couchrest/mixins/class_proxy.rb +0 -124
  32. data/lib/couchrest/mixins/collection.rb +0 -260
  33. data/lib/couchrest/mixins/design_doc.rb +0 -103
  34. data/lib/couchrest/mixins/document_queries.rb +0 -80
  35. data/lib/couchrest/mixins/extended_attachments.rb +0 -70
  36. data/lib/couchrest/mixins/extended_document_mixins.rb +0 -9
  37. data/lib/couchrest/mixins/properties.rb +0 -158
  38. data/lib/couchrest/mixins/validation.rb +0 -246
  39. data/lib/couchrest/mixins/views.rb +0 -173
  40. data/lib/couchrest/more/casted_model.rb +0 -58
  41. data/lib/couchrest/more/extended_document.rb +0 -310
  42. data/lib/couchrest/more/property.rb +0 -58
  43. data/lib/couchrest/more/typecast.rb +0 -180
  44. data/lib/couchrest/support/blank.rb +0 -42
  45. data/lib/couchrest/support/rails.rb +0 -42
  46. data/lib/couchrest/validation/auto_validate.rb +0 -157
  47. data/lib/couchrest/validation/contextual_validators.rb +0 -78
  48. data/lib/couchrest/validation/validation_errors.rb +0 -125
  49. data/lib/couchrest/validation/validators/absent_field_validator.rb +0 -74
  50. data/lib/couchrest/validation/validators/confirmation_validator.rb +0 -107
  51. data/lib/couchrest/validation/validators/format_validator.rb +0 -122
  52. data/lib/couchrest/validation/validators/formats/email.rb +0 -66
  53. data/lib/couchrest/validation/validators/formats/url.rb +0 -43
  54. data/lib/couchrest/validation/validators/generic_validator.rb +0 -120
  55. data/lib/couchrest/validation/validators/length_validator.rb +0 -139
  56. data/lib/couchrest/validation/validators/method_validator.rb +0 -89
  57. data/lib/couchrest/validation/validators/numeric_validator.rb +0 -109
  58. data/lib/couchrest/validation/validators/required_field_validator.rb +0 -114
  59. data/spec/couchrest/more/attribute_protection_spec.rb +0 -150
  60. data/spec/couchrest/more/casted_extended_doc_spec.rb +0 -73
  61. data/spec/couchrest/more/casted_model_spec.rb +0 -406
  62. data/spec/couchrest/more/extended_doc_attachment_spec.rb +0 -135
  63. data/spec/couchrest/more/extended_doc_inherited_spec.rb +0 -40
  64. data/spec/couchrest/more/extended_doc_spec.rb +0 -807
  65. data/spec/couchrest/more/extended_doc_subclass_spec.rb +0 -98
  66. data/spec/couchrest/more/extended_doc_view_spec.rb +0 -456
  67. data/spec/couchrest/more/property_spec.rb +0 -628
  68. data/spec/fixtures/more/article.rb +0 -35
  69. data/spec/fixtures/more/card.rb +0 -22
  70. data/spec/fixtures/more/cat.rb +0 -20
  71. data/spec/fixtures/more/course.rb +0 -22
  72. data/spec/fixtures/more/event.rb +0 -8
  73. data/spec/fixtures/more/invoice.rb +0 -17
  74. data/spec/fixtures/more/person.rb +0 -9
  75. data/spec/fixtures/more/question.rb +0 -6
  76. data/spec/fixtures/more/service.rb +0 -12
  77. data/spec/fixtures/more/user.rb +0 -22
@@ -1,144 +0,0 @@
1
- require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'couchrest')
2
-
3
- def show obj
4
- puts obj.inspect
5
- puts
6
- end
7
-
8
- SERVER = CouchRest.new
9
- SERVER.default_database = 'couchrest-extendeddoc-example'
10
-
11
- class Author < CouchRest::ExtendedDocument
12
- use_database SERVER.default_database
13
- property :name
14
-
15
- def drink_scotch
16
- puts "... glug type glug ... I'm #{name} ... type glug glug ..."
17
- end
18
- end
19
-
20
- class Post < CouchRest::ExtendedDocument
21
- use_database SERVER.default_database
22
-
23
- property :title
24
- property :body
25
- property :author, :cast_as => 'Author'
26
-
27
- timestamps!
28
- end
29
-
30
- class Comment < CouchRest::ExtendedDocument
31
- use_database SERVER.default_database
32
-
33
- property :commenter, :cast_as => 'Author'
34
- timestamps!
35
-
36
- def post= post
37
- self["post_id"] = post.id
38
- end
39
- def post
40
- Post.get(self['post_id']) if self['post_id']
41
- end
42
-
43
- end
44
-
45
- puts "Act I: CRUD"
46
- puts
47
- puts "(pause for dramatic effect)"
48
- puts
49
- sleep 2
50
-
51
- puts "Create an author."
52
- quentin = Author.new("name" => "Quentin Hazel")
53
- show quentin
54
-
55
- puts "Create a new post."
56
- post = Post.new(:title => "First Post", :body => "Lorem ipsum dolor sit amet, consectetur adipisicing elit...")
57
- show post
58
-
59
- puts "Add the author to the post."
60
- post.author = quentin
61
- show post
62
-
63
- puts "Save the post."
64
- post.save
65
- show post
66
-
67
- puts "Load the post."
68
- reloaded = Post.get(post.id)
69
- show reloaded
70
-
71
- puts "The author of the post is an instance of Author."
72
- reloaded.author.drink_scotch
73
-
74
- puts "\nAdd some comments to the post."
75
- comment_one = Comment.new :text => "Blah blah blah", :commenter => {:name => "Joe Sixpack"}
76
- comment_two = Comment.new :text => "Yeah yeah yeah", :commenter => {:name => "Jane Doe"}
77
- comment_three = Comment.new :text => "Whatever...", :commenter => {:name => "John Stewart"}
78
-
79
- # TODO - maybe add some magic here?
80
- comment_one.post = post
81
- comment_two.post = post
82
- comment_three.post = post
83
- comment_one.save
84
- comment_two.save
85
- comment_three.save
86
-
87
- show comment_one
88
- show comment_two
89
- show comment_three
90
-
91
- puts "We can load a post through its comment (no magic here)."
92
- show post = comment_one.post
93
-
94
- puts "Commenters are also authors."
95
- comment_two['commenter'].drink_scotch
96
- comment_one['commenter'].drink_scotch
97
- comment_three['commenter'].drink_scotch
98
-
99
- puts "\nLet's save an author to her own document."
100
- jane = comment_two['commenter']
101
- jane.save
102
- show jane
103
-
104
- puts "Oh, that's neat! Because Ruby passes hash valuee by reference, Jane's new id has been added to the comment she left."
105
- show comment_two
106
-
107
- puts "Of course, we'd better remember to save it."
108
- comment_two.save
109
- show comment_two
110
-
111
- puts "Oooh, denormalized... feel the burn!"
112
- puts
113
- puts
114
- puts
115
- puts "Act II: Views"
116
- puts
117
- puts
118
- sleep 2
119
-
120
- puts "Let's find all the comments that go with our post."
121
- puts "Our post has id #{post.id}, so lets find all the comments with that post_id."
122
- puts
123
-
124
- class Comment
125
- view_by :post_id
126
- end
127
-
128
- comments = Comment.by_post_id :key => post.id
129
- show comments
130
-
131
- puts "That was too easy."
132
- puts "We can even wrap it up in a finder on the Post class."
133
- puts
134
-
135
- class Post
136
- def comments
137
- Comment.by_post_id :key => id
138
- end
139
- end
140
-
141
- show post.comments
142
- puts "Gimme 5 minutes and I'll roll this into the framework. ;)"
143
- puts
144
- puts "There is a lot more that can be done with views, but a lot of the interesting stuff is joins, which of course range across types. We'll pick up where we left off, next time."
@@ -1,35 +0,0 @@
1
- module RestClientAdapter
2
-
3
- module API
4
- def proxy=(url)
5
- RestClient.proxy = url
6
- end
7
-
8
- def proxy
9
- RestClient.proxy
10
- end
11
-
12
- def get(uri, headers={})
13
- RestClient.get(uri, headers).to_s
14
- end
15
-
16
- def post(uri, payload, headers={})
17
- RestClient.post(uri, payload, headers).to_s
18
- end
19
-
20
- def put(uri, payload, headers={})
21
- RestClient.put(uri, payload, headers).to_s
22
- end
23
-
24
- def delete(uri, headers={})
25
- RestClient.delete(uri, headers).to_s
26
- end
27
-
28
- def copy(uri, headers)
29
- RestClient::Request.execute( :method => :copy,
30
- :url => uri,
31
- :headers => headers).to_s
32
- end
33
- end
34
-
35
- end
@@ -1,48 +0,0 @@
1
- require 'couchrest/core/adapters/restclient'
2
-
3
- # Abstraction layet for HTTP communications.
4
- #
5
- # By defining a basic API that CouchRest is relying on,
6
- # it allows for easy experimentations and implementations of various libraries.
7
- #
8
- # Most of the API is based on the RestClient API that was used in the early version of CouchRest.
9
- #
10
- module HttpAbstraction
11
-
12
- # here is the list of exception expected by CouchRest
13
- # please convert the underlying errors in this set of known
14
- # exceptions.
15
- class ResourceNotFound < StandardError; end
16
- class RequestFailed < StandardError; end
17
- class RequestTimeout < StandardError; end
18
- class ServerBrokeConnection < StandardError; end
19
- class Conflict < StandardError; end
20
-
21
-
22
- # # Here is the API you need to implement if you want to write a new adapter
23
- # # See adapters/restclient.rb for more information.
24
- #
25
- # def self.proxy=(url)
26
- # end
27
- #
28
- # def self.proxy
29
- # end
30
- #
31
- # def self.get(uri, headers=nil)
32
- # end
33
- #
34
- # def self.post(uri, payload, headers=nil)
35
- # end
36
- #
37
- # def self.put(uri, payload, headers=nil)
38
- # end
39
- #
40
- # def self.delete(uri, headers=nil)
41
- # end
42
- #
43
- # def self.copy(uri, headers)
44
- # end
45
-
46
- end
47
-
48
- HttpAbstraction.extend(RestClientAdapter::API)
@@ -1,4 +0,0 @@
1
- module CouchRest
2
- class View
3
- end
4
- end
@@ -1,4 +0,0 @@
1
- mixins_dir = File.join(File.dirname(__FILE__), 'mixins')
2
-
3
- require File.join(mixins_dir, 'attachments')
4
- require File.join(mixins_dir, 'callbacks')
@@ -1,31 +0,0 @@
1
- module CouchRest
2
- module Mixins
3
- module Attachments
4
-
5
- # saves an attachment directly to couchdb
6
- def put_attachment(name, file, options={})
7
- raise ArgumentError, "doc must be saved" unless self.rev
8
- raise ArgumentError, "doc.database required to put_attachment" unless database
9
- result = database.put_attachment(self, name, file, options)
10
- self['_rev'] = result['rev']
11
- result['ok']
12
- end
13
-
14
- # returns an attachment's data
15
- def fetch_attachment(name)
16
- raise ArgumentError, "doc must be saved" unless self.rev
17
- raise ArgumentError, "doc.database required to put_attachment" unless database
18
- database.fetch_attachment(self, name)
19
- end
20
-
21
- # deletes an attachment directly from couchdb
22
- def delete_attachment(name, force=false)
23
- raise ArgumentError, "doc.database required to delete_attachment" unless database
24
- result = database.delete_attachment(self, name, force)
25
- self['_rev'] = result['rev']
26
- result['ok']
27
- end
28
-
29
- end
30
- end
31
- end
@@ -1,74 +0,0 @@
1
- module CouchRest
2
- module Mixins
3
- module AttributeProtection
4
- # Attribute protection from mass assignment to CouchRest properties
5
- #
6
- # Protected methods will be removed from
7
- # * new
8
- # * update_attributes
9
- # * upate_attributes_without_saving
10
- # * attributes=
11
- #
12
- # There are two modes of protection
13
- # 1) Declare accessible poperties, assume all the rest are protected
14
- # property :name, :accessible => true
15
- # property :admin # this will be automatically protected
16
- #
17
- # 2) Declare protected properties, assume all the rest are accessible
18
- # property :name # this will not be protected
19
- # property :admin, :protected => true
20
- #
21
- # Note: you cannot set both flags in a single class
22
-
23
- def self.included(base)
24
- base.extend(ClassMethods)
25
- end
26
-
27
- module ClassMethods
28
- def accessible_properties
29
- properties.select { |prop| prop.options[:accessible] }
30
- end
31
-
32
- def protected_properties
33
- properties.select { |prop| prop.options[:protected] }
34
- end
35
- end
36
-
37
- def accessible_properties
38
- self.class.accessible_properties
39
- end
40
-
41
- def protected_properties
42
- self.class.protected_properties
43
- end
44
-
45
- def remove_protected_attributes(attributes)
46
- protected_names = properties_to_remove_from_mass_assignment.map { |prop| prop.name }
47
- return attributes if protected_names.empty?
48
-
49
- attributes.reject! do |property_name, property_value|
50
- protected_names.include?(property_name.to_s)
51
- end
52
-
53
- attributes || {}
54
- end
55
-
56
- private
57
-
58
- def properties_to_remove_from_mass_assignment
59
- has_protected = !protected_properties.empty?
60
- has_accessible = !accessible_properties.empty?
61
-
62
- if !has_protected && !has_accessible
63
- []
64
- elsif has_protected && !has_accessible
65
- protected_properties
66
- elsif has_accessible && !has_protected
67
- properties.reject { |prop| prop.options[:accessible] }
68
- else
69
- raise "Set either :accessible or :protected for #{self.class}, but not both"
70
- end
71
- end
72
- end
73
- end
74
- end
@@ -1,532 +0,0 @@
1
- # Copyright (c) 2006-2009 David Heinemeier Hansson
2
- #
3
- # Permission is hereby granted, free of charge, to any person obtaining
4
- # a copy of this software and associated documentation files (the
5
- # "Software"), to deal in the Software without restriction, including
6
- # without limitation the rights to use, copy, modify, merge, publish,
7
- # distribute, sublicense, and/or sell copies of the Software, and to
8
- # permit persons to whom the Software is furnished to do so, subject to
9
- # the following conditions:
10
- #
11
- # The above copyright notice and this permission notice shall be
12
- # included in all copies or substantial portions of the Software.
13
- #
14
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
- # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
- # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
- # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
- # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
- # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
- # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
- #
22
- # Extracted from ActiveSupport::NewCallbacks written by Yehuda Katz
23
- # http://github.com/rails/rails/raw/d6e4113c83a9d55be6f2af247da2cecaa855f43b/activesupport/lib/active_support/new_callbacks.rb
24
- # http://github.com/rails/rails/commit/1126a85aed576402d978e6f76eb393b6baaa9541
25
-
26
- require File.join(File.dirname(__FILE__), '..', 'support', 'class')
27
-
28
- module CouchRest
29
- # Callbacks are hooks into the lifecycle of an object that allow you to trigger logic
30
- # before or after an alteration of the object state.
31
- #
32
- # Mixing in this module allows you to define callbacks in your class.
33
- #
34
- # Example:
35
- # class Storage
36
- # include ActiveSupport::Callbacks
37
- #
38
- # define_callbacks :save
39
- # end
40
- #
41
- # class ConfigStorage < Storage
42
- # save_callback :before, :saving_message
43
- # def saving_message
44
- # puts "saving..."
45
- # end
46
- #
47
- # save_callback :after do |object|
48
- # puts "saved"
49
- # end
50
- #
51
- # def save
52
- # _run_save_callbacks do
53
- # puts "- save"
54
- # end
55
- # end
56
- # end
57
- #
58
- # config = ConfigStorage.new
59
- # config.save
60
- #
61
- # Output:
62
- # saving...
63
- # - save
64
- # saved
65
- #
66
- # Callbacks from parent classes are inherited.
67
- #
68
- # Example:
69
- # class Storage
70
- # include ActiveSupport::Callbacks
71
- #
72
- # define_callbacks :save
73
- #
74
- # save_callback :before, :prepare
75
- # def prepare
76
- # puts "preparing save"
77
- # end
78
- # end
79
- #
80
- # class ConfigStorage < Storage
81
- # save_callback :before, :saving_message
82
- # def saving_message
83
- # puts "saving..."
84
- # end
85
- #
86
- # save_callback :after do |object|
87
- # puts "saved"
88
- # end
89
- #
90
- # def save
91
- # _run_save_callbacks do
92
- # puts "- save"
93
- # end
94
- # end
95
- # end
96
- #
97
- # config = ConfigStorage.new
98
- # config.save
99
- #
100
- # Output:
101
- # preparing save
102
- # saving...
103
- # - save
104
- # saved
105
- module Callbacks
106
- def self.included(klass)
107
- klass.extend ClassMethods
108
- end
109
-
110
- def run_callbacks(kind, options = {}, &blk)
111
- send("_run_#{kind}_callbacks", &blk)
112
- end
113
-
114
- class Callback
115
- @@_callback_sequence = 0
116
-
117
- attr_accessor :filter, :kind, :name, :options, :per_key, :klass
118
- def initialize(filter, kind, options, klass)
119
- @kind, @klass = kind, klass
120
-
121
- normalize_options!(options)
122
-
123
- @per_key = options.delete(:per_key)
124
- @raw_filter, @options = filter, options
125
- @filter = _compile_filter(filter)
126
- @compiled_options = _compile_options(options)
127
- @callback_id = next_id
128
-
129
- _compile_per_key_options
130
- end
131
-
132
- def clone(klass)
133
- obj = super()
134
- obj.klass = klass
135
- obj.per_key = @per_key.dup
136
- obj.options = @options.dup
137
- obj.per_key[:if] = @per_key[:if].dup
138
- obj.per_key[:unless] = @per_key[:unless].dup
139
- obj.options[:if] = @options[:if].dup
140
- obj.options[:unless] = @options[:unless].dup
141
- obj
142
- end
143
-
144
- def normalize_options!(options)
145
- options[:if] = Array.wrap(options[:if])
146
- options[:unless] = Array.wrap(options[:unless])
147
-
148
- options[:per_key] ||= {}
149
- options[:per_key][:if] = Array.wrap(options[:per_key][:if])
150
- options[:per_key][:unless] = Array.wrap(options[:per_key][:unless])
151
- end
152
-
153
- def next_id
154
- @@_callback_sequence += 1
155
- end
156
-
157
- def matches?(_kind, _filter)
158
- @kind == _kind &&
159
- @filter == _filter
160
- end
161
-
162
- def _update_filter(filter_options, new_options)
163
- filter_options[:if].push(new_options[:unless]) if new_options.key?(:unless)
164
- filter_options[:unless].push(new_options[:if]) if new_options.key?(:if)
165
- end
166
-
167
- def recompile!(_options, _per_key)
168
- _update_filter(self.options, _options)
169
- _update_filter(self.per_key, _per_key)
170
-
171
- @callback_id = next_id
172
- @filter = _compile_filter(@raw_filter)
173
- @compiled_options = _compile_options(@options)
174
- _compile_per_key_options
175
- end
176
-
177
- def _compile_per_key_options
178
- key_options = _compile_options(@per_key)
179
-
180
- @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
181
- def _one_time_conditions_valid_#{@callback_id}?
182
- true #{key_options[0]}
183
- end
184
- RUBY_EVAL
185
- end
186
-
187
- # This will supply contents for before and around filters, and no
188
- # contents for after filters (for the forward pass).
189
- def start(key = nil, options = {})
190
- object, terminator = (options || {}).values_at(:object, :terminator)
191
-
192
- return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?")
193
-
194
- terminator ||= false
195
-
196
- # options[0] is the compiled form of supplied conditions
197
- # options[1] is the "end" for the conditional
198
-
199
- if @kind == :before || @kind == :around
200
- if @kind == :before
201
- # if condition # before_save :filter_name, :if => :condition
202
- # filter_name
203
- # end
204
- filter = <<-RUBY_EVAL
205
- unless halted
206
- result = #{@filter}
207
- halted = (#{terminator})
208
- end
209
- RUBY_EVAL
210
-
211
- [@compiled_options[0], filter, @compiled_options[1]].compact.join("\n")
212
- else
213
- # Compile around filters with conditions into proxy methods
214
- # that contain the conditions.
215
- #
216
- # For `around_save :filter_name, :if => :condition':
217
- #
218
- # def _conditional_callback_save_17
219
- # if condition
220
- # filter_name do
221
- # yield self
222
- # end
223
- # else
224
- # yield self
225
- # end
226
- # end
227
-
228
- name = "_conditional_callback_#{@kind}_#{next_id}"
229
- txt, line = <<-RUBY_EVAL, __LINE__ + 1
230
- def #{name}(halted)
231
- #{@compiled_options[0] || "if true"} && !halted
232
- #{@filter} do
233
- yield self
234
- end
235
- else
236
- yield self
237
- end
238
- end
239
- RUBY_EVAL
240
- @klass.class_eval(txt, __FILE__, line)
241
- "#{name}(halted) do"
242
- end
243
- end
244
- end
245
-
246
- # This will supply contents for around and after filters, but not
247
- # before filters (for the backward pass).
248
- def end(key = nil, options = {})
249
- object = (options || {})[:object]
250
-
251
- return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?")
252
-
253
- if @kind == :around || @kind == :after
254
- # if condition # after_save :filter_name, :if => :condition
255
- # filter_name
256
- # end
257
- if @kind == :after
258
- [@compiled_options[0], @filter, @compiled_options[1]].compact.join("\n")
259
- else
260
- "end"
261
- end
262
- end
263
- end
264
-
265
- private
266
- # Options support the same options as filters themselves (and support
267
- # symbols, string, procs, and objects), so compile a conditional
268
- # expression based on the options
269
- def _compile_options(options)
270
- return [] if options[:if].empty? && options[:unless].empty?
271
-
272
- conditions = []
273
-
274
- unless options[:if].empty?
275
- conditions << Array.wrap(_compile_filter(options[:if]))
276
- end
277
-
278
- unless options[:unless].empty?
279
- conditions << Array.wrap(_compile_filter(options[:unless])).map {|f| "!#{f}"}
280
- end
281
-
282
- ["if #{conditions.flatten.join(" && ")}", "end"]
283
- end
284
-
285
- # Filters support:
286
- # Arrays:: Used in conditions. This is used to specify
287
- # multiple conditions. Used internally to
288
- # merge conditions from skip_* filters
289
- # Symbols:: A method to call
290
- # Strings:: Some content to evaluate
291
- # Procs:: A proc to call with the object
292
- # Objects:: An object with a before_foo method on it to call
293
- #
294
- # All of these objects are compiled into methods and handled
295
- # the same after this point:
296
- # Arrays:: Merged together into a single filter
297
- # Symbols:: Already methods
298
- # Strings:: class_eval'ed into methods
299
- # Procs:: define_method'ed into methods
300
- # Objects::
301
- # a method is created that calls the before_foo method
302
- # on the object.
303
- def _compile_filter(filter)
304
- method_name = "_callback_#{@kind}_#{next_id}"
305
- case filter
306
- when Array
307
- filter.map {|f| _compile_filter(f)}
308
- when Symbol
309
- filter
310
- when String
311
- "(#{filter})"
312
- when Proc
313
- @klass.send(:define_method, method_name, &filter)
314
- return method_name if filter.arity == 0
315
-
316
- method_name << (filter.arity == 1 ? "(self)" : " self, Proc.new ")
317
- else
318
- @klass.send(:define_method, "#{method_name}_object") { filter }
319
-
320
- _normalize_legacy_filter(kind, filter)
321
-
322
- @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
323
- def #{method_name}(&blk)
324
- #{method_name}_object.send(:#{kind}, self, &blk)
325
- end
326
- RUBY_EVAL
327
-
328
- method_name
329
- end
330
- end
331
-
332
- def _normalize_legacy_filter(kind, filter)
333
- if !filter.respond_to?(kind) && filter.respond_to?(:filter)
334
- filter.class_eval(
335
- "def #{kind}(context, &block) filter(context, &block) end",
336
- __FILE__, __LINE__ - 1)
337
- elsif filter.respond_to?(:before) && filter.respond_to?(:after) && kind == :around
338
- def filter.around(context)
339
- should_continue = before(context)
340
- yield if should_continue
341
- after(context)
342
- end
343
- end
344
- end
345
-
346
- end
347
-
348
- # An Array with a compile method
349
- class CallbackChain < Array
350
- def initialize(symbol)
351
- @symbol = symbol
352
- end
353
-
354
- def compile(key = nil, options = {})
355
- method = []
356
- method << "halted = false"
357
- each do |callback|
358
- method << callback.start(key, options)
359
- end
360
- method << "yield self if block_given? && !halted"
361
- reverse_each do |callback|
362
- method << callback.end(key, options)
363
- end
364
- method.compact.join("\n")
365
- end
366
-
367
- def clone(klass)
368
- chain = CallbackChain.new(@symbol)
369
- chain.push(*map {|c| c.clone(klass)})
370
- end
371
- end
372
-
373
- module ClassMethods
374
- #CHAINS = {:before => :before, :around => :before, :after => :after}
375
-
376
- # Make the _run_save_callbacks method. The generated method takes
377
- # a block that it'll yield to. It'll call the before and around filters
378
- # in order, yield the block, and then run the after filters.
379
- #
380
- # _run_save_callbacks do
381
- # save
382
- # end
383
- #
384
- # The _run_save_callbacks method can optionally take a key, which
385
- # will be used to compile an optimized callback method for each
386
- # key. See #define_callbacks for more information.
387
- def _define_runner(symbol)
388
- body = send("_#{symbol}_callback").
389
- compile(nil, :terminator => send("_#{symbol}_terminator"))
390
-
391
- body, line = <<-RUBY_EVAL, __LINE__ + 1
392
- def _run_#{symbol}_callbacks(key = nil, &blk)
393
- if key
394
- name = "_run__\#{self.class.name.hash.abs}__#{symbol}__\#{key.hash.abs}__callbacks"
395
-
396
- unless respond_to?(name)
397
- self.class._create_keyed_callback(name, :#{symbol}, self, &blk)
398
- end
399
-
400
- send(name, &blk)
401
- else
402
- #{body}
403
- end
404
- end
405
- RUBY_EVAL
406
-
407
- undef_method "_run_#{symbol}_callbacks" if method_defined?("_run_#{symbol}_callbacks")
408
- class_eval body, __FILE__, line
409
- end
410
-
411
- # This is called the first time a callback is called with a particular
412
- # key. It creates a new callback method for the key, calculating
413
- # which callbacks can be omitted because of per_key conditions.
414
- def _create_keyed_callback(name, kind, obj, &blk)
415
- @_keyed_callbacks ||= {}
416
- @_keyed_callbacks[name] ||= begin
417
- str = send("_#{kind}_callback").
418
- compile(name, :object => obj, :terminator => send("_#{kind}_terminator"))
419
-
420
- class_eval "def #{name}() #{str} end", __FILE__, __LINE__
421
-
422
- true
423
- end
424
- end
425
-
426
- # Define callbacks.
427
- #
428
- # Creates a <name>_callback method that you can use to add callbacks.
429
- #
430
- # Syntax:
431
- # save_callback :before, :before_meth
432
- # save_callback :after, :after_meth, :if => :condition
433
- # save_callback :around {|r| stuff; yield; stuff }
434
- #
435
- # The <name>_callback method also updates the _run_<name>_callbacks
436
- # method, which is the public API to run the callbacks.
437
- #
438
- # Also creates a skip_<name>_callback method that you can use to skip
439
- # callbacks.
440
- #
441
- # When creating or skipping callbacks, you can specify conditions that
442
- # are always the same for a given key. For instance, in ActionPack,
443
- # we convert :only and :except conditions into per-key conditions.
444
- #
445
- # before_filter :authenticate, :except => "index"
446
- # becomes
447
- # dispatch_callback :before, :authenticate, :per_key => {:unless => proc {|c| c.action_name == "index"}}
448
- #
449
- # Per-Key conditions are evaluated only once per use of a given key.
450
- # In the case of the above example, you would do:
451
- #
452
- # run_dispatch_callbacks(action_name) { ... dispatch stuff ... }
453
- #
454
- # In that case, each action_name would get its own compiled callback
455
- # method that took into consideration the per_key conditions. This
456
- # is a speed improvement for ActionPack.
457
- def _update_callbacks(name, filters = CallbackChain.new(name), block = nil)
458
- type = [:before, :after, :around].include?(filters.first) ? filters.shift : :before
459
- options = filters.last.is_a?(Hash) ? filters.pop : {}
460
- filters.unshift(block) if block
461
-
462
- callbacks = send("_#{name}_callback")
463
- yield callbacks, type, filters, options if block_given?
464
-
465
- _define_runner(name)
466
- end
467
-
468
- alias_method :_reset_callbacks, :_update_callbacks
469
-
470
- def set_callback(name, *filters, &block)
471
- _update_callbacks(name, filters, block) do |callbacks, type, filters, options|
472
- filters.map! do |filter|
473
- # overrides parent class
474
- callbacks.delete_if {|c| c.matches?(type, filter) }
475
- Callback.new(filter, type, options.dup, self)
476
- end
477
-
478
- options[:prepend] ? callbacks.unshift(*filters) : callbacks.push(*filters)
479
- end
480
- end
481
-
482
- def skip_callback(name, *filters, &block)
483
- _update_callbacks(name, filters, block) do |callbacks, type, filters, options|
484
- filters.each do |filter|
485
- callbacks = send("_#{name}_callback=", callbacks.clone(self))
486
-
487
- filter = callbacks.find {|c| c.matches?(type, filter) }
488
-
489
- if filter && options.any?
490
- filter.recompile!(options, options[:per_key] || {})
491
- else
492
- callbacks.delete(filter)
493
- end
494
- end
495
- end
496
- end
497
-
498
- def define_callbacks(*symbols)
499
- terminator = symbols.pop if symbols.last.is_a?(String)
500
- symbols.each do |symbol|
501
- extlib_inheritable_accessor("_#{symbol}_terminator") { terminator }
502
-
503
- extlib_inheritable_accessor("_#{symbol}_callback") do
504
- CallbackChain.new(symbol)
505
- end
506
-
507
- _define_runner(symbol)
508
-
509
- # Define more convenient callback methods
510
- # set_callback(:save, :before) becomes before_save
511
- [:before, :after, :around].each do |filter|
512
- self.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
513
- def self.#{filter}_#{symbol}(*symbols, &blk)
514
- _alias_callbacks(symbols, blk) do |callback, options|
515
- set_callback(:#{symbol}, :#{filter}, callback, options)
516
- end
517
- end
518
- RUBY_EVAL
519
- end
520
- end
521
- end
522
-
523
- def _alias_callbacks(callbacks, block)
524
- options = callbacks.last.is_a?(Hash) ? callbacks.pop : {}
525
- callbacks.push(block) if block
526
- callbacks.each do |callback|
527
- yield callback, options
528
- end
529
- end
530
- end
531
- end
532
- end