activemodel 3.0.0.beta4 → 3.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/CHANGELOG +1 -39
  2. data/MIT-LICENSE +1 -1
  3. data/README +16 -200
  4. data/lib/active_model.rb +19 -28
  5. data/lib/active_model/attribute_methods.rb +27 -142
  6. data/lib/active_model/conversion.rb +1 -37
  7. data/lib/active_model/dirty.rb +12 -51
  8. data/lib/active_model/errors.rb +22 -146
  9. data/lib/active_model/lint.rb +14 -48
  10. data/lib/active_model/locale/en.yml +23 -26
  11. data/lib/active_model/naming.rb +5 -41
  12. data/lib/active_model/observing.rb +16 -35
  13. data/lib/active_model/serialization.rb +0 -57
  14. data/lib/active_model/serializers/json.rb +8 -13
  15. data/lib/active_model/serializers/xml.rb +123 -63
  16. data/lib/active_model/state_machine.rb +70 -0
  17. data/lib/active_model/state_machine/event.rb +62 -0
  18. data/lib/active_model/state_machine/machine.rb +75 -0
  19. data/lib/active_model/state_machine/state.rb +47 -0
  20. data/lib/active_model/state_machine/state_transition.rb +40 -0
  21. data/lib/active_model/test_case.rb +2 -0
  22. data/lib/active_model/validations.rb +62 -125
  23. data/lib/active_model/validations/acceptance.rb +18 -23
  24. data/lib/active_model/validations/confirmation.rb +10 -14
  25. data/lib/active_model/validations/exclusion.rb +13 -15
  26. data/lib/active_model/validations/format.rb +24 -26
  27. data/lib/active_model/validations/inclusion.rb +13 -15
  28. data/lib/active_model/validations/length.rb +65 -61
  29. data/lib/active_model/validations/numericality.rb +58 -76
  30. data/lib/active_model/validations/presence.rb +8 -8
  31. data/lib/active_model/validations/with.rb +22 -90
  32. data/lib/active_model/validations_repair_helper.rb +35 -0
  33. data/lib/active_model/version.rb +2 -3
  34. metadata +19 -63
  35. data/lib/active_model/callbacks.rb +0 -134
  36. data/lib/active_model/railtie.rb +0 -2
  37. data/lib/active_model/translation.rb +0 -60
  38. data/lib/active_model/validations/validates.rb +0 -108
  39. data/lib/active_model/validator.rb +0 -183
data/CHANGELOG CHANGED
@@ -1,42 +1,4 @@
1
- *Rails 3.0.0 [beta 4] (June 8th, 2010)*
2
-
3
- * JSON supports a custom root option: to_json(:root => 'custom') #4515 [Jatinder Singh]
4
-
5
-
6
- *Rails 3.0.0 [beta 3] (April 13th, 2010)*
7
-
8
- * No changes
9
-
10
-
11
- *Rails 3.0.0 [beta 2] (April 1st, 2010)*
12
-
13
- * #new_record? and #destroyed? were removed from ActiveModel::Lint. Use
14
- persisted? instead. A model is persisted if it's not a new_record? and it was
15
- not destroyed? [MG]
16
-
17
- * Added validations reflection in ActiveModel::Validations [JV]
18
-
19
- Model.validators
20
- Model.validators_on(:field)
21
-
22
- * #to_key was added to ActiveModel::Lint so we can generate DOM IDs for
23
- AMo objects with composite keys [MG]
24
-
25
-
26
- *Rails 3.0.0 [beta 1] (February 4, 2010)*
27
-
28
- * ActiveModel::Observer#add_observer!
29
-
30
- It has a custom hook to define after_find that should really be in a
31
- ActiveRecord::Observer subclass:
32
-
33
- def add_observer!(klass)
34
- klass.add_observer(self)
35
- klass.class_eval 'def after_find() end' unless
36
- klass.respond_to?(:after_find)
37
- end
38
-
39
- * Change the ActiveModel::Base.include_root_in_json default to true for Rails 3 [DHH]
1
+ *Edge*
40
2
 
