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.
Files changed (113) hide show
  1. data/lib/cassandra_object/associations/one_to_many.rb +136 -0
  2. data/lib/cassandra_object/associations/one_to_one.rb +77 -0
  3. data/lib/cassandra_object/associations.rb +35 -0
  4. data/lib/cassandra_object/attributes.rb +93 -0
  5. data/lib/cassandra_object/base.rb +104 -0
  6. data/lib/cassandra_object/callbacks.rb +10 -0
  7. data/lib/cassandra_object/collection.rb +8 -0
  8. data/lib/cassandra_object/cursor.rb +86 -0
  9. data/lib/cassandra_object/dirty.rb +27 -0
  10. data/lib/cassandra_object/identity/abstract_key_factory.rb +36 -0
  11. data/lib/cassandra_object/identity/key.rb +20 -0
  12. data/lib/cassandra_object/identity/natural_key_factory.rb +51 -0
  13. data/lib/cassandra_object/identity/uuid_key_factory.rb +37 -0
  14. data/lib/cassandra_object/identity.rb +61 -0
  15. data/lib/cassandra_object/indexes.rb +129 -0
  16. data/lib/cassandra_object/legacy_callbacks.rb +33 -0
  17. data/lib/cassandra_object/migrations.rb +72 -0
  18. data/lib/cassandra_object/mocking.rb +15 -0
  19. data/lib/cassandra_object/persistence.rb +193 -0
  20. data/lib/cassandra_object/serialization.rb +6 -0
  21. data/lib/cassandra_object/type_registration.rb +7 -0
  22. data/lib/cassandra_object/types.rb +128 -0
  23. data/lib/cassandra_object/validation.rb +58 -0
  24. data/lib/cassandra_object.rb +30 -0
  25. data/vendor/active_support_shims.rb +4 -0
  26. data/vendor/activemodel/CHANGELOG +13 -0
  27. data/vendor/activemodel/CHANGES +12 -0
  28. data/vendor/activemodel/MIT-LICENSE +21 -0
  29. data/vendor/activemodel/README +21 -0
  30. data/vendor/activemodel/Rakefile +52 -0
  31. data/vendor/activemodel/activemodel.gemspec +19 -0
  32. data/vendor/activemodel/examples/validations.rb +29 -0
  33. data/vendor/activemodel/lib/active_model/attribute_methods.rb +291 -0
  34. data/vendor/activemodel/lib/active_model/callbacks.rb +91 -0
  35. data/vendor/activemodel/lib/active_model/conversion.rb +8 -0
  36. data/vendor/activemodel/lib/active_model/deprecated_error_methods.rb +33 -0
  37. data/vendor/activemodel/lib/active_model/dirty.rb +126 -0
  38. data/vendor/activemodel/lib/active_model/errors.rb +162 -0
  39. data/vendor/activemodel/lib/active_model/lint.rb +91 -0
  40. data/vendor/activemodel/lib/active_model/locale/en.yml +27 -0
  41. data/vendor/activemodel/lib/active_model/naming.rb +45 -0
  42. data/vendor/activemodel/lib/active_model/observing.rb +191 -0
  43. data/vendor/activemodel/lib/active_model/railtie.rb +2 -0
  44. data/vendor/activemodel/lib/active_model/serialization.rb +30 -0
  45. data/vendor/activemodel/lib/active_model/serializers/json.rb +96 -0
  46. data/vendor/activemodel/lib/active_model/serializers/xml.rb +204 -0
  47. data/vendor/activemodel/lib/active_model/state_machine/event.rb +62 -0
  48. data/vendor/activemodel/lib/active_model/state_machine/machine.rb +75 -0
  49. data/vendor/activemodel/lib/active_model/state_machine/state.rb +47 -0
  50. data/vendor/activemodel/lib/active_model/state_machine/state_transition.rb +40 -0
  51. data/vendor/activemodel/lib/active_model/state_machine.rb +70 -0
  52. data/vendor/activemodel/lib/active_model/test_case.rb +18 -0
  53. data/vendor/activemodel/lib/active_model/translation.rb +44 -0
  54. data/vendor/activemodel/lib/active_model/validations/acceptance.rb +55 -0
  55. data/vendor/activemodel/lib/active_model/validations/confirmation.rb +47 -0
  56. data/vendor/activemodel/lib/active_model/validations/exclusion.rb +42 -0
  57. data/vendor/activemodel/lib/active_model/validations/format.rb +64 -0
  58. data/vendor/activemodel/lib/active_model/validations/inclusion.rb +42 -0
  59. data/vendor/activemodel/lib/active_model/validations/length.rb +117 -0
  60. data/vendor/activemodel/lib/active_model/validations/numericality.rb +111 -0
  61. data/vendor/activemodel/lib/active_model/validations/presence.rb +42 -0
  62. data/vendor/activemodel/lib/active_model/validations/with.rb +59 -0
  63. data/vendor/activemodel/lib/active_model/validations.rb +120 -0
  64. data/vendor/activemodel/lib/active_model/validator.rb +110 -0
  65. data/vendor/activemodel/lib/active_model/version.rb +9 -0
  66. data/vendor/activemodel/lib/active_model.rb +61 -0
  67. data/vendor/activemodel/test/cases/attribute_methods_test.rb +46 -0
  68. data/vendor/activemodel/test/cases/callbacks_test.rb +70 -0
  69. data/vendor/activemodel/test/cases/helper.rb +23 -0
  70. data/vendor/activemodel/test/cases/lint_test.rb +28 -0
  71. data/vendor/activemodel/test/cases/naming_test.rb +28 -0
  72. data/vendor/activemodel/test/cases/observing_test.rb +133 -0
  73. data/vendor/activemodel/test/cases/serializeration/json_serialization_test.rb +83 -0
  74. data/vendor/activemodel/test/cases/serializeration/xml_serialization_test.rb +110 -0
  75. data/vendor/activemodel/test/cases/state_machine/event_test.rb +49 -0
  76. data/vendor/activemodel/test/cases/state_machine/machine_test.rb +43 -0
  77. data/vendor/activemodel/test/cases/state_machine/state_test.rb +72 -0
  78. data/vendor/activemodel/test/cases/state_machine/state_transition_test.rb +84 -0
  79. data/vendor/activemodel/test/cases/state_machine_test.rb +312 -0
  80. data/vendor/activemodel/test/cases/tests_database.rb +37 -0
  81. data/vendor/activemodel/test/cases/translation_test.rb +45 -0
  82. data/vendor/activemodel/test/cases/validations/acceptance_validation_test.rb +71 -0
  83. data/vendor/activemodel/test/cases/validations/conditional_validation_test.rb +141 -0
  84. data/vendor/activemodel/test/cases/validations/confirmation_validation_test.rb +58 -0
  85. data/vendor/activemodel/test/cases/validations/exclusion_validation_test.rb +47 -0
  86. data/vendor/activemodel/test/cases/validations/format_validation_test.rb +118 -0
  87. data/vendor/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb +175 -0
  88. data/vendor/activemodel/test/cases/validations/i18n_validation_test.rb +527 -0
  89. data/vendor/activemodel/test/cases/validations/inclusion_validation_test.rb +71 -0
  90. data/vendor/activemodel/test/cases/validations/length_validation_test.rb +437 -0
  91. data/vendor/activemodel/test/cases/validations/numericality_validation_test.rb +180 -0
  92. data/vendor/activemodel/test/cases/validations/presence_validation_test.rb +70 -0
  93. data/vendor/activemodel/test/cases/validations/with_validation_test.rb +166 -0
  94. data/vendor/activemodel/test/cases/validations_test.rb +215 -0
  95. data/vendor/activemodel/test/config.rb +3 -0
  96. data/vendor/activemodel/test/fixtures/topics.yml +41 -0
  97. data/vendor/activemodel/test/models/contact.rb +7 -0
  98. data/vendor/activemodel/test/models/custom_reader.rb +17 -0
  99. data/vendor/activemodel/test/models/developer.rb +6 -0
  100. data/vendor/activemodel/test/models/person.rb +9 -0
  101. data/vendor/activemodel/test/models/reply.rb +34 -0
  102. data/vendor/activemodel/test/models/topic.rb +9 -0
  103. data/vendor/activemodel/test/models/track_back.rb +4 -0
  104. data/vendor/activemodel/test/schema.rb +14 -0
  105. data/vendor/activesupport/lib/active_support/autoload.rb +48 -0
  106. data/vendor/activesupport/lib/active_support/concern.rb +25 -0
  107. data/vendor/activesupport/lib/active_support/core_ext/array/wrap.rb +20 -0
  108. data/vendor/activesupport/lib/active_support/core_ext/object/blank.rb +58 -0
  109. data/vendor/activesupport/lib/active_support/core_ext/object/tap.rb +6 -0
  110. data/vendor/activesupport/lib/active_support/dependency_module.rb +17 -0
  111. data/vendor/activesupport/lib/active_support/i18n.rb +2 -0
  112. data/vendor/activesupport/lib/active_support/locale/en.yml +33 -0
  113. 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
@@ -0,0 +1,2 @@
1
+ require "active_model"
2
+ require "rails"