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,30 @@
|
|
|
1
|
+
require 'active_support/core_ext/hash/except'
|
|
2
|
+
require 'active_support/core_ext/hash/slice'
|
|
3
|
+
|
|
4
|
+
module ActiveModel
|
|
5
|
+
module Serialization
|
|
6
|
+
def serializable_hash(options = nil)
|
|
7
|
+
options ||= {}
|
|
8
|
+
|
|
9
|
+
options[:only] = Array.wrap(options[:only]).map { |n| n.to_s }
|
|
10
|
+
options[:except] = Array.wrap(options[:except]).map { |n| n.to_s }
|
|
11
|
+
|
|
12
|
+
attribute_names = attributes.keys.sort
|
|
13
|
+
if options[:only].any?
|
|
14
|
+
attribute_names &= options[:only]
|
|
15
|
+
elsif options[:except].any?
|
|
16
|
+
attribute_names -= options[:except]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
method_names = Array.wrap(options[:methods]).inject([]) do |methods, name|
|
|
20
|
+
methods << name if respond_to?(name.to_s)
|
|
21
|
+
methods
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
(attribute_names + method_names).inject({}) { |hash, name|
|
|
25
|
+
hash[name] = send(name)
|
|
26
|
+
hash
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
require 'active_support/json'
|
|
2
|
+
require 'active_support/core_ext/class/attribute_accessors'
|
|
3
|
+
|
|
4
|
+
module ActiveModel
|
|
5
|
+
module Serializers
|
|
6
|
+
module JSON
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
include ActiveModel::Serialization
|
|
9
|
+
|
|
10
|
+
included do
|
|
11
|
+
extend ActiveModel::Naming
|
|
12
|
+
|
|
13
|
+
cattr_accessor :include_root_in_json, :instance_writer => true
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Returns a JSON string representing the model. Some configuration is
|
|
17
|
+
# available through +options+.
|
|
18
|
+
#
|
|
19
|
+
# The option <tt>ActiveModel::Base.include_root_in_json</tt> controls the
|
|
20
|
+
# top-level behavior of to_json. It is true by default. When it is <tt>true</tt>,
|
|
21
|
+
# to_json will emit a single root node named after the object's type. For example:
|
|
22
|
+
#
|
|
23
|
+
# konata = User.find(1)
|
|
24
|
+
# konata.to_json
|
|
25
|
+
# # => { "user": {"id": 1, "name": "Konata Izumi", "age": 16,
|
|
26
|
+
# "created_at": "2006/08/01", "awesome": true} }
|
|
27
|
+
#
|
|
28
|
+
# ActiveRecord::Base.include_root_in_json = false
|
|
29
|
+
# konata.to_json
|
|
30
|
+
# # => {"id": 1, "name": "Konata Izumi", "age": 16,
|
|
31
|
+
# "created_at": "2006/08/01", "awesome": true}
|
|
32
|
+
#
|
|
33
|
+
# The remainder of the examples in this section assume include_root_in_json is set to
|
|
34
|
+
# <tt>false</tt>.
|
|
35
|
+
#
|
|
36
|
+
# Without any +options+, the returned JSON string will include all
|
|
37
|
+
# the model's attributes. For example:
|
|
38
|
+
#
|
|
39
|
+
# konata = User.find(1)
|
|
40
|
+
# konata.to_json
|
|
41
|
+
# # => {"id": 1, "name": "Konata Izumi", "age": 16,
|
|
42
|
+
# "created_at": "2006/08/01", "awesome": true}
|
|
43
|
+
#
|
|
44
|
+
# The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the attributes
|
|
45
|
+
# included, and work similar to the +attributes+ method. For example:
|
|
46
|
+
#
|
|
47
|
+
# konata.to_json(:only => [ :id, :name ])
|
|
48
|
+
# # => {"id": 1, "name": "Konata Izumi"}
|
|
49
|
+
#
|
|
50
|
+
# konata.to_json(:except => [ :id, :created_at, :age ])
|
|
51
|
+
# # => {"name": "Konata Izumi", "awesome": true}
|
|
52
|
+
#
|
|
53
|
+
# To include any methods on the model, use <tt>:methods</tt>.
|
|
54
|
+
#
|
|
55
|
+
# konata.to_json(:methods => :permalink)
|
|
56
|
+
# # => {"id": 1, "name": "Konata Izumi", "age": 16,
|
|
57
|
+
# "created_at": "2006/08/01", "awesome": true,
|
|
58
|
+
# "permalink": "1-konata-izumi"}
|
|
59
|
+
#
|
|
60
|
+
# To include associations, use <tt>:include</tt>.
|
|
61
|
+
#
|
|
62
|
+
# konata.to_json(:include => :posts)
|
|
63
|
+
# # => {"id": 1, "name": "Konata Izumi", "age": 16,
|
|
64
|
+
# "created_at": "2006/08/01", "awesome": true,
|
|
65
|
+
# "posts": [{"id": 1, "author_id": 1, "title": "Welcome to the weblog"},
|
|
66
|
+
# {"id": 2, author_id: 1, "title": "So I was thinking"}]}
|
|
67
|
+
#
|
|
68
|
+
# 2nd level and higher order associations work as well:
|
|
69
|
+
#
|
|
70
|
+
# konata.to_json(:include => { :posts => {
|
|
71
|
+
# :include => { :comments => {
|
|
72
|
+
# :only => :body } },
|
|
73
|
+
# :only => :title } })
|
|
74
|
+
# # => {"id": 1, "name": "Konata Izumi", "age": 16,
|
|
75
|
+
# "created_at": "2006/08/01", "awesome": true,
|
|
76
|
+
# "posts": [{"comments": [{"body": "1st post!"}, {"body": "Second!"}],
|
|
77
|
+
# "title": "Welcome to the weblog"},
|
|
78
|
+
# {"comments": [{"body": "Don't think too hard"}],
|
|
79
|
+
# "title": "So I was thinking"}]}
|
|
80
|
+
def encode_json(encoder)
|
|
81
|
+
hash = serializable_hash(encoder.options)
|
|
82
|
+
hash = { self.class.model_name.element => hash } if include_root_in_json
|
|
83
|
+
ActiveSupport::JSON.encode(hash)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def as_json(options = nil)
|
|
87
|
+
self
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def from_json(json)
|
|
91
|
+
self.attributes = ActiveSupport::JSON.decode(json)
|
|
92
|
+
self
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
require 'active_support/core_ext/class/attribute_accessors'
|
|
2
|
+
require 'active_support/core_ext/hash/conversions'
|
|
3
|
+
|
|
4
|
+
module ActiveModel
|
|
5
|
+
module Serializers
|
|
6
|
+
module Xml
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
include ActiveModel::Serialization
|
|
9
|
+
|
|
10
|
+
class Serializer #:nodoc:
|
|
11
|
+
class Attribute #:nodoc:
|
|
12
|
+
attr_reader :name, :value, :type
|
|
13
|
+
|
|
14
|
+
def initialize(name, serializable)
|
|
15
|
+
@name, @serializable = name, serializable
|
|
16
|
+
@type = compute_type
|
|
17
|
+
@value = compute_value
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# There is a significant speed improvement if the value
|
|
21
|
+
# does not need to be escaped, as <tt>tag!</tt> escapes all values
|
|
22
|
+
# to ensure that valid XML is generated. For known binary
|
|
23
|
+
# values, it is at least an order of magnitude faster to
|
|
24
|
+
# Base64 encode binary values and directly put them in the
|
|
25
|
+
# output XML than to pass the original value or the Base64
|
|
26
|
+
# encoded value to the <tt>tag!</tt> method. It definitely makes
|
|
27
|
+
# no sense to Base64 encode the value and then give it to
|
|
28
|
+
# <tt>tag!</tt>, since that just adds additional overhead.
|
|
29
|
+
def needs_encoding?
|
|
30
|
+
![ :binary, :date, :datetime, :boolean, :float, :integer ].include?(type)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def decorations(include_types = true)
|
|
34
|
+
decorations = {}
|
|
35
|
+
|
|
36
|
+
if type == :binary
|
|
37
|
+
decorations[:encoding] = 'base64'
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
if include_types && type != :string
|
|
41
|
+
decorations[:type] = type
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
if value.nil?
|
|
45
|
+
decorations[:nil] = true
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
decorations
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
protected
|
|
52
|
+
def compute_type
|
|
53
|
+
value = @serializable.send(name)
|
|
54
|
+
type = Hash::XML_TYPE_NAMES[value.class.name]
|
|
55
|
+
type ||= :string if value.respond_to?(:to_str)
|
|
56
|
+
type ||= :yaml
|
|
57
|
+
type
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def compute_value
|
|
61
|
+
value = @serializable.send(name)
|
|
62
|
+
|
|
63
|
+
if formatter = Hash::XML_FORMATTING[type.to_s]
|
|
64
|
+
value ? formatter.call(value) : nil
|
|
65
|
+
else
|
|
66
|
+
value
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
class MethodAttribute < Attribute #:nodoc:
|
|
72
|
+
protected
|
|
73
|
+
def compute_type
|
|
74
|
+
Hash::XML_TYPE_NAMES[@serializable.send(name).class.name] || :string
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
attr_reader :options
|
|
79
|
+
|
|
80
|
+
def initialize(serializable, options = nil)
|
|
81
|
+
@serializable = serializable
|
|
82
|
+
@options = options ? options.dup : {}
|
|
83
|
+
|
|
84
|
+
@options[:only] = Array.wrap(@options[:only]).map { |n| n.to_s }
|
|
85
|
+
@options[:except] = Array.wrap(@options[:except]).map { |n| n.to_s }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# To replicate the behavior in ActiveRecord#attributes,
|
|
89
|
+
# <tt>:except</tt> takes precedence over <tt>:only</tt>. If <tt>:only</tt> is not set
|
|
90
|
+
# for a N level model but is set for the N+1 level models,
|
|
91
|
+
# then because <tt>:except</tt> is set to a default value, the second
|
|
92
|
+
# level model can have both <tt>:except</tt> and <tt>:only</tt> set. So if
|
|
93
|
+
# <tt>:only</tt> is set, always delete <tt>:except</tt>.
|
|
94
|
+
def serializable_attribute_names
|
|
95
|
+
attribute_names = @serializable.attributes.keys.sort
|
|
96
|
+
|
|
97
|
+
if options[:only].any?
|
|
98
|
+
attribute_names &= options[:only]
|
|
99
|
+
elsif options[:except].any?
|
|
100
|
+
attribute_names -= options[:except]
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
attribute_names
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def serializable_attributes
|
|
107
|
+
serializable_attribute_names.collect { |name| Attribute.new(name, @serializable) }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def serializable_method_attributes
|
|
111
|
+
Array(options[:methods]).inject([]) do |methods, name|
|
|
112
|
+
methods << MethodAttribute.new(name.to_s, @serializable) if @serializable.respond_to?(name.to_s)
|
|
113
|
+
methods
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def serialize
|
|
118
|
+
args = [root]
|
|
119
|
+
|
|
120
|
+
if options[:namespace]
|
|
121
|
+
args << {:xmlns => options[:namespace]}
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
if options[:type]
|
|
125
|
+
args << {:type => options[:type]}
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
builder.tag!(*args) do
|
|
129
|
+
add_attributes
|
|
130
|
+
procs = options.delete(:procs)
|
|
131
|
+
options[:procs] = procs
|
|
132
|
+
add_procs
|
|
133
|
+
yield builder if block_given?
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
private
|
|
138
|
+
def builder
|
|
139
|
+
@builder ||= begin
|
|
140
|
+
require 'builder' unless defined? ::Builder
|
|
141
|
+
options[:indent] ||= 2
|
|
142
|
+
builder = options[:builder] ||= ::Builder::XmlMarkup.new(:indent => options[:indent])
|
|
143
|
+
|
|
144
|
+
unless options[:skip_instruct]
|
|
145
|
+
builder.instruct!
|
|
146
|
+
options[:skip_instruct] = true
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
builder
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def root
|
|
154
|
+
root = (options[:root] || @serializable.class.model_name.singular).to_s
|
|
155
|
+
reformat_name(root)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def dasherize?
|
|
159
|
+
!options.has_key?(:dasherize) || options[:dasherize]
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def camelize?
|
|
163
|
+
options.has_key?(:camelize) && options[:camelize]
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def reformat_name(name)
|
|
167
|
+
name = name.camelize if camelize?
|
|
168
|
+
dasherize? ? name.dasherize : name
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def add_attributes
|
|
172
|
+
(serializable_attributes + serializable_method_attributes).each do |attribute|
|
|
173
|
+
builder.tag!(
|
|
174
|
+
reformat_name(attribute.name),
|
|
175
|
+
attribute.value.to_s,
|
|
176
|
+
attribute.decorations(!options[:skip_types])
|
|
177
|
+
)
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def add_procs
|
|
182
|
+
if procs = options.delete(:procs)
|
|
183
|
+
[ *procs ].each do |proc|
|
|
184
|
+
if proc.arity > 1
|
|
185
|
+
proc.call(options, @serializable)
|
|
186
|
+
else
|
|
187
|
+
proc.call(options)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def to_xml(options = {}, &block)
|
|
195
|
+
Serializer.new(self, options).serialize(&block)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def from_xml(xml)
|
|
199
|
+
self.attributes = Hash.from_xml(xml).values.first
|
|
200
|
+
self
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module ActiveModel
|
|
2
|
+
module StateMachine
|
|
3
|
+
class Event
|
|
4
|
+
attr_reader :name, :success
|
|
5
|
+
|
|
6
|
+
def initialize(machine, name, options = {}, &block)
|
|
7
|
+
@machine, @name, @transitions = machine, name, []
|
|
8
|
+
if machine
|
|
9
|
+
machine.klass.send(:define_method, "#{name}!") do |*args|
|
|
10
|
+
machine.fire_event(name, self, true, *args)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
machine.klass.send(:define_method, name.to_s) do |*args|
|
|
14
|
+
machine.fire_event(name, self, false, *args)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
update(options, &block)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def fire(obj, to_state = nil, *args)
|
|
21
|
+
transitions = @transitions.select { |t| t.from == obj.current_state(@machine ? @machine.name : nil) }
|
|
22
|
+
raise InvalidTransition if transitions.size == 0
|
|
23
|
+
|
|
24
|
+
next_state = nil
|
|
25
|
+
transitions.each do |transition|
|
|
26
|
+
next if to_state && !Array(transition.to).include?(to_state)
|
|
27
|
+
if transition.perform(obj)
|
|
28
|
+
next_state = to_state || Array(transition.to).first
|
|
29
|
+
transition.execute(obj, *args)
|
|
30
|
+
break
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
next_state
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def transitions_from_state?(state)
|
|
37
|
+
@transitions.any? { |t| t.from? state }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def ==(event)
|
|
41
|
+
if event.is_a? Symbol
|
|
42
|
+
name == event
|
|
43
|
+
else
|
|
44
|
+
name == event.name
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def update(options = {}, &block)
|
|
49
|
+
if options.key?(:success) then @success = options[:success] end
|
|
50
|
+
if block then instance_eval(&block) end
|
|
51
|
+
self
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
def transitions(trans_opts)
|
|
56
|
+
Array(trans_opts[:from]).each do |s|
|
|
57
|
+
@transitions << StateTransition.new(trans_opts.merge({:from => s.to_sym}))
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
module ActiveModel
|
|
2
|
+
module StateMachine
|
|
3
|
+
class Machine
|
|
4
|
+
attr_writer :initial_state
|
|
5
|
+
attr_accessor :states, :events, :state_index
|
|
6
|
+
attr_reader :klass, :name
|
|
7
|
+
|
|
8
|
+
def initialize(klass, name, options = {}, &block)
|
|
9
|
+
@klass, @name, @states, @state_index, @events = klass, name, [], {}, {}
|
|
10
|
+
update(options, &block)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def initial_state
|
|
14
|
+
@initial_state ||= (states.first ? states.first.name : nil)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def update(options = {}, &block)
|
|
18
|
+
if options.key?(:initial) then @initial_state = options[:initial] end
|
|
19
|
+
if block then instance_eval(&block) end
|
|
20
|
+
self
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def fire_event(event, record, persist, *args)
|
|
24
|
+
state_index[record.current_state(@name)].call_action(:exit, record)
|
|
25
|
+
if new_state = @events[event].fire(record, *args)
|
|
26
|
+
state_index[new_state].call_action(:enter, record)
|
|
27
|
+
|
|
28
|
+
if record.respond_to?(event_fired_callback)
|
|
29
|
+
record.send(event_fired_callback, record.current_state, new_state)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
record.current_state(@name, new_state, persist)
|
|
33
|
+
record.send(@events[event].success) if @events[event].success
|
|
34
|
+
true
|
|
35
|
+
else
|
|
36
|
+
if record.respond_to?(event_failed_callback)
|
|
37
|
+
record.send(event_failed_callback, event)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
false
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def states_for_select
|
|
45
|
+
states.map { |st| [st.display_name, st.name.to_s] }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def events_for(state)
|
|
49
|
+
events = @events.values.select { |event| event.transitions_from_state?(state) }
|
|
50
|
+
events.map! { |event| event.name }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def current_state_variable
|
|
54
|
+
"@#{@name}_current_state"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
def state(name, options = {})
|
|
59
|
+
@states << (state_index[name] ||= State.new(name, :machine => self)).update(options)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def event(name, options = {}, &block)
|
|
63
|
+
(@events[name] ||= Event.new(self, name)).update(options, &block)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def event_fired_callback
|
|
67
|
+
@event_fired_callback ||= (@name == :default ? '' : "#{@name}_") + 'event_fired'
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def event_failed_callback
|
|
71
|
+
@event_failed_callback ||= (@name == :default ? '' : "#{@name}_") + 'event_failed'
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
module ActiveModel
|
|
2
|
+
module StateMachine
|
|
3
|
+
class State
|
|
4
|
+
attr_reader :name, :options
|
|
5
|
+
|
|
6
|
+
def initialize(name, options = {})
|
|
7
|
+
@name = name
|
|
8
|
+
if machine = options.delete(:machine)
|
|
9
|
+
machine.klass.define_state_query_method(name)
|
|
10
|
+
end
|
|
11
|
+
update(options)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def ==(state)
|
|
15
|
+
if state.is_a? Symbol
|
|
16
|
+
name == state
|
|
17
|
+
else
|
|
18
|
+
name == state.name
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def call_action(action, record)
|
|
23
|
+
action = @options[action]
|
|
24
|
+
case action
|
|
25
|
+
when Symbol, String
|
|
26
|
+
record.send(action)
|
|
27
|
+
when Proc
|
|
28
|
+
action.call(record)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def display_name
|
|
33
|
+
@display_name ||= name.to_s.gsub(/_/, ' ').capitalize
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def for_select
|
|
37
|
+
[display_name, name.to_s]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def update(options = {})
|
|
41
|
+
if options.key?(:display) then @display_name = options.delete(:display) end
|
|
42
|
+
@options = options
|
|
43
|
+
self
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module ActiveModel
|
|
2
|
+
module StateMachine
|
|
3
|
+
class StateTransition
|
|
4
|
+
attr_reader :from, :to, :options
|
|
5
|
+
|
|
6
|
+
def initialize(opts)
|
|
7
|
+
@from, @to, @guard, @on_transition = opts[:from], opts[:to], opts[:guard], opts[:on_transition]
|
|
8
|
+
@options = opts
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def perform(obj)
|
|
12
|
+
case @guard
|
|
13
|
+
when Symbol, String
|
|
14
|
+
obj.send(@guard)
|
|
15
|
+
when Proc
|
|
16
|
+
@guard.call(obj)
|
|
17
|
+
else
|
|
18
|
+
true
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def execute(obj, *args)
|
|
23
|
+
case @on_transition
|
|
24
|
+
when Symbol, String
|
|
25
|
+
obj.send(@on_transition, *args)
|
|
26
|
+
when Proc
|
|
27
|
+
@on_transition.call(obj, *args)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def ==(obj)
|
|
32
|
+
@from == obj.from && @to == obj.to
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def from?(value)
|
|
36
|
+
@from == value
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
module ActiveModel
|
|
2
|
+
module StateMachine
|
|
3
|
+
autoload :Event, 'active_model/state_machine/event'
|
|
4
|
+
autoload :Machine, 'active_model/state_machine/machine'
|
|
5
|
+
autoload :State, 'active_model/state_machine/state'
|
|
6
|
+
autoload :StateTransition, 'active_model/state_machine/state_transition'
|
|
7
|
+
|
|
8
|
+
extend ActiveSupport::Concern
|
|
9
|
+
|
|
10
|
+
class InvalidTransition < Exception
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module ClassMethods
|
|
14
|
+
def inherited(klass)
|
|
15
|
+
super
|
|
16
|
+
klass.state_machines = state_machines
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def state_machines
|
|
20
|
+
@state_machines ||= {}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def state_machines=(value)
|
|
24
|
+
@state_machines = value ? value.dup : nil
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def state_machine(name = nil, options = {}, &block)
|
|
28
|
+
if name.is_a?(Hash)
|
|
29
|
+
options = name
|
|
30
|
+
name = nil
|
|
31
|
+
end
|
|
32
|
+
name ||= :default
|
|
33
|
+
state_machines[name] ||= Machine.new(self, name)
|
|
34
|
+
block ? state_machines[name].update(options, &block) : state_machines[name]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def define_state_query_method(state_name)
|
|
38
|
+
name = "#{state_name}?"
|
|
39
|
+
undef_method(name) if method_defined?(name)
|
|
40
|
+
class_eval "def #{name}; current_state.to_s == %(#{state_name}) end"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def current_state(name = nil, new_state = nil, persist = false)
|
|
45
|
+
sm = self.class.state_machine(name)
|
|
46
|
+
ivar = sm.current_state_variable
|
|
47
|
+
if name && new_state
|
|
48
|
+
if persist && respond_to?(:write_state)
|
|
49
|
+
write_state(sm, new_state)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
if respond_to?(:write_state_without_persistence)
|
|
53
|
+
write_state_without_persistence(sm, new_state)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
instance_variable_set(ivar, new_state)
|
|
57
|
+
else
|
|
58
|
+
instance_variable_set(ivar, nil) unless instance_variable_defined?(ivar)
|
|
59
|
+
value = instance_variable_get(ivar)
|
|
60
|
+
return value if value
|
|
61
|
+
|
|
62
|
+
if respond_to?(:read_state)
|
|
63
|
+
value = instance_variable_set(ivar, read_state(sm))
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
value || sm.initial_state
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require "active_support/test_case"
|
|
2
|
+
|
|
3
|
+
module ActiveModel #:nodoc:
|
|
4
|
+
class TestCase < ActiveSupport::TestCase #:nodoc:
|
|
5
|
+
def with_kcode(kcode)
|
|
6
|
+
if RUBY_VERSION < '1.9'
|
|
7
|
+
orig_kcode, $KCODE = $KCODE, kcode
|
|
8
|
+
begin
|
|
9
|
+
yield
|
|
10
|
+
ensure
|
|
11
|
+
$KCODE = orig_kcode
|
|
12
|
+
end
|
|
13
|
+
else
|
|
14
|
+
yield
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
require 'active_support/core_ext/hash/reverse_merge'
|
|
2
|
+
|
|
3
|
+
module ActiveModel
|
|
4
|
+
module Translation
|
|
5
|
+
include ActiveModel::Naming
|
|
6
|
+
|
|
7
|
+
# Returns the i18n_scope for the class. Overwrite if you want custom lookup.
|
|
8
|
+
def i18n_scope
|
|
9
|
+
:activemodel
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# When localizing a string, goes through the lookup returned by this method.
|
|
13
|
+
# Used in ActiveModel::Name#human, ActiveModel::Errors#full_messages and
|
|
14
|
+
# ActiveModel::Translation#human_attribute_name.
|
|
15
|
+
def lookup_ancestors
|
|
16
|
+
self.ancestors.select { |x| x.respond_to?(:model_name) }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Transforms attributes names into a more human format, such as "First name" instead of "first_name".
|
|
20
|
+
#
|
|
21
|
+
# Example:
|
|
22
|
+
#
|
|
23
|
+
# Person.human_attribute_name("first_name") # => "First name"
|
|
24
|
+
#
|
|
25
|
+
# Specify +options+ with additional translating options.
|
|
26
|
+
def human_attribute_name(attribute, options = {})
|
|
27
|
+
defaults = lookup_ancestors.map do |klass|
|
|
28
|
+
:"#{klass.model_name.underscore}.#{attribute}"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
defaults << options.delete(:default) if options[:default]
|
|
32
|
+
defaults << attribute.to_s.humanize
|
|
33
|
+
|
|
34
|
+
options.reverse_merge! :scope => [self.i18n_scope, :attributes], :count => 1, :default => defaults
|
|
35
|
+
I18n.translate(defaults.shift, options)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Model.human_name is deprecated. Use Model.model_name.human instead.
|
|
39
|
+
def human_name(*args)
|
|
40
|
+
ActiveSupport::Deprecation.warn("human_name has been deprecated, please use model_name.human instead", caller[0,5])
|
|
41
|
+
model_name.human(*args)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|