41
3
  * Add validates_format_of :without => /regexp/ option. #430 [Elliot Winkler, Peer Allan]
42
4
 
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2004-2010 David Heinemeier Hansson
1
+ Copyright (c) 2004-2009 David Heinemeier Hansson
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README CHANGED
@@ -1,205 +1,21 @@
1
- = Active Model - defined interfaces for Rails
2
-
3
- Prior to Rails 3.0, if a plugin or gem developer wanted to be able to have
4
- an object interact with Action Pack helpers, it was required to either
5
- copy chunks of code from Rails, or monkey patch entire helpers to make them
6
- handle objects that did not look like Active Record. This generated code
7
- duplication and fragile applications that broke on upgrades.
8
-
9
- Active Model is a solution for this problem.
10
-
11
- Active Model provides a known set of interfaces that your objects can implement
12
- to then present a common interface to the Action Pack helpers. You can include
13
- functionality from the following modules:
1
+ Active Model
2
+ ==============
14
3
 
15
- * Adding attribute magic to your objects
4
+ Totally experimental library that aims to extract common model mixins from
5
+ ActiveRecord for use in ActiveResource (and other similar libraries).
6
+ This is in a very rough state (no autotest or spec rake tasks set up yet),
7
+ so please excuse the mess.
16
8
 
17
- Add prefixes and suffixes to defined attribute methods...
18
-
19
- class Person
20
- include ActiveModel::AttributeMethods
21
-
22
- attribute_method_prefix 'clear_'
23
- define_attribute_methods [:name, :age]
24
-
25
- attr_accessor :name, :age
26
-
27
- def clear_attribute(attr)
28
- send("#{attr}=", nil)
29
- end
30
- end
31
-
32
- ...gives you clear_name, clear_age.
33
-
34
- {Learn more}[link:classes/ActiveModel/AttributeMethods.html]
35
-
36
- * Adding callbacks to your objects
9
+ Here's what I plan to extract:
10
+ * ActiveModel::Observing
11
+ * ActiveModel::Callbacks
12
+ * ActiveModel::Validations
37
13
 
38
- class Person
39
- extend ActiveModel::Callbacks
40
- define_model_callbacks :create
41
-
42
- def create
43
- _run_create_callbacks do
44
- # Your create action methods here
45
- end
46
- end
47
- end
48
-
49
- ...gives you before_create, around_create and after_create class methods that
50
- wrap your create method.
51
-
52
- {Learn more}[link:classes/ActiveModel/CallBacks.html]
14
+ # for ActiveResource params and ActiveRecord options
15
+ * ActiveModel::Scoping
53
16
 
54
- * For classes that already look like an Active Record object
17
+ # to_json, to_xml, etc
18
+ * ActiveModel::Serialization
55
19
 
