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,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
|