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,58 @@
1
+ module CassandraObject
2
+ module Validation
3
+ class RecordInvalidError < StandardError
4
+ attr_reader :record
5
+ def initialize(record)
6
+ @record = record
7
+ super("Invalid record: #{@record.errors.full_messages.to_sentence}")
8
+ end
9
+
10
+ def self.raise_error(record)
11
+ raise new(record)
12
+ end
13
+ end
14
+ extend ActiveSupport::Concern
15
+ include ActiveModel::Validations
16
+
17
+ included do
18
+ define_model_callbacks :validation
19
+ if CassandraObject.old_active_support
20
+ define_callbacks :validate
21
+ else
22
+ define_callbacks :validate, :scope => :name
23
+ end
24
+ end
25
+
26
+ module ClassMethods
27
+ def create!(attributes)
28
+ returning new(attributes), &:save!
29
+ end
30
+ end
31
+
32
+ module InstanceMethods
33
+ def valid?
34
+ run_callbacks :validation do
35
+ super
36
+ end
37
+ end
38
+
39
+ def save
40
+ if valid?
41
+ super
42
+ else
43
+ false
44
+ end
45
+ end
46
+
47
+ def save!
48
+ save || RecordInvalidError.raise_error(self)
49
+ end
50
+
51
+ if CassandraObject.old_active_support
52
+ def _run_validate_callbacks
53
+ run_callbacks :validate
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,30 @@
1
+ require 'rubygems'
2
+ require 'i18n'
3
+ require 'active_support'
4
+ require 'active_support/version'
5
+
6
+ module CassandraObject
7
+ class << self
8
+ attr_accessor :old_active_support
9
+ end
10
+ self.old_active_support = false
11
+ VERSION = "0.5.0"
12
+ end
13
+
14
+
15
+ if ActiveSupport::VERSION::STRING =~ /^2/
16
+
17
+ vendor = File.expand_path(File.dirname(__FILE__) + "/../vendor")
18
+ CassandraObject.old_active_support = true
19
+ $LOAD_PATH << vendor
20
+ require 'active_support_shims'
21
+ $LOAD_PATH << vendor + "/activemodel/lib"
22
+ require 'active_model'
23
+ else
24
+ require 'active_support/all'
25
+ require 'active_model'
26
+ end
27
+
28
+ require 'cassandra_object/base'
29
+
30
+
@@ -0,0 +1,4 @@
1
+ $: << File.dirname(__FILE__) + "/activesupport/lib"
2
+
3
+ require 'active_support/concern'
4
+ require 'active_support/autoload'
@@ -0,0 +1,13 @@
1
+ *Edge*
2
+
3
+ * Change the ActiveModel::Base.include_root_in_json default to true for Rails 3 [DHH]
4
+
5
+ * Add validates_format_of :without => /regexp/ option. #430 [Elliot Winkler, Peer Allan]
6
+
7
+ Example :
8
+
9
+ validates_format_of :subdomain, :without => /www|admin|mail/
10
+
11
+ * Introduce validates_with to encapsulate attribute validations in a class. #2630 [Jeff Dean]
12
+
13
+ * Extracted from Active Record and Active Resource.
@@ -0,0 +1,12 @@
1
+ Changes from extracting bits to ActiveModel
2
+
3
+ * ActiveModel::Observer#add_observer!
4
+
5
+ It has a custom hook to define after_find that should really be in a
6
+ ActiveRecord::Observer subclass:
7
+
8
+ def add_observer!(klass)
9
+ klass.add_observer(self)
10
+ klass.class_eval 'def after_find() end' unless
11
+ klass.respond_to?(:after_find)
12
+ end
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2004-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
+
@@ -0,0 +1,21 @@
1
+ Active Model
2
+ ==============
3
+
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.
8
+
9
+ Here's what I plan to extract:
10
+ * ActiveModel::Observing
11
+ * ActiveModel::Callbacks
12
+ * ActiveModel::Validations
13
+
14
+ # for ActiveResource params and ActiveRecord options
15
+ * ActiveModel::Scoping
16
+
17
+ # to_json, to_xml, etc
18
+ * ActiveModel::Serialization
19
+
20
+ I'm trying to keep ActiveRecord compatibility where possible, but I'm
21
+ annotating the spots where I'm diverging a bit.
@@ -0,0 +1,52 @@
1
+ dir = File.dirname(__FILE__)
2
+ require "#{dir}/lib/active_model/version"
3
+
4
+ PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
5
+ PKG_NAME = 'activemodel'
6
+ PKG_VERSION = ActiveModel::VERSION::STRING + PKG_BUILD
7
+ PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
8
+ RELEASE_NAME = "REL #{PKG_VERSION}"
9
+
10
+
11
+ require 'rake/testtask'
12
+
13
+ task :default => :test
14
+
15
+ Rake::TestTask.new do |t|
16
+ t.libs << "#{dir}/test"
17
+ t.test_files = Dir.glob("#{dir}/test/cases/**/*_test.rb").sort
18
+ t.warning = true
19
+ end
20
+
21
+ namespace :test do
22
+ task :isolated do
23
+ ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME'))
24
+ Dir.glob("#{dir}/test/**/*_test.rb").all? do |file|
25
+ system(ruby, '-w', "-I#{dir}/lib", "-I#{dir}/test", file)
26
+ end or raise "Failures"
27
+ end
28
+ end
29
+
30
+
31
+ require 'rake/rdoctask'
32
+
33
+ # Generate the RDoc documentation
34
+ Rake::RDocTask.new do |rdoc|
35
+ rdoc.rdoc_dir = "#{dir}/doc"
36
+ rdoc.title = "Active Model"
37
+ rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
38
+ rdoc.options << '--charset' << 'utf-8'
39
+ rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo'
40
+ rdoc.rdoc_files.include("#{dir}/README", "#{dir}/CHANGES")
41
+ rdoc.rdoc_files.include("#{dir}/lib/**/*.rb")
42
+ end
43
+
44
+
45
+ require 'rake/packagetask'
46
+ require 'rake/gempackagetask'
47
+
48
+ spec = eval(File.read("#{dir}/activemodel.gemspec"))
49
+
50
+ Rake::GemPackageTask.new(spec) do |p|
51
+ p.gem_spec = spec
52
+ end
@@ -0,0 +1,19 @@
1
+ Gem::Specification.new do |s|
2
+ s.platform = Gem::Platform::RUBY
3
+ s.name = 'activemodel'
4
+ s.version = '3.0.pre'
5
+ s.summary = "A toolkit for building other modeling frameworks like ActiveRecord"
6
+ s.description = %q{Extracts common modeling concerns from ActiveRecord to share between similar frameworks like ActiveResource.}
7
+
8
+ s.author = "David Heinemeier Hansson"
9
+ s.email = "david@loudthinking.com"
10
+ s.rubyforge_project = "activemodel"
11
+ s.homepage = "http://www.rubyonrails.org"
12
+
13
+ s.has_rdoc = true
14
+
15
+ s.add_dependency('activesupport', '= 3.0.pre')
16
+
17
+ s.require_path = 'lib'
18
+ s.files = Dir["CHANGELOG", "MIT-LICENSE", "README", "lib/**/*"]
19
+ end
@@ -0,0 +1,29 @@
1
+ require 'active_model'
2
+
3
+ class Person
4
+ include ActiveModel::Conversion
5
+ include ActiveModel::Validations
6
+
7
+ validates_presence_of :name
8
+
9
+ attr_accessor :name
10
+
11
+ def initialize(attributes = {})
12
+ @name = attributes[:name]
13
+ end
14
+
15
+ def persist
16
+ @persisted = true
17
+ end
18
+
19
+ def new_record?
20
+ @persisted
21
+ end
22
+ end
23
+
24
+ person1 = Person.new
25
+ p person1.valid?
26
+ person1.errors
27
+
28
+ person2 = Person.new(:name => "matz")
29
+ p person2.valid?
@@ -0,0 +1,291 @@
1
+ require 'active_support/core_ext/hash/keys'
2
+ require 'active_support/core_ext/class/inheritable_attributes'
3
+
4
+ module ActiveModel
5
+ class MissingAttributeError < NoMethodError
6
+ end
7
+
8
+ module AttributeMethods
9
+ extend ActiveSupport::Concern
10
+
11
+ # Declare and check for suffixed attribute methods.
12
+ module ClassMethods
13
+ # Defines an "attribute" method (like +inheritance_column+ or
14
+ # +table_name+). A new (class) method will be created with the
15
+ # given name. If a value is specified, the new method will
16
+ # return that value (as a string). Otherwise, the given block
17
+ # will be used to compute the value of the method.
18
+ #
19
+ # The original method will be aliased, with the new name being
20
+ # prefixed with "original_". This allows the new method to
21
+ # access the original value.
22
+ #
23
+ # Example:
24
+ #
25
+ # class A < ActiveRecord::Base
26
+ # define_attr_method :primary_key, "sysid"
27
+ # define_attr_method( :inheritance_column ) do
28
+ # original_inheritance_column + "_id"
29
+ # end
30
+ # end
31
+ def define_attr_method(name, value=nil, &block)
32
+ sing = metaclass
33
+ sing.send :alias_method, "original_#{name}", name
34
+ if block_given?
35
+ sing.send :define_method, name, &block
36
+ else
37
+ # use eval instead of a block to work around a memory leak in dev
38
+ # mode in fcgi
39
+ sing.class_eval "def #{name}; #{value.to_s.inspect}; end"
40
+ end
41
+ end
42
+
43
+ # Declares a method available for all attributes with the given prefix.
44
+ # Uses +method_missing+ and <tt>respond_to?</tt> to rewrite the method.
45
+ #
46
+ # #{prefix}#{attr}(*args, &block)
47
+ #
48
+ # to
49
+ #
50
+ # #{prefix}attribute(#{attr}, *args, &block)
51
+ #
52
+ # An <tt>#{prefix}attribute</tt> instance method must exist and accept at least
53
+ # the +attr+ argument.
54
+ #
55
+ # For example:
56
+ #
57
+ # class Person < ActiveRecord::Base
58
+ # attribute_method_prefix 'clear_'
59
+ #
60
+ # private
61
+ # def clear_attribute(attr)
62
+ # ...
63
+ # end
64
+ # end
65
+ #
66
+ # person = Person.find(1)
67
+ # person.name # => 'Gem'
68
+ # person.clear_name
69
+ # person.name # => ''
70
+ def attribute_method_prefix(*prefixes)
71
+ attribute_method_matchers.concat(prefixes.map { |prefix| AttributeMethodMatcher.new :prefix => prefix })
72
+ undefine_attribute_methods
73
+ end
74
+
75
+ # Declares a method available for all attributes with the given suffix.
76
+ # Uses +method_missing+ and <tt>respond_to?</tt> to rewrite the method.
77
+ #
78
+ # #{attr}#{suffix}(*args, &block)
79
+ #
80
+ # to
81
+ #
82
+ # attribute#{suffix}(#{attr}, *args, &block)
83
+ #
84
+ # An <tt>attribute#{suffix}</tt> instance method must exist and accept at least
85
+ # the +attr+ argument.
86
+ #
87
+ # For example:
88
+ #
89
+ # class Person < ActiveRecord::Base
90
+ # attribute_method_suffix '_short?'
91
+ #
92
+ # private
93
+ # def attribute_short?(attr)
94
+ # ...
95
+ # end
96
+ # end
97
+ #
98
+ # person = Person.find(1)
99
+ # person.name # => 'Gem'
100
+ # person.name_short? # => true
101
+ def attribute_method_suffix(*suffixes)
102
+ attribute_method_matchers.concat(suffixes.map { |suffix| AttributeMethodMatcher.new :suffix => suffix })
103
+ undefine_attribute_methods
104
+ end
105
+
106
+ # Declares a method available for all attributes with the given prefix
107
+ # and suffix. Uses +method_missing+ and <tt>respond_to?</tt> to rewrite
108
+ # the method.
109
+ #
110
+ # #{prefix}#{attr}#{suffix}(*args, &block)
111
+ #
112
+ # to
113
+ #
114
+ # #{prefix}attribute#{suffix}(#{attr}, *args, &block)
115
+ #
116
+ # An <tt>#{prefix}attribute#{suffix}</tt> instance method must exist and
117
+ # accept at least the +attr+ argument.
118
+ #
119
+ # For example:
120
+ #
121
+ # class Person < ActiveRecord::Base
122
+ # attribute_method_affix :prefix => 'reset_', :suffix => '_to_default!'
123
+ #
124
+ # private
125
+ # def reset_attribute_to_default!(attr)
126
+ # ...
127
+ # end
128
+ # end
129
+ #
130
+ # person = Person.find(1)
131
+ # person.name # => 'Gem'
132
+ # person.reset_name_to_default!
133
+ # person.name # => 'Gemma'
134
+ def attribute_method_affix(*affixes)
135
+ attribute_method_matchers.concat(affixes.map { |affix| AttributeMethodMatcher.new :prefix => affix[:prefix], :suffix => affix[:suffix] })
136
+ undefine_attribute_methods
137
+ end
138
+
139
+ def alias_attribute(new_name, old_name)
140
+ attribute_method_matchers.each do |matcher|
141
+ module_eval <<-STR, __FILE__, __LINE__+1
142
+ def #{matcher.method_name(new_name)}(*args)
143
+ send(:#{matcher.method_name(old_name)}, *args)
144
+ end
145
+ STR
146
+ end
147
+ end
148
+
149
+ def define_attribute_methods(attr_names)
150
+ return if attribute_methods_generated?
151
+ attr_names.each do |attr_name|
152
+ attribute_method_matchers.each do |matcher|
153
+ unless instance_method_already_implemented?(matcher.method_name(attr_name))
154
+ generate_method = "define_method_#{matcher.prefix}attribute#{matcher.suffix}"
155
+
156
+ if respond_to?(generate_method)
157
+ send(generate_method, attr_name)
158
+ else
159
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__+1
160
+ def #{matcher.method_name(attr_name)}(*args)
161
+ send(:#{matcher.method_missing_target}, '#{attr_name}', *args)
162
+ end
163
+ STR
164
+ end
165
+ end
166
+ end
167
+ end
168
+ @attribute_methods_generated = true
169
+ end
170
+
171
+ def undefine_attribute_methods
172
+ generated_attribute_methods.module_eval do
173
+ instance_methods.each { |m| undef_method(m) }
174
+ end
175
+ @attribute_methods_generated = nil
176
+ end
177
+
178
+ def generated_attribute_methods #:nodoc:
179
+ @generated_attribute_methods ||= begin
180
+ mod = Module.new
181
+ include mod
182
+ mod
183
+ end
184
+ end
185
+
186
+ def attribute_methods_generated?
187
+ @attribute_methods_generated ||= nil
188
+ end
189
+
190
+ protected
191
+ def instance_method_already_implemented?(method_name)
192
+ method_defined?(method_name)
193
+ end
194
+
195
+ private
196
+ class AttributeMethodMatcher
197
+ attr_reader :prefix, :suffix
198
+
199
+ AttributeMethodMatch = Struct.new(:target, :attr_name)
200
+
201
+ def initialize(options = {})
202
+ options.symbolize_keys!
203
+ @prefix, @suffix = options[:prefix] || '', options[:suffix] || ''
204
+ @regex = /^(#{Regexp.escape(@prefix)})(.+?)(#{Regexp.escape(@suffix)})$/
205
+ end
206
+
207
+ def match(method_name)
208
+ if matchdata = @regex.match(method_name)
209
+ AttributeMethodMatch.new(method_missing_target, matchdata[2])
210
+ else
211
+ nil
212
+ end
213
+ end
214
+
215
+ def method_name(attr_name)
216
+ "#{prefix}#{attr_name}#{suffix}"
217
+ end
218
+
219
+ def method_missing_target
220
+ :"#{prefix}attribute#{suffix}"
221
+ end
222
+ end
223
+
224
+ def attribute_method_matchers #:nodoc:
225
+ read_inheritable_attribute(:attribute_method_matchers) || write_inheritable_attribute(:attribute_method_matchers, [])
226
+ end
227
+ end
228
+
229
+ # Allows access to the object attributes, which are held in the <tt>@attributes</tt> hash, as though they
230
+ # were first-class methods. So a Person class with a name attribute can use Person#name and
231
+ # Person#name= and never directly use the attributes hash -- except for multiple assigns with
232
+ # ActiveRecord#attributes=. A Milestone class can also ask Milestone#completed? to test that
233
+ # the completed attribute is not +nil+ or 0.
234
+ #
235
+ # It's also possible to instantiate related objects, so a Client class belonging to the clients
236
+ # table with a +master_id+ foreign key can instantiate master through Client#master.
237
+ def method_missing(method_id, *args, &block)
238
+ method_name = method_id.to_s
239
+ if match = match_attribute_method?(method_name)
240
+ guard_private_attribute_method!(method_name, args)
241
+ return __send__(match.target, match.attr_name, *args, &block)
242
+ end
243
+ super
244
+ end
245
+
246
+ # A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
247
+ # <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt>
248
+ # which will all return +true+.
249
+ alias :respond_to_without_attributes? :respond_to?
250
+ def respond_to?(method, include_private_methods = false)
251
+ if super
252
+ return true
253
+ elsif !include_private_methods && super(method, true)
254
+ # If we're here then we haven't found among non-private methods
255
+ # but found among all methods. Which means that given method is private.
256
+ return false
257
+ elsif match_attribute_method?(method.to_s)
258
+ return true
259
+ end
260
+ super
261
+ end
262
+
263
+ protected
264
+ def attribute_method?(attr_name)
265
+ attributes.include?(attr_name)
266
+ end
267
+
268
+ private
269
+ # Returns a struct representing the matching attribute method.
270
+ # The struct's attributes are prefix, base and suffix.
271
+ def match_attribute_method?(method_name)
272
+ self.class.send(:attribute_method_matchers).each do |method|
273
+ if (match = method.match(method_name)) && attribute_method?(match.attr_name)
274
+ return match
275
+ end
276
+ end
277
+ nil
278
+ end
279
+
280
+ # prevent method_missing from calling private methods with #send
281
+ def guard_private_attribute_method!(method_name, args)
282
+ if self.class.private_method_defined?(method_name)
283
+ raise NoMethodError.new("Attempt to call private method", method_name, args)
284
+ end
285
+ end
286
+
287
+ def missing_attribute(attr_name, stack)
288
+ raise ActiveModel::MissingAttributeError, "missing attribute: #{attr_name}", stack
289
+ end
290
+ end
291
+ end
@@ -0,0 +1,91 @@
1
+ require 'active_support/callbacks'
2
+
3
+ module ActiveModel
4
+ module Callbacks
5
+ def self.extended(base)
6
+ base.class_eval do
7
+ include ActiveSupport::Callbacks
8
+ end
9
+ end
10
+
11
+ # Define callbacks similar to ActiveRecord ones. It means:
12
+ #
13
+ # * The callback chain is aborted whenever the block given to
14
+ # _run_callbacks returns false.
15
+ #
16
+ # * If a class is given to the fallback, it will search for
17
+ # before_create, around_create and after_create methods.
18
+ #
19
+ # == Usage
20
+ #
21
+ # First you need to define which callbacks your model will have:
22
+ #
23
+ # class MyModel
24
+ # define_model_callbacks :create
25
+ # end
26
+ #
27
+ # This will define three class methods: before_create, around_create,
28
+ # and after_create. They accept a symbol, a string, an object or a block.
29
+ #
30
+ # After you create a callback, you need to tell when they are executed.
31
+ # For example, you could do:
32
+ #
33
+ # def create
34
+ # _run_create_callbacks do
35
+ # super
36
+ # end
37
+ # end
38
+ #
39
+ # == Options
40
+ #
41
+ # define_model_callbacks accepts all options define_callbacks does, in
42
+ # case you want to overwrite a default. Besides that, it also accepts
43
+ # an :only option, where you can choose if you want all types (before,
44
+ # around or after) or just some:
45
+ #
46
+ # define_model_callbacks :initializer, :only => :after
47
+ #
48
+ def define_model_callbacks(*callbacks)
49
+ options = callbacks.extract_options!
50
+ options = { :terminator => "result == false", :scope => [:kind, :name] }.merge(options)
51
+
52
+ types = Array(options.delete(:only))
53
+ types = [:before, :around, :after] if types.empty?
54
+
55
+ callbacks.each do |callback|
56
+ define_callbacks(callback, options)
57
+
58
+ types.each do |type|
59
+ send(:"_define_#{type}_model_callback", self, callback)
60
+ end
61
+ end
62
+ end
63
+
64
+ def _define_before_model_callback(klass, callback) #:nodoc:
65
+ klass.class_eval <<-CALLBACK, __FILE__, __LINE__
66
+ def self.before_#{callback}(*args, &block)
67
+ set_callback(:#{callback}, :before, *args, &block)
68
+ end
69
+ CALLBACK
70
+ end
71
+
72
+ def _define_around_model_callback(klass, callback) #:nodoc:
73
+ klass.class_eval <<-CALLBACK, __FILE__, __LINE__
74
+ def self.around_#{callback}(*args, &block)
75
+ set_callback(:#{callback}, :around, *args, &block)
76
+ end
77
+ CALLBACK
78
+ end
79
+
80
+ def _define_after_model_callback(klass, callback) #:nodoc:
81
+ klass.class_eval <<-CALLBACK, __FILE__, __LINE__
82
+ def self.after_#{callback}(*args, &block)
83
+ options = args.extract_options!
84
+ options[:prepend] = true
85
+ options[:if] = Array(options[:if]) << "!halted && value != false"
86
+ set_callback(:#{callback}, :after, *(args << options), &block)
87
+ end
88
+ CALLBACK
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,8 @@
1
+ module ActiveModel
2
+ # Include ActiveModel::Conversion if your object "acts like an ActiveModel model".
3
+ module Conversion
4
+ def to_model
5
+ self
6
+ end
7
+ end
8
+ end