56
- class Person
57
- include ActiveModel::Conversion
58
- end
59
-
60
- ...returns the class itself when sent :to_model
61
-
62
- {Learn more}[link:classes/ActiveModel/Conversion.html]
63
-
64
- * Tracking changes in your object
65
-
66
- Provides all the value tracking features implemented by ActiveRecord...
67
-
68
- person = Person.new
69
- person.name # => nil
70
- person.changed? # => false
71
- person.name = 'bob'
72
- person.changed? # => true
73
- person.changed # => ['name']
74
- person.changes # => { 'name' => [nil, 'bob'] }
75
- person.name = 'robert'
76
- person.save
77
- person.previous_changes # => {'name' => ['bob, 'robert']}
78
-
79
- {Learn more}[link:classes/ActiveModel/Dirty.html]
80
-
81
- * Adding +errors+ support to your object
82
-
83
- Provides the error messages to allow your object to interact with Action Pack
84
- helpers seamlessly...
85
-
86
- class Person
87
-
88
- def initialize
89
- @errors = ActiveModel::Errors.new(self)
90
- end
91
-
92
- attr_accessor :name
93
- attr_reader :errors
94
-
95
- def validate!
96
- errors.add(:name, "can not be nil") if name == nil
97
- end
98
-
99
- def ErrorsPerson.human_attribute_name(attr, options = {})
100
- "Name"
101
- end
102
-
103
- end
104
-
105
- ... gives you...
106
-
107
- person.errors.full_messages
108
- # => ["Name Can not be nil"]
109
- person.errors.full_messages
110
- # => ["Name Can not be nil"]
111
-
112
- {Learn more}[link:classes/ActiveModel/Errors.html]
113
-
114
- * Testing the compliance of your object
115
-
116
- Use ActiveModel::Lint to test the compliance of your object to the
117
- basic ActiveModel API...
118
-
119
- {Learn more}[link:classes/ActiveModel/Lint/Tests.html]
120
-
121
- * Providing a human face to your object
122
-
123
- ActiveModel::Naming provides your model with the model_name convention
124
- and a human_name attribute...
125
-
126
- class NamedPerson
127
- extend ActiveModel::Naming
128
- end
129
-
130
- ...gives you...
131
-
132
- NamedPerson.model_name #=> "NamedPerson"
133
- NamedPerson.model_name.human #=> "Named person"
134
-
135
- {Learn more}[link:classes/ActiveModel/Naming.html]
136
-
137
- * Adding observer support to your objects
138
-
139
- ActiveModel::Observers allows your object to implement the Observer
140
- pattern in a Rails App and take advantage of all the standard observer
141
- functions.
142
-
143
- {Learn more}[link:classes/ActiveModel/Observer.html]
144
-
145
- * Making your object serializable
146
-
147
- ActiveModel::Serialization provides a standard interface for your object
148
- to provide to_json or to_xml serialization...
149
-
150
- s = SerialPerson.new
151
- s.serializable_hash # => {"name"=>nil}
152
- s.to_json # => "{\"name\":null}"
153
- s.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
154
-
155
- {Learn more}[link:classes/ActiveModel/Serialization.html]
156
-
157
- * Integrating with Rail's internationalization (i18n) handling through
158
- ActiveModel::Translations...
159
-
160
- class Person
161
- extend ActiveModel::Translation
162
- end
163
-
164
- {Learn more}[link:classes/ActiveModel/Translation.html]
165
-
166
- * Providing a full Validation stack for your objects...
167
-
168
- class Person
169
- include ActiveModel::Validations
170
-
171
- attr_accessor :first_name, :last_name
172
-
173
-
174
- validates_each :first_name, :last_name do |record, attr, value|
175
- record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z
176
- end
177
- end
178
-
179
-
180
- person = Person.new(:first_name => 'zoolander')
181
- person.valid? #=> false
182
-
183
- {Learn more}[link:classes/ActiveModel/Validations.html]
184
-
185
- * Make custom validators
186
-
187
- class Person
188
- include ActiveModel::Validations
189
- validates_with HasNameValidator
190
- attr_accessor :name
191
- end
192
-
193
- class HasNameValidator < ActiveModel::Validator
194
- def validate(record)
195
- record.errors[:name] = "must exist" if record.name.blank?
196
- end
197
- end
198
-
199
- p = ValidatorPerson.new
200
- p.valid? #=> false
201
- p.errors.full_messages #=> ["Name must exist"]
202
- p.name = "Bob"
203
- p.valid? #=> true
204
-
205
- {Learn more}[link:classes/ActiveModel/Validator.html]
20
+ I'm trying to keep ActiveRecord compatibility where possible, but I'm
21
+ annotating the spots where I'm diverging a bit.
data/lib/active_model.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2004-2010 David Heinemeier Hansson
2
+ # Copyright (c) 2004-2009 David Heinemeier Hansson
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining
5
5
  # a copy of this software and associated documentation files (the
@@ -21,41 +21,32 @@
21
21
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
  #++
23
23
 
24
- activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__)
25
- $:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path)
24
+ activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib"
25
+ $:.unshift(activesupport_path) if File.directory?(activesupport_path)
26
26
  require 'active_support'
27
27
 
28
-
29
28
  module ActiveModel
