cassandra_object 0.6.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- 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,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,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
|