cassandra_object 0.6.0.pre
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.
- data/lib/cassandra_object/associations/one_to_many.rb +136 -0
- data/lib/cassandra_object/associations/one_to_one.rb +77 -0
- data/lib/cassandra_object/associations.rb +35 -0
- data/lib/cassandra_object/attributes.rb +93 -0
- data/lib/cassandra_object/base.rb +104 -0
- data/lib/cassandra_object/callbacks.rb +10 -0
- data/lib/cassandra_object/collection.rb +8 -0
- data/lib/cassandra_object/cursor.rb +86 -0
- data/lib/cassandra_object/dirty.rb +27 -0
- data/lib/cassandra_object/identity/abstract_key_factory.rb +36 -0
- data/lib/cassandra_object/identity/key.rb +20 -0
- data/lib/cassandra_object/identity/natural_key_factory.rb +51 -0
- data/lib/cassandra_object/identity/uuid_key_factory.rb +37 -0
- data/lib/cassandra_object/identity.rb +61 -0
- data/lib/cassandra_object/indexes.rb +129 -0
- data/lib/cassandra_object/legacy_callbacks.rb +33 -0
- data/lib/cassandra_object/migrations.rb +72 -0
- data/lib/cassandra_object/mocking.rb +15 -0
- data/lib/cassandra_object/persistence.rb +193 -0
- data/lib/cassandra_object/serialization.rb +6 -0
- data/lib/cassandra_object/type_registration.rb +7 -0
- data/lib/cassandra_object/types.rb +128 -0
- data/lib/cassandra_object/validation.rb +58 -0
- data/lib/cassandra_object.rb +30 -0
- data/vendor/active_support_shims.rb +4 -0
- data/vendor/activemodel/CHANGELOG +13 -0
- data/vendor/activemodel/CHANGES +12 -0
- data/vendor/activemodel/MIT-LICENSE +21 -0
- data/vendor/activemodel/README +21 -0
- data/vendor/activemodel/Rakefile +52 -0
- data/vendor/activemodel/activemodel.gemspec +19 -0
- data/vendor/activemodel/examples/validations.rb +29 -0
- data/vendor/activemodel/lib/active_model/attribute_methods.rb +291 -0
- data/vendor/activemodel/lib/active_model/callbacks.rb +91 -0
- data/vendor/activemodel/lib/active_model/conversion.rb +8 -0
- data/vendor/activemodel/lib/active_model/deprecated_error_methods.rb +33 -0
- data/vendor/activemodel/lib/active_model/dirty.rb +126 -0
- data/vendor/activemodel/lib/active_model/errors.rb +162 -0
- data/vendor/activemodel/lib/active_model/lint.rb +91 -0
- data/vendor/activemodel/lib/active_model/locale/en.yml +27 -0
- data/vendor/activemodel/lib/active_model/naming.rb +45 -0
- data/vendor/activemodel/lib/active_model/observing.rb +191 -0
- data/vendor/activemodel/lib/active_model/railtie.rb +2 -0
- data/vendor/activemodel/lib/active_model/serialization.rb +30 -0
- data/vendor/activemodel/lib/active_model/serializers/json.rb +96 -0
- data/vendor/activemodel/lib/active_model/serializers/xml.rb +204 -0
- data/vendor/activemodel/lib/active_model/state_machine/event.rb +62 -0
- data/vendor/activemodel/lib/active_model/state_machine/machine.rb +75 -0
- data/vendor/activemodel/lib/active_model/state_machine/state.rb +47 -0
- data/vendor/activemodel/lib/active_model/state_machine/state_transition.rb +40 -0
- data/vendor/activemodel/lib/active_model/state_machine.rb +70 -0
- data/vendor/activemodel/lib/active_model/test_case.rb +18 -0
- data/vendor/activemodel/lib/active_model/translation.rb +44 -0
- data/vendor/activemodel/lib/active_model/validations/acceptance.rb +55 -0
- data/vendor/activemodel/lib/active_model/validations/confirmation.rb +47 -0
- data/vendor/activemodel/lib/active_model/validations/exclusion.rb +42 -0
- data/vendor/activemodel/lib/active_model/validations/format.rb +64 -0
- data/vendor/activemodel/lib/active_model/validations/inclusion.rb +42 -0
- data/vendor/activemodel/lib/active_model/validations/length.rb +117 -0
- data/vendor/activemodel/lib/active_model/validations/numericality.rb +111 -0
- data/vendor/activemodel/lib/active_model/validations/presence.rb +42 -0
- data/vendor/activemodel/lib/active_model/validations/with.rb +59 -0
- data/vendor/activemodel/lib/active_model/validations.rb +120 -0
- data/vendor/activemodel/lib/active_model/validator.rb +110 -0
- data/vendor/activemodel/lib/active_model/version.rb +9 -0
- data/vendor/activemodel/lib/active_model.rb +61 -0
- data/vendor/activemodel/test/cases/attribute_methods_test.rb +46 -0
- data/vendor/activemodel/test/cases/callbacks_test.rb +70 -0
- data/vendor/activemodel/test/cases/helper.rb +23 -0
- data/vendor/activemodel/test/cases/lint_test.rb +28 -0
- data/vendor/activemodel/test/cases/naming_test.rb +28 -0
- data/vendor/activemodel/test/cases/observing_test.rb +133 -0
- data/vendor/activemodel/test/cases/serializeration/json_serialization_test.rb +83 -0
- data/vendor/activemodel/test/cases/serializeration/xml_serialization_test.rb +110 -0
- data/vendor/activemodel/test/cases/state_machine/event_test.rb +49 -0
- data/vendor/activemodel/test/cases/state_machine/machine_test.rb +43 -0
- data/vendor/activemodel/test/cases/state_machine/state_test.rb +72 -0
- data/vendor/activemodel/test/cases/state_machine/state_transition_test.rb +84 -0
- data/vendor/activemodel/test/cases/state_machine_test.rb +312 -0
- data/vendor/activemodel/test/cases/tests_database.rb +37 -0
- data/vendor/activemodel/test/cases/translation_test.rb +45 -0
- data/vendor/activemodel/test/cases/validations/acceptance_validation_test.rb +71 -0
- data/vendor/activemodel/test/cases/validations/conditional_validation_test.rb +141 -0
- data/vendor/activemodel/test/cases/validations/confirmation_validation_test.rb +58 -0
- data/vendor/activemodel/test/cases/validations/exclusion_validation_test.rb +47 -0
- data/vendor/activemodel/test/cases/validations/format_validation_test.rb +118 -0
- data/vendor/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb +175 -0
- data/vendor/activemodel/test/cases/validations/i18n_validation_test.rb +527 -0
- data/vendor/activemodel/test/cases/validations/inclusion_validation_test.rb +71 -0
- data/vendor/activemodel/test/cases/validations/length_validation_test.rb +437 -0
- data/vendor/activemodel/test/cases/validations/numericality_validation_test.rb +180 -0
- data/vendor/activemodel/test/cases/validations/presence_validation_test.rb +70 -0
- data/vendor/activemodel/test/cases/validations/with_validation_test.rb +166 -0
- data/vendor/activemodel/test/cases/validations_test.rb +215 -0
- data/vendor/activemodel/test/config.rb +3 -0
- data/vendor/activemodel/test/fixtures/topics.yml +41 -0
- data/vendor/activemodel/test/models/contact.rb +7 -0
- data/vendor/activemodel/test/models/custom_reader.rb +17 -0
- data/vendor/activemodel/test/models/developer.rb +6 -0
- data/vendor/activemodel/test/models/person.rb +9 -0
- data/vendor/activemodel/test/models/reply.rb +34 -0
- data/vendor/activemodel/test/models/topic.rb +9 -0
- data/vendor/activemodel/test/models/track_back.rb +4 -0
- data/vendor/activemodel/test/schema.rb +14 -0
- data/vendor/activesupport/lib/active_support/autoload.rb +48 -0
- data/vendor/activesupport/lib/active_support/concern.rb +25 -0
- data/vendor/activesupport/lib/active_support/core_ext/array/wrap.rb +20 -0
- data/vendor/activesupport/lib/active_support/core_ext/object/blank.rb +58 -0
- data/vendor/activesupport/lib/active_support/core_ext/object/tap.rb +6 -0
- data/vendor/activesupport/lib/active_support/dependency_module.rb +17 -0
- data/vendor/activesupport/lib/active_support/i18n.rb +2 -0
- data/vendor/activesupport/lib/active_support/locale/en.yml +33 -0
- metadata +230 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module ActiveModel
|
|
2
|
+
module DeprecatedErrorMethods
|
|
3
|
+
def on(attribute)
|
|
4
|
+
message = "Errors#on have been deprecated, use Errors#[] instead.\n"
|
|
5
|
+
message << "Also note that the behaviour of Errors#[] has changed. Errors#[] now always returns an Array. An empty Array is "
|
|
6
|
+
message << "returned when there are no errors on the specified attribute."
|
|
7
|
+
ActiveSupport::Deprecation.warn(message)
|
|
8
|
+
|
|
9
|
+
errors = self[attribute]
|
|
10
|
+
errors.size < 2 ? errors.first : errors
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def on_base
|
|
14
|
+
ActiveSupport::Deprecation.warn "Errors#on_base have been deprecated, use Errors#[:base] instead"
|
|
15
|
+
ActiveSupport::Deprecation.silence { on(:base) }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def add_to_base(msg)
|
|
19
|
+
ActiveSupport::Deprecation.warn "Errors#add_to_base(msg) has been deprecated, use Errors#[:base] << msg instead"
|
|
20
|
+
self[:base] << msg
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def invalid?(attribute)
|
|
24
|
+
ActiveSupport::Deprecation.warn "Errors#invalid?(attribute) has been deprecated, use Errors#[attribute].any? instead"
|
|
25
|
+
self[attribute].any?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def each_full
|
|
29
|
+
ActiveSupport::Deprecation.warn "Errors#each_full has been deprecated, use Errors#to_a.each instead"
|
|
30
|
+
to_a.each { |error| yield error }
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
module ActiveModel
|
|
2
|
+
# Track unsaved attribute changes.
|
|
3
|
+
#
|
|
4
|
+
# A newly instantiated object is unchanged:
|
|
5
|
+
# person = Person.find_by_name('Uncle Bob')
|
|
6
|
+
# person.changed? # => false
|
|
7
|
+
#
|
|
8
|
+
# Change the name:
|
|
9
|
+
# person.name = 'Bob'
|
|
10
|
+
# person.changed? # => true
|
|
11
|
+
# person.name_changed? # => true
|
|
12
|
+
# person.name_was # => 'Uncle Bob'
|
|
13
|
+
# person.name_change # => ['Uncle Bob', 'Bob']
|
|
14
|
+
# person.name = 'Bill'
|
|
15
|
+
# person.name_change # => ['Uncle Bob', 'Bill']
|
|
16
|
+
#
|
|
17
|
+
# Save the changes:
|
|
18
|
+
# person.save
|
|
19
|
+
# person.changed? # => false
|
|
20
|
+
# person.name_changed? # => false
|
|
21
|
+
#
|
|
22
|
+
# Assigning the same value leaves the attribute unchanged:
|
|
23
|
+
# person.name = 'Bill'
|
|
24
|
+
# person.name_changed? # => false
|
|
25
|
+
# person.name_change # => nil
|
|
26
|
+
#
|
|
27
|
+
# Which attributes have changed?
|
|
28
|
+
# person.name = 'Bob'
|
|
29
|
+
# person.changed # => ['name']
|
|
30
|
+
# person.changes # => { 'name' => ['Bill', 'Bob'] }
|
|
31
|
+
#
|
|
32
|
+
# Resetting an attribute returns it to its original state:
|
|
33
|
+
# person.reset_name! # => 'Bill'
|
|
34
|
+
# person.changed? # => false
|
|
35
|
+
# person.name_changed? # => false
|
|
36
|
+
# person.name # => 'Bill'
|
|
37
|
+
#
|
|
38
|
+
# Before modifying an attribute in-place:
|
|
39
|
+
# person.name_will_change!
|
|
40
|
+
# person.name << 'y'
|
|
41
|
+
# person.name_change # => ['Bill', 'Billy']
|
|
42
|
+
module Dirty
|
|
43
|
+
extend ActiveSupport::Concern
|
|
44
|
+
include ActiveModel::AttributeMethods
|
|
45
|
+
|
|
46
|
+
included do
|
|
47
|
+
attribute_method_suffix '_changed?', '_change', '_will_change!', '_was'
|
|
48
|
+
attribute_method_affix :prefix => 'reset_', :suffix => '!'
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Do any attributes have unsaved changes?
|
|
52
|
+
# person.changed? # => false
|
|
53
|
+
# person.name = 'bob'
|
|
54
|
+
# person.changed? # => true
|
|
55
|
+
def changed?
|
|
56
|
+
!changed_attributes.empty?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# List of attributes with unsaved changes.
|
|
60
|
+
# person.changed # => []
|
|
61
|
+
# person.name = 'bob'
|
|
62
|
+
# person.changed # => ['name']
|
|
63
|
+
def changed
|
|
64
|
+
changed_attributes.keys
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Map of changed attrs => [original value, new value].
|
|
68
|
+
# person.changes # => {}
|
|
69
|
+
# person.name = 'bob'
|
|
70
|
+
# person.changes # => { 'name' => ['bill', 'bob'] }
|
|
71
|
+
def changes
|
|
72
|
+
changed.inject({}) { |h, attr| h[attr] = attribute_change(attr); h }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Map of attributes that were changed when the model was saved.
|
|
76
|
+
# person.name # => 'bob'
|
|
77
|
+
# person.name = 'robert'
|
|
78
|
+
# person.save
|
|
79
|
+
# person.previous_changes # => {'name' => ['bob, 'robert']}
|
|
80
|
+
def previous_changes
|
|
81
|
+
previously_changed_attributes
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
# Map of change <tt>attr => original value</tt>.
|
|
86
|
+
def changed_attributes
|
|
87
|
+
@changed_attributes ||= {}
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Map of fields that were changed when the model was saved
|
|
91
|
+
def previously_changed_attributes
|
|
92
|
+
@previously_changed || {}
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Handle <tt>*_changed?</tt> for +method_missing+.
|
|
96
|
+
def attribute_changed?(attr)
|
|
97
|
+
changed_attributes.include?(attr)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Handle <tt>*_change</tt> for +method_missing+.
|
|
101
|
+
def attribute_change(attr)
|
|
102
|
+
[changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Handle <tt>*_was</tt> for +method_missing+.
|
|
106
|
+
def attribute_was(attr)
|
|
107
|
+
attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Handle <tt>*_will_change!</tt> for +method_missing+.
|
|
111
|
+
def attribute_will_change!(attr)
|
|
112
|
+
begin
|
|
113
|
+
value = __send__(attr)
|
|
114
|
+
value = value.duplicable? ? value.clone : value
|
|
115
|
+
rescue TypeError, NoMethodError
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
changed_attributes[attr] = value
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Handle <tt>reset_*!</tt> for +method_missing+.
|
|
122
|
+
def reset_attribute!(attr)
|
|
123
|
+
__send__("#{attr}=", changed_attributes[attr]) if attribute_changed?(attr)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
require 'active_support/core_ext/string/inflections'
|
|
2
|
+
require 'active_support/ordered_hash'
|
|
3
|
+
|
|
4
|
+
module ActiveModel
|
|
5
|
+
class Errors < ActiveSupport::OrderedHash
|
|
6
|
+
include DeprecatedErrorMethods
|
|
7
|
+
|
|
8
|
+
def initialize(base)
|
|
9
|
+
@base = base
|
|
10
|
+
super()
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
alias_method :get, :[]
|
|
14
|
+
alias_method :set, :[]=
|
|
15
|
+
|
|
16
|
+
def [](attribute)
|
|
17
|
+
if errors = get(attribute.to_sym)
|
|
18
|
+
errors
|
|
19
|
+
else
|
|
20
|
+
set(attribute.to_sym, [])
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def []=(attribute, error)
|
|
25
|
+
self[attribute.to_sym] << error
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def each
|
|
29
|
+
each_key do |attribute|
|
|
30
|
+
self[attribute].each { |error| yield attribute, error }
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def size
|
|
35
|
+
values.flatten.size
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def to_a
|
|
39
|
+
full_messages
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def count
|
|
43
|
+
to_a.size
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def to_xml(options={})
|
|
47
|
+
require 'builder' unless defined? ::Builder
|
|
48
|
+
options[:root] ||= "errors"
|
|
49
|
+
options[:indent] ||= 2
|
|
50
|
+
options[:builder] ||= ::Builder::XmlMarkup.new(:indent => options[:indent])
|
|
51
|
+
|
|
52
|
+
options[:builder].instruct! unless options.delete(:skip_instruct)
|
|
53
|
+
options[:builder].errors do |e|
|
|
54
|
+
to_a.each { |error| e.error(error) }
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Adds an error message (+messsage+) to the +attribute+, which will be returned on a call to <tt>on(attribute)</tt>
|
|
59
|
+
# for the same attribute and ensure that this error object returns false when asked if <tt>empty?</tt>. More than one
|
|
60
|
+
# error can be added to the same +attribute+ in which case an array will be returned on a call to <tt>on(attribute)</tt>.
|
|
61
|
+
# If no +messsage+ is supplied, :invalid is assumed.
|
|
62
|
+
# If +message+ is a Symbol, it will be translated, using the appropriate scope (see translate_error).
|
|
63
|
+
def add(attribute, message = nil, options = {})
|
|
64
|
+
message ||= :invalid
|
|
65
|
+
message = generate_message(attribute, message, options) if message.is_a?(Symbol)
|
|
66
|
+
self[attribute] << message
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Will add an error message to each of the attributes in +attributes+ that is empty.
|
|
70
|
+
def add_on_empty(attributes, custom_message = nil)
|
|
71
|
+
[attributes].flatten.each do |attribute|
|
|
72
|
+
value = @base.send(:read_attribute_for_validation, attribute)
|
|
73
|
+
is_empty = value.respond_to?(:empty?) ? value.empty? : false
|
|
74
|
+
add(attribute, :empty, :default => custom_message) unless !value.nil? && !is_empty
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Will add an error message to each of the attributes in +attributes+ that is blank (using Object#blank?).
|
|
79
|
+
def add_on_blank(attributes, custom_message = nil)
|
|
80
|
+
[attributes].flatten.each do |attribute|
|
|
81
|
+
value = @base.send(:read_attribute_for_validation, attribute)
|
|
82
|
+
add(attribute, :blank, :default => custom_message) if value.blank?
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Returns all the full error messages in an array.
|
|
87
|
+
#
|
|
88
|
+
# class Company
|
|
89
|
+
# validates_presence_of :name, :address, :email
|
|
90
|
+
# validates_length_of :name, :in => 5..30
|
|
91
|
+
# end
|
|
92
|
+
#
|
|
93
|
+
# company = Company.create(:address => '123 First St.')
|
|
94
|
+
# company.errors.full_messages # =>
|
|
95
|
+
# ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Address can't be blank"]
|
|
96
|
+
def full_messages
|
|
97
|
+
full_messages = []
|
|
98
|
+
|
|
99
|
+
each do |attribute, messages|
|
|
100
|
+
messages = Array(messages)
|
|
101
|
+
next if messages.empty?
|
|
102
|
+
|
|
103
|
+
if attribute == :base
|
|
104
|
+
messages.each {|m| full_messages << m }
|
|
105
|
+
else
|
|
106
|
+
attr_name = attribute.to_s.gsub('.', '_').humanize
|
|
107
|
+
attr_name = @base.class.human_attribute_name(attribute, :default => attr_name)
|
|
108
|
+
options = { :default => "{{attribute}} {{message}}", :attribute => attr_name,
|
|
109
|
+
:scope => @base.class.i18n_scope }
|
|
110
|
+
|
|
111
|
+
messages.each do |m|
|
|
112
|
+
full_messages << I18n.t(:"errors.format", options.merge(:message => m))
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
full_messages
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Translates an error message in its default scope (<tt>activemodel.errors.messages</tt>).
|
|
121
|
+
# Error messages are first looked up in <tt>models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>, if it's not there,
|
|
122
|
+
# it's looked up in <tt>models.MODEL.MESSAGE</tt> and if that is not there it returns the translation of the
|
|
123
|
+
# default message (e.g. <tt>activemodel.errors.messages.MESSAGE</tt>). The translated model name,
|
|
124
|
+
# translated attribute name and the value are available for interpolation.
|
|
125
|
+
#
|
|
126
|
+
# When using inheritence in your models, it will check all the inherited models too, but only if the model itself
|
|
127
|
+
# hasn't been found. Say you have <tt>class Admin < User; end</tt> and you wanted the translation for the <tt>:blank</tt>
|
|
128
|
+
# error +message+ for the <tt>title</tt> +attribute+, it looks for these translations:
|
|
129
|
+
#
|
|
130
|
+
# <ol>
|
|
131
|
+
# <li><tt>activemodel.errors.models.admin.attributes.title.blank</tt></li>
|
|
132
|
+
# <li><tt>activemodel.errors.models.admin.blank</tt></li>
|
|
133
|
+
# <li><tt>activemodel.errors.models.user.attributes.title.blank</tt></li>
|
|
134
|
+
# <li><tt>activemodel.errors.models.user.blank</tt></li>
|
|
135
|
+
# <li><tt>activemodel.errors.messages.blank</tt></li>
|
|
136
|
+
# <li>any default you provided through the +options+ hash (in the activemodel.errors scope)</li>
|
|
137
|
+
# </ol>
|
|
138
|
+
def generate_message(attribute, message = :invalid, options = {})
|
|
139
|
+
message, options[:default] = options[:default], message if options[:default].is_a?(Symbol)
|
|
140
|
+
|
|
141
|
+
defaults = @base.class.lookup_ancestors.map do |klass|
|
|
142
|
+
[ :"models.#{klass.name.underscore}.attributes.#{attribute}.#{message}",
|
|
143
|
+
:"models.#{klass.name.underscore}.#{message}" ]
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
defaults << options.delete(:default)
|
|
147
|
+
defaults = defaults.compact.flatten << :"messages.#{message}"
|
|
148
|
+
|
|
149
|
+
key = defaults.shift
|
|
150
|
+
value = @base.send(:read_attribute_for_validation, attribute)
|
|
151
|
+
|
|
152
|
+
options = { :default => defaults,
|
|
153
|
+
:model => @base.class.model_name.human,
|
|
154
|
+
:attribute => @base.class.human_attribute_name(attribute),
|
|
155
|
+
:value => value,
|
|
156
|
+
:scope => [@base.class.i18n_scope, :errors]
|
|
157
|
+
}.merge(options)
|
|
158
|
+
|
|
159
|
+
I18n.translate(key, options)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# You can test whether an object is compliant with the ActiveModel API by
|
|
2
|
+
# including ActiveModel::Lint::Tests in your TestCase. It will included
|
|
3
|
+
# tests that tell you whether your object is fully compliant, or if not,
|
|
4
|
+
# which aspects of the API are not implemented.
|
|
5
|
+
#
|
|
6
|
+
# These tests do not attempt to determine the semantic correctness of the
|
|
7
|
+
# returned values. For instance, you could implement valid? to always
|
|
8
|
+
# return true, and the tests would pass. It is up to you to ensure that
|
|
9
|
+
# the values are semantically meaningful.
|
|
10
|
+
#
|
|
11
|
+
# Objects you pass in are expected to return a compliant object from a
|
|
12
|
+
# call to to_model. It is perfectly fine for to_model to return self.
|
|
13
|
+
module ActiveModel
|
|
14
|
+
module Lint
|
|
15
|
+
module Tests
|
|
16
|
+
# valid?
|
|
17
|
+
# ------
|
|
18
|
+
#
|
|
19
|
+
# Returns a boolean that specifies whether the object is in a valid or invalid
|
|
20
|
+
# state.
|
|
21
|
+
def test_valid?
|
|
22
|
+
assert model.respond_to?(:valid?), "The model should respond to valid?"
|
|
23
|
+
assert_boolean model.valid?, "valid?"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# new_record?
|
|
27
|
+
# -----------
|
|
28
|
+
#
|
|
29
|
+
# Returns a boolean that specifies whether the object has been persisted yet.
|
|
30
|
+
# This is used when calculating the URL for an object. If the object is
|
|
31
|
+
# not persisted, a form for that object, for instance, will be POSTed to the
|
|
32
|
+
# collection. If it is persisted, a form for the object will put PUTed to the
|
|
33
|
+
# URL for the object.
|
|
34
|
+
def test_new_record?
|
|
35
|
+
assert model.respond_to?(:new_record?), "The model should respond to new_record?"
|
|
36
|
+
assert_boolean model.new_record?, "new_record?"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def test_destroyed?
|
|
40
|
+
assert model.respond_to?(:destroyed?), "The model should respond to destroyed?"
|
|
41
|
+
assert_boolean model.destroyed?, "destroyed?"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# naming
|
|
45
|
+
# ------
|
|
46
|
+
#
|
|
47
|
+
# Model.model_name must returns a string with some convenience methods as
|
|
48
|
+
# :human and :partial_path. Check ActiveModel::Naming for more information.
|
|
49
|
+
#
|
|
50
|
+
def test_model_naming
|
|
51
|
+
assert model.class.respond_to?(:model_name), "The model should respond to model_name"
|
|
52
|
+
model_name = model.class.model_name
|
|
53
|
+
assert_kind_of String, model_name
|
|
54
|
+
assert_kind_of String, model_name.human
|
|
55
|
+
assert_kind_of String, model_name.partial_path
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# errors
|
|
59
|
+
# ------
|
|
60
|
+
#
|
|
61
|
+
# Returns an object that has :[] and :full_messages defined on it. See below
|
|
62
|
+
# for more details.
|
|
63
|
+
|
|
64
|
+
# Returns an Array of Strings that are the errors for the attribute in
|
|
65
|
+
# question. If localization is used, the Strings should be localized
|
|
66
|
+
# for the current locale. If no error is present, this method should
|
|
67
|
+
# return an empty Array.
|
|
68
|
+
def test_errors_aref
|
|
69
|
+
assert model.respond_to?(:errors), "The model should respond to errors"
|
|
70
|
+
assert model.errors[:hello].is_a?(Array), "errors#[] should return an Array"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Returns an Array of all error messages for the object. Each message
|
|
74
|
+
# should contain information about the field, if applicable.
|
|
75
|
+
def test_errors_full_messages
|
|
76
|
+
assert model.respond_to?(:errors), "The model should respond to errors"
|
|
77
|
+
assert model.errors.full_messages.is_a?(Array), "errors#full_messages should return an Array"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
def model
|
|
82
|
+
assert @model.respond_to?(:to_model), "The object should respond_to to_model"
|
|
83
|
+
@model.to_model
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def assert_boolean(result, name)
|
|
87
|
+
assert result == true || result == false, "#{name} should be a boolean"
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
en:
|
|
2
|
+
activemodel:
|
|
3
|
+
errors:
|
|
4
|
+
# model.errors.full_messages format.
|
|
5
|
+
format: "{{attribute}} {{message}}"
|
|
6
|
+
|
|
7
|
+
# The values :model, :attribute and :value are always available for interpolation
|
|
8
|
+
# The value :count is available when applicable. Can be used for pluralization.
|
|
9
|
+
messages:
|
|
10
|
+
inclusion: "is not included in the list"
|
|
11
|
+
exclusion: "is reserved"
|
|
12
|
+
invalid: "is invalid"
|
|
13
|
+
confirmation: "doesn't match confirmation"
|
|
14
|
+
accepted: "must be accepted"
|
|
15
|
+
empty: "can't be empty"
|
|
16
|
+
blank: "can't be blank"
|
|
17
|
+
too_long: "is too long (maximum is {{count}} characters)"
|
|
18
|
+
too_short: "is too short (minimum is {{count}} characters)"
|
|
19
|
+
wrong_length: "is the wrong length (should be {{count}} characters)"
|
|
20
|
+
not_a_number: "is not a number"
|
|
21
|
+
greater_than: "must be greater than {{count}}"
|
|
22
|
+
greater_than_or_equal_to: "must be greater than or equal to {{count}}"
|
|
23
|
+
equal_to: "must be equal to {{count}}"
|
|
24
|
+
less_than: "must be less than {{count}}"
|
|
25
|
+
less_than_or_equal_to: "must be less than or equal to {{count}}"
|
|
26
|
+
odd: "must be odd"
|
|
27
|
+
even: "must be even"
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
require 'active_support/inflector'
|
|
2
|
+
|
|
3
|
+
module ActiveModel
|
|
4
|
+
class Name < String
|
|
5
|
+
attr_reader :singular, :plural, :element, :collection, :partial_path
|
|
6
|
+
alias_method :cache_key, :collection
|
|
7
|
+
|
|
8
|
+
def initialize(klass)
|
|
9
|
+
super(klass.name)
|
|
10
|
+
@klass = klass
|
|
11
|
+
@singular = ActiveSupport::Inflector.underscore(self).tr('/', '_').freeze
|
|
12
|
+
@plural = ActiveSupport::Inflector.pluralize(@singular).freeze
|
|
13
|
+
@element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(self)).freeze
|
|
14
|
+
@human = ActiveSupport::Inflector.humanize(@element).freeze
|
|
15
|
+
@collection = ActiveSupport::Inflector.tableize(self).freeze
|
|
16
|
+
@partial_path = "#{@collection}/#{@element}".freeze
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Transform the model name into a more humane format, using I18n. By default,
|
|
20
|
+
# it will underscore then humanize the class name (BlogPost.model_name.human #=> "Blog post").
|
|
21
|
+
# Specify +options+ with additional translating options.
|
|
22
|
+
def human(options={})
|
|
23
|
+
return @human unless @klass.respond_to?(:lookup_ancestors) &&
|
|
24
|
+
@klass.respond_to?(:i18n_scope)
|
|
25
|
+
|
|
26
|
+
defaults = @klass.lookup_ancestors.map do |klass|
|
|
27
|
+
klass.model_name.underscore.to_sym
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
defaults << options.delete(:default) if options[:default]
|
|
31
|
+
defaults << @human
|
|
32
|
+
|
|
33
|
+
options.reverse_merge! :scope => [@klass.i18n_scope, :models], :count => 1, :default => defaults
|
|
34
|
+
I18n.translate(defaults.shift, options)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
module Naming
|
|
39
|
+
# Returns an ActiveModel::Name object for module. It can be
|
|
40
|
+
# used to retrieve all kinds of naming-related information.
|
|
41
|
+
def model_name
|
|
42
|
+
@_model_name ||= ActiveModel::Name.new(self)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
require 'observer'
|
|
2
|
+
require 'singleton'
|
|
3
|
+
require 'active_support/core_ext/array/wrap'
|
|
4
|
+
require 'active_support/core_ext/module/aliasing'
|
|
5
|
+
require 'active_support/core_ext/string/inflections'
|
|
6
|
+
|
|
7
|
+
module ActiveModel
|
|
8
|
+
module Observing
|
|
9
|
+
extend ActiveSupport::Concern
|
|
10
|
+
|
|
11
|
+
included do
|
|
12
|
+
extend Observable
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
module ClassMethods
|
|
16
|
+
# Activates the observers assigned. Examples:
|
|
17
|
+
#
|
|
18
|
+
# # Calls PersonObserver.instance
|
|
19
|
+
# ActiveRecord::Base.observers = :person_observer
|
|
20
|
+
#
|
|
21
|
+
# # Calls Cacher.instance and GarbageCollector.instance
|
|
22
|
+
# ActiveRecord::Base.observers = :cacher, :garbage_collector
|
|
23
|
+
#
|
|
24
|
+
# # Same as above, just using explicit class references
|
|
25
|
+
# ActiveRecord::Base.observers = Cacher, GarbageCollector
|
|
26
|
+
#
|
|
27
|
+
# Note: Setting this does not instantiate the observers yet. +instantiate_observers+ is
|
|
28
|
+
# called during startup, and before each development request.
|
|
29
|
+
def observers=(*values)
|
|
30
|
+
@observers = values.flatten
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Gets the current observers.
|
|
34
|
+
def observers
|
|
35
|
+
@observers ||= []
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Instantiate the global Active Record observers.
|
|
39
|
+
def instantiate_observers
|
|
40
|
+
observers.each { |o| instantiate_observer(o) }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
protected
|
|
44
|
+
def instantiate_observer(observer) #:nodoc:
|
|
45
|
+
# string/symbol
|
|
46
|
+
if observer.respond_to?(:to_sym)
|
|
47
|
+
observer = observer.to_s.camelize.constantize.instance
|
|
48
|
+
elsif observer.respond_to?(:instance)
|
|
49
|
+
observer.instance
|
|
50
|
+
else
|
|
51
|
+
raise ArgumentError, "#{observer} must be a lowercase, underscored class name (or an instance of the class itself) responding to the instance method. Example: Person.observers = :big_brother # calls BigBrother.instance"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Notify observers when the observed class is subclassed.
|
|
56
|
+
def inherited(subclass)
|
|
57
|
+
super
|
|
58
|
+
changed
|
|
59
|
+
notify_observers :observed_class_inherited, subclass
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
# Fires notifications to model's observers
|
|
65
|
+
#
|
|
66
|
+
# def save
|
|
67
|
+
# notify_observers(:before_save)
|
|
68
|
+
# ...
|
|
69
|
+
# notify_observers(:after_save)
|
|
70
|
+
# end
|
|
71
|
+
def notify_observers(method)
|
|
72
|
+
self.class.changed
|
|
73
|
+
self.class.notify_observers(method, self)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Observer classes respond to lifecycle callbacks to implement trigger-like
|
|
78
|
+
# behavior outside the original class. This is a great way to reduce the
|
|
79
|
+
# clutter that normally comes when the model class is burdened with
|
|
80
|
+
# functionality that doesn't pertain to the core responsibility of the
|
|
81
|
+
# class. Example:
|
|
82
|
+
#
|
|
83
|
+
# class CommentObserver < ActiveModel::Observer
|
|
84
|
+
# def after_save(comment)
|
|
85
|
+
# Notifications.deliver_comment("admin@do.com", "New comment was posted", comment)
|
|
86
|
+
# end
|
|
87
|
+
# end
|
|
88
|
+
#
|
|
89
|
+
# This Observer sends an email when a Comment#save is finished.
|
|
90
|
+
#
|
|
91
|
+
# class ContactObserver < ActiveModel::Observer
|
|
92
|
+
# def after_create(contact)
|
|
93
|
+
# contact.logger.info('New contact added!')
|
|
94
|
+
# end
|
|
95
|
+
#
|
|
96
|
+
# def after_destroy(contact)
|
|
97
|
+
# contact.logger.warn("Contact with an id of #{contact.id} was destroyed!")
|
|
98
|
+
# end
|
|
99
|
+
# end
|
|
100
|
+
#
|
|
101
|
+
# This Observer uses logger to log when specific callbacks are triggered.
|
|
102
|
+
#
|
|
103
|
+
# == Observing a class that can't be inferred
|
|
104
|
+
#
|
|
105
|
+
# Observers will by default be mapped to the class with which they share a name. So CommentObserver will
|
|
106
|
+
# be tied to observing Comment, ProductManagerObserver to ProductManager, and so on. If you want to name your observer
|
|
107
|
+
# differently than the class you're interested in observing, you can use the Observer.observe class method which takes
|
|
108
|
+
# either the concrete class (Product) or a symbol for that class (:product):
|
|
109
|
+
#
|
|
110
|
+
# class AuditObserver < ActiveModel::Observer
|
|
111
|
+
# observe :account
|
|
112
|
+
#
|
|
113
|
+
# def after_update(account)
|
|
114
|
+
# AuditTrail.new(account, "UPDATED")
|
|
115
|
+
# end
|
|
116
|
+
# end
|
|
117
|
+
#
|
|
118
|
+
# If the audit observer needs to watch more than one kind of object, this can be specified with multiple arguments:
|
|
119
|
+
#
|
|
120
|
+
# class AuditObserver < ActiveModel::Observer
|
|
121
|
+
# observe :account, :balance
|
|
122
|
+
#
|
|
123
|
+
# def after_update(record)
|
|
124
|
+
# AuditTrail.new(record, "UPDATED")
|
|
125
|
+
# end
|
|
126
|
+
# end
|
|
127
|
+
#
|
|
128
|
+
# The AuditObserver will now act on both updates to Account and Balance by treating them both as records.
|
|
129
|
+
#
|
|
130
|
+
class Observer
|
|
131
|
+
include Singleton
|
|
132
|
+
|
|
133
|
+
class << self
|
|
134
|
+
# Attaches the observer to the supplied model classes.
|
|
135
|
+
def observe(*models)
|
|
136
|
+
models.flatten!
|
|
137
|
+
models.collect! { |model| model.respond_to?(:to_sym) ? model.to_s.camelize.constantize : model }
|
|
138
|
+
define_method(:observed_classes) { models }
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Returns an array of Classes to observe.
|
|
142
|
+
#
|
|
143
|
+
# You can override this instead of using the +observe+ helper.
|
|
144
|
+
#
|
|
145
|
+
# class AuditObserver < ActiveModel::Observer
|
|
146
|
+
# def self.observed_classes
|
|
147
|
+
# [AccountObserver, BalanceObserver]
|
|
148
|
+
# end
|
|
149
|
+
# end
|
|
150
|
+
def observed_classes
|
|
151
|
+
Array.wrap(observed_class)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# The class observed by default is inferred from the observer's class name:
|
|
155
|
+
# assert_equal Person, PersonObserver.observed_class
|
|
156
|
+
def observed_class
|
|
157
|
+
if observed_class_name = name[/(.*)Observer/, 1]
|
|
158
|
+
observed_class_name.constantize
|
|
159
|
+
else
|
|
160
|
+
nil
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Start observing the declared classes and their subclasses.
|
|
166
|
+
def initialize
|
|
167
|
+
observed_classes.each { |klass| add_observer!(klass) }
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def observed_classes #:nodoc:
|
|
171
|
+
self.class.observed_classes
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Send observed_method(object) if the method exists.
|
|
175
|
+
def update(observed_method, object) #:nodoc:
|
|
176
|
+
send(observed_method, object) if respond_to?(observed_method)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Special method sent by the observed class when it is inherited.
|
|
180
|
+
# Passes the new subclass.
|
|
181
|
+
def observed_class_inherited(subclass) #:nodoc:
|
|
182
|
+
self.class.observe(observed_classes + [subclass])
|
|
183
|
+
add_observer!(subclass)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
protected
|
|
187
|
+
def add_observer!(klass) #:nodoc:
|
|
188
|
+
klass.add_observer(self)
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|