30
- extend ActiveSupport::Autoload
31
-
32
- autoload :AttributeMethods
33
- autoload :BlockValidator, 'active_model/validator'
34
- autoload :Callbacks
35
- autoload :Conversion
36
- autoload :DeprecatedErrorMethods
37
- autoload :Dirty
38
- autoload :EachValidator, 'active_model/validator'
39
- autoload :Errors
40
- autoload :Lint
29
+ autoload :AttributeMethods, 'active_model/attribute_methods'
30
+ autoload :Conversion, 'active_model/conversion'
31
+ autoload :DeprecatedErrorMethods, 'active_model/deprecated_error_methods'
32
+ autoload :Dirty, 'active_model/dirty'
33
+ autoload :Errors, 'active_model/errors'
34
+ autoload :Lint, 'active_model/lint'
41
35
  autoload :Name, 'active_model/naming'
42
- autoload :Naming
36
+ autoload :Naming, 'active_model/naming'
43
37
  autoload :Observer, 'active_model/observing'
44
- autoload :Observing
45
- autoload :Serialization
46
- autoload :TestCase
47
- autoload :Translation
48
- autoload :VERSION
49
- autoload :Validations
50
- autoload :Validator
38
+ autoload :Observing, 'active_model/observing'
39
+ autoload :Serialization, 'active_model/serialization'
40
+ autoload :StateMachine, 'active_model/state_machine'
41
+ autoload :TestCase, 'active_model/test_case'
42
+ autoload :Validations, 'active_model/validations'
43
+ autoload :ValidationsRepairHelper, 'active_model/validations_repair_helper'
44
+ autoload :VERSION, 'active_model/version'
51
45
 
52
46
  module Serializers
53
- extend ActiveSupport::Autoload
54
-
55
- autoload :JSON
56
- autoload :Xml
47
+ autoload :JSON, 'active_model/serializers/json'
48
+ autoload :Xml, 'active_model/serializers/xml'
57
49
  end
58
50
  end
59
51
 
60
- require 'active_support/i18n'
61
52
  I18n.load_path << File.dirname(__FILE__) + '/active_model/locale/en.yml'
@@ -5,54 +5,10 @@ module ActiveModel
5
5
  class MissingAttributeError < NoMethodError
6
6
  end
7
7
 
8
- # <tt>ActiveModel::AttributeMethods</tt> provides a way to add prefixes and suffixes
9
- # to your methods as well as handling the creation of Active Record like class methods
10
- # such as +table_name+.
11
- #
12
- # The requirements to implement ActiveModel::AttributeMethods are:
13
- #
14
- # * <tt>include ActiveModel::AttributeMethods</tt> in your object
15
- # * Call each Attribute Method module method you want to add, such as
16
- # attribute_method_suffix or attribute_method_prefix
17
- # * Call <tt>define_attribute_methods</tt> after the other methods are
18
- # called.
19
- # * Define the various generic +_attribute+ methods that you have declared
20
- #
21
- # A minimal implementation could be:
22
- #
23
- # class Person
24
- # include ActiveModel::AttributeMethods
25
- #
26
- # attribute_method_affix :prefix => 'reset_', :suffix => '_to_default!'
27
- # attribute_method_suffix '_contrived?'
28
- # attribute_method_prefix 'clear_'
29
- # define_attribute_methods ['name']
30
- #
31
- # attr_accessor :name
32
- #
33
- # private
34
- #
35
- # def attribute_contrived?(attr)
36
- # true
37
- # end
38
- #
39
- # def clear_attribute(attr)
40
- # send("#{attr}=", nil)
41
- # end
42
- #
43
- # def reset_attribute_to_default!(attr)
44
- # send("#{attr}=", "Default Name")
45
- # end
46
- # end
47
- #
48
- # Please notice that whenever you include ActiveModel::AtributeMethods in your class,
49
- # it requires you to implement a <tt>attributes</tt> methods which returns a hash with
50
- # each attribute name in your model as hash key and the attribute value as hash value.
51
- # Hash keys must be a string.
52
- #
53
8
  module AttributeMethods
54
9
  extend ActiveSupport::Concern
55
10
 
11
+ # Declare and check for suffixed attribute methods.
56
12
  module ClassMethods
57
13
  # Defines an "attribute" method (like +inheritance_column+ or
58
14
  # +table_name+). A new (class) method will be created with the
@@ -66,43 +22,21 @@ module ActiveModel
66
22
  #
67
23
  # Example:
68
24
  #
69
- # class Person
70
- #
71
- # include ActiveModel::AttributeMethods
72
- #
73
- # cattr_accessor :primary_key
74
- # cattr_accessor :inheritance_column
75
- #
25
+ # class A < ActiveRecord::Base
76
26
  # define_attr_method :primary_key, "sysid"
77
27
  # define_attr_method( :inheritance_column ) do
78
28
  # original_inheritance_column + "_id"
79
29
  # end
80
- #
81
30
  # end
82
- #
83
- # Provides you with:
84
- #
85
- # AttributePerson.primary_key
86
- # # => "sysid"
87
- # AttributePerson.inheritance_column = 'address'
88
- # AttributePerson.inheritance_column
89
- # # => 'address_id'
90
31
  def define_attr_method(name, value=nil, &block)
91
- sing = singleton_class
92
- sing.class_eval <<-eorb, __FILE__, __LINE__ + 1
93
- if method_defined?(:original_#{name})
94
- undef :original_#{name}
95
- end
96
- alias_method :original_#{name}, :#{name}
97
- eorb
32
+ sing = metaclass
33
+ sing.send :alias_method, "original_#{name}", name
98
34
  if block_given?
99
35
  sing.send :define_method, name, &block
100
36
  else
101
37
  # use eval instead of a block to work around a memory leak in dev
102
38
  # mode in fcgi
103
- sing.class_eval <<-eorb, __FILE__, __LINE__ + 1
104
- def #{name}; #{value.to_s.inspect}; end
105
- eorb
39
+ sing.class_eval "def #{name}; #{value.to_s.inspect}; end"
106
40
  end
107
41
  end
108
42
 
@@ -120,25 +54,19 @@ module ActiveModel
120
54
  #
121
55
  # For example:
122
56
  #
123
- # class Person
124
- #
125
- # include ActiveModel::AttributeMethods
126
- # attr_accessor :name
57
+ # class Person < ActiveRecord::Base
127
58
  # attribute_method_prefix 'clear_'
128
- # define_attribute_methods [:name]
129
59
  #
130
60
  # private
131
- #
132
- # def clear_attribute(attr)
133
- # send("#{attr}=", nil)
134
- # end
61
+ # def clear_attribute(attr)
62
+ # ...
63
+ # end
135
64
  # end
136
65
  #
137
- # person = Person.new
138
- # person.name = "Bob"
139
- # person.name # => "Bob"
66
+ # person = Person.find(1)
67
+ # person.name # => 'Gem'
140
68
  # person.clear_name
141
- # person.name # => nil
69
+ # person.name # => ''
142
70
  def attribute_method_prefix(*prefixes)
143
71
  attribute_method_matchers.concat(prefixes.map { |prefix| AttributeMethodMatcher.new :prefix => prefix })
144
72
  undefine_attribute_methods
@@ -158,24 +86,18 @@ module ActiveModel
158
86
  #
159
87
  # For example:
160
88
  #
161
- # class Person
162
- #
163
- # include ActiveModel::AttributeMethods
164
- # attr_accessor :name
89
+ # class Person < ActiveRecord::Base
165
90
  # attribute_method_suffix '_short?'
166
- # define_attribute_methods [:name]
167
91
  #
168
92
  # private
169
- #
170
- # def attribute_short?(attr)
171
- # send(attr).length < 5
172
- # end
93
+ # def attribute_short?(attr)
94
+ # ...
95
+ # end
173
96
  # end
174
97
  #
175
- # person = Person.new
176
- # person.name = "Bob"
177
- # person.name # => "Bob"
178
- # person.name_short? # => true
98
+ # person = Person.find(1)
99
+ # person.name # => 'Gem'
100
+ # person.name_short? # => true
179
101
  def attribute_method_suffix(*suffixes)
180
102
  attribute_method_matchers.concat(suffixes.map { |suffix| AttributeMethodMatcher.new :suffix => suffix })
181
103
  undefine_attribute_methods
@@ -196,21 +118,16 @@ module ActiveModel
196
118
  #
197
119
  # For example:
198
120
  #
199
- # class Person
200
- #
201
- # include ActiveModel::AttributeMethods
202
- # attr_accessor :name
121
+ # class Person < ActiveRecord::Base
203
122
  # attribute_method_affix :prefix => 'reset_', :suffix => '_to_default!'
204
- # define_attribute_methods [:name]
205
123
  #
206
124
  # private
207
- #
208
- # def reset_attribute_to_default!(attr)
209
- # ...
210
- # end
125
+ # def reset_attribute_to_default!(attr)
126
+ # ...
127
+ # end
211
128
  # end
212
129
  #
213
- # person = Person.new
130
+ # person = Person.find(1)
214
131
  # person.name # => 'Gem'
215
132
  # person.reset_name_to_default!
216
133
  # person.name # => 'Gemma'
@@ -221,7 +138,7 @@ module ActiveModel
221
138
 
222
139
  def alias_attribute(new_name, old_name)
223
140
  attribute_method_matchers.each do |matcher|
224
- module_eval <<-STR, __FILE__, __LINE__ + 1
141
+ module_eval <<-STR, __FILE__, __LINE__+1
225
142
  def #{matcher.method_name(new_name)}(*args)
226
143
  send(:#{matcher.method_name(old_name)}, *args)
227
144
  end
@@ -229,30 +146,6 @@ module ActiveModel
229
146
  end
230
147
  end
231
148
 
232
- # Declares a the attributes that should be prefixed and suffixed by
233
- # ActiveModel::AttributeMethods.
234
- #
235
- # To use, pass in an array of attribute names (as strings or symbols),
236
- # be sure to declare +define_attribute_methods+ after you define any
237
- # prefix, suffix or affix methods, or they will not hook in.
238
- #
239
- # class Person
240
- #
241
- # include ActiveModel::AttributeMethods
242
- # attr_accessor :name, :age, :address
243
- # attribute_method_prefix 'clear_'
244
- #
245
- # # Call to define_attribute_methods must appear after the
246
- # # attribute_method_prefix, attribute_method_suffix or
247
- # # attribute_method_affix declares.
248
- # define_attribute_methods [:name, :age, :address]
249
- #
250
- # private
251
- #
252
- # def clear_attribute(attr)
253
- # ...
254
- # end
255
- # end
256
149
  def define_attribute_methods(attr_names)
257
150
  return if attribute_methods_generated?
258
151
  attr_names.each do |attr_name|
@@ -263,13 +156,8 @@ module ActiveModel
263
156
  if respond_to?(generate_method)
264
157
  send(generate_method, attr_name)
265
158
  else
266
- method_name = matcher.method_name(attr_name)
267
-
268
- generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
269
- if method_defined?(:#{method_name})
270
- undef :#{method_name}
271
- end
272
- def #{method_name}(*args)
159
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__+1
160
+ def #{matcher.method_name(attr_name)}(*args)
273
161
  send(:#{matcher.method_missing_target}, '#{attr_name}', *args)
274
162
  end
275
163
  STR
@@ -280,7 +168,6 @@ module ActiveModel
280
168
  @attribute_methods_generated = true
281
169
  end
282
170
 
283
- # Removes all the preiously dynamically defined methods from the class
284
171
  def undefine_attribute_methods
285
172
  generated_attribute_methods.module_eval do
286
173
  instance_methods.each { |m| undef_method(m) }
@@ -288,7 +175,6 @@ module ActiveModel
288
175
  @attribute_methods_generated = nil
289
176
  end
290
177
 
291
- # Returns true if the attribute methods defined have been generated.
292
178
  def generated_attribute_methods #:nodoc:
293
179
  @generated_attribute_methods ||= begin
294
180
  mod = Module.new
@@ -297,7 +183,6 @@ module ActiveModel
297
183
  end
298
184
  end
299
185
 
300
- # Returns true if the attribute methods defined have been generated.
301
186
  def attribute_methods_generated?
302
187
  @attribute_methods_generated ||= nil
303
188
  end