seomoz-ripple 1.0.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +20 -0
- data/Guardfile +15 -0
- data/Rakefile +88 -0
- data/lib/rails/generators/ripple/configuration/configuration_generator.rb +13 -0
- data/lib/rails/generators/ripple/configuration/templates/ripple.yml +24 -0
- data/lib/rails/generators/ripple/js/js_generator.rb +13 -0
- data/lib/rails/generators/ripple/js/templates/js/contrib.js +63 -0
- data/lib/rails/generators/ripple/js/templates/js/iso8601.js +76 -0
- data/lib/rails/generators/ripple/js/templates/js/ripple.js +132 -0
- data/lib/rails/generators/ripple/model/model_generator.rb +20 -0
- data/lib/rails/generators/ripple/model/templates/model.rb +10 -0
- data/lib/rails/generators/ripple/observer/observer_generator.rb +16 -0
- data/lib/rails/generators/ripple/observer/templates/observer.rb +4 -0
- data/lib/rails/generators/ripple/test/templates/test_server.rb +46 -0
- data/lib/rails/generators/ripple/test/test_generator.rb +39 -0
- data/lib/rails/generators/ripple_generator.rb +78 -0
- data/lib/ripple.rb +79 -0
- data/lib/ripple/associations.rb +356 -0
- data/lib/ripple/associations/embedded.rb +35 -0
- data/lib/ripple/associations/instantiators.rb +26 -0
- data/lib/ripple/associations/linked.rb +65 -0
- data/lib/ripple/associations/many.rb +38 -0
- data/lib/ripple/associations/many_embedded_proxy.rb +38 -0
- data/lib/ripple/associations/many_linked_proxy.rb +66 -0
- data/lib/ripple/associations/many_reference_proxy.rb +93 -0
- data/lib/ripple/associations/many_stored_key_proxy.rb +76 -0
- data/lib/ripple/associations/one.rb +20 -0
- data/lib/ripple/associations/one_embedded_proxy.rb +35 -0
- data/lib/ripple/associations/one_key_proxy.rb +58 -0
- data/lib/ripple/associations/one_linked_proxy.rb +22 -0
- data/lib/ripple/associations/one_stored_key_proxy.rb +43 -0
- data/lib/ripple/associations/proxy.rb +118 -0
- data/lib/ripple/attribute_methods.rb +118 -0
- data/lib/ripple/attribute_methods/dirty.rb +50 -0
- data/lib/ripple/attribute_methods/query.rb +34 -0
- data/lib/ripple/attribute_methods/read.rb +26 -0
- data/lib/ripple/attribute_methods/write.rb +25 -0
- data/lib/ripple/callbacks.rb +74 -0
- data/lib/ripple/conflict/basic_resolver.rb +82 -0
- data/lib/ripple/conflict/document_hooks.rb +20 -0
- data/lib/ripple/conflict/resolver.rb +71 -0
- data/lib/ripple/conflict/test_helper.rb +33 -0
- data/lib/ripple/conversion.rb +28 -0
- data/lib/ripple/core_ext.rb +2 -0
- data/lib/ripple/core_ext/casting.rb +148 -0
- data/lib/ripple/core_ext/object.rb +8 -0
- data/lib/ripple/document.rb +104 -0
- data/lib/ripple/document/bucket_access.rb +25 -0
- data/lib/ripple/document/finders.rb +131 -0
- data/lib/ripple/document/key.rb +43 -0
- data/lib/ripple/document/link.rb +30 -0
- data/lib/ripple/document/persistence.rb +115 -0
- data/lib/ripple/embedded_document.rb +64 -0
- data/lib/ripple/embedded_document/around_callbacks.rb +18 -0
- data/lib/ripple/embedded_document/finders.rb +26 -0
- data/lib/ripple/embedded_document/persistence.rb +77 -0
- data/lib/ripple/i18n.rb +2 -0
- data/lib/ripple/inspection.rb +32 -0
- data/lib/ripple/locale/en.yml +21 -0
- data/lib/ripple/nested_attributes.rb +265 -0
- data/lib/ripple/observable.rb +28 -0
- data/lib/ripple/properties.rb +73 -0
- data/lib/ripple/property_type_mismatch.rb +12 -0
- data/lib/ripple/railtie.rb +13 -0
- data/lib/ripple/serialization.rb +84 -0
- data/lib/ripple/timestamps.rb +27 -0
- data/lib/ripple/translation.rb +14 -0
- data/lib/ripple/validations.rb +67 -0
- data/lib/ripple/validations/associated_validator.rb +43 -0
- data/ripple.gemspec +46 -0
- data/seomoz-ripple.gemspec +46 -0
- data/spec/fixtures/config.yml +8 -0
- data/spec/integration/ripple/associations_spec.rb +220 -0
- data/spec/integration/ripple/conflict_resolution_spec.rb +293 -0
- data/spec/integration/ripple/nested_attributes_spec.rb +264 -0
- data/spec/integration/ripple/persistence_spec.rb +57 -0
- data/spec/integration/ripple/search_associations_spec.rb +42 -0
- data/spec/ripple/associations/many_embedded_proxy_spec.rb +122 -0
- data/spec/ripple/associations/many_linked_proxy_spec.rb +191 -0
- data/spec/ripple/associations/many_reference_proxy_spec.rb +170 -0
- data/spec/ripple/associations/many_stored_key_proxy_spec.rb +158 -0
- data/spec/ripple/associations/one_embedded_proxy_spec.rb +125 -0
- data/spec/ripple/associations/one_key_proxy_spec.rb +82 -0
- data/spec/ripple/associations/one_linked_proxy_spec.rb +91 -0
- data/spec/ripple/associations/one_stored_key_proxy_spec.rb +72 -0
- data/spec/ripple/associations/proxy_spec.rb +84 -0
- data/spec/ripple/associations_spec.rb +129 -0
- data/spec/ripple/attribute_methods/dirty_spec.rb +80 -0
- data/spec/ripple/attribute_methods_spec.rb +230 -0
- data/spec/ripple/bucket_access_spec.rb +25 -0
- data/spec/ripple/callbacks_spec.rb +176 -0
- data/spec/ripple/conflict/resolver_spec.rb +42 -0
- data/spec/ripple/conversion_spec.rb +22 -0
- data/spec/ripple/core_ext_spec.rb +103 -0
- data/spec/ripple/document/link_spec.rb +67 -0
- data/spec/ripple/document_spec.rb +96 -0
- data/spec/ripple/embedded_document/finders_spec.rb +29 -0
- data/spec/ripple/embedded_document/persistence_spec.rb +80 -0
- data/spec/ripple/embedded_document_spec.rb +84 -0
- data/spec/ripple/finders_spec.rb +217 -0
- data/spec/ripple/inspection_spec.rb +51 -0
- data/spec/ripple/key_spec.rb +30 -0
- data/spec/ripple/observable_spec.rb +121 -0
- data/spec/ripple/persistence_spec.rb +326 -0
- data/spec/ripple/properties_spec.rb +262 -0
- data/spec/ripple/ripple_spec.rb +71 -0
- data/spec/ripple/serialization_spec.rb +51 -0
- data/spec/ripple/timestamps_spec.rb +76 -0
- data/spec/ripple/validations/associated_validator_spec.rb +77 -0
- data/spec/ripple/validations_spec.rb +104 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/associations.rb +1 -0
- data/spec/support/associations/proxies.rb +17 -0
- data/spec/support/integration_setup.rb +11 -0
- data/spec/support/mocks.rb +4 -0
- data/spec/support/models.rb +4 -0
- data/spec/support/models/address.rb +12 -0
- data/spec/support/models/box.rb +13 -0
- data/spec/support/models/car.rb +16 -0
- data/spec/support/models/cardboard_box.rb +3 -0
- data/spec/support/models/clock.rb +12 -0
- data/spec/support/models/clock_observer.rb +3 -0
- data/spec/support/models/company.rb +23 -0
- data/spec/support/models/customer.rb +4 -0
- data/spec/support/models/driver.rb +6 -0
- data/spec/support/models/email.rb +4 -0
- data/spec/support/models/engine.rb +5 -0
- data/spec/support/models/family.rb +16 -0
- data/spec/support/models/favorite.rb +4 -0
- data/spec/support/models/invoice.rb +7 -0
- data/spec/support/models/late_invoice.rb +3 -0
- data/spec/support/models/ninja.rb +9 -0
- data/spec/support/models/note.rb +5 -0
- data/spec/support/models/page.rb +4 -0
- data/spec/support/models/paid_invoice.rb +4 -0
- data/spec/support/models/passenger.rb +6 -0
- data/spec/support/models/profile.rb +10 -0
- data/spec/support/models/seat.rb +5 -0
- data/spec/support/models/subscription.rb +27 -0
- data/spec/support/models/tasks.rb +14 -0
- data/spec/support/models/team.rb +11 -0
- data/spec/support/models/transactions.rb +17 -0
- data/spec/support/models/tree.rb +4 -0
- data/spec/support/models/user.rb +10 -0
- data/spec/support/models/wheel.rb +6 -0
- data/spec/support/models/widget.rb +22 -0
- data/spec/support/test_server.rb +18 -0
- data/spec/support/test_server.yml.example +2 -0
- metadata +362 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module Ripple
|
4
|
+
module AttributeMethods
|
5
|
+
module Query
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
attribute_method_suffix "?"
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
# Based on code from ActiveRecord
|
14
|
+
def attribute?(attr_name)
|
15
|
+
unless value = attribute(attr_name)
|
16
|
+
false
|
17
|
+
else
|
18
|
+
prop = self.class.properties[attr_name]
|
19
|
+
if prop.nil?
|
20
|
+
if Numeric === value || value !~ /[^0-9]/
|
21
|
+
!value.to_i.zero?
|
22
|
+
else
|
23
|
+
Boolean.ripple_cast(value) || value.present?
|
24
|
+
end
|
25
|
+
elsif prop.type <= Numeric
|
26
|
+
!value.zero?
|
27
|
+
else
|
28
|
+
value.present?
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module Ripple
|
4
|
+
module AttributeMethods
|
5
|
+
module Read
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
attribute_method_suffix ""
|
10
|
+
end
|
11
|
+
|
12
|
+
def [](attr_name)
|
13
|
+
attribute(attr_name)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
def attribute(attr_name)
|
18
|
+
if @attributes.include?(attr_name)
|
19
|
+
@attributes[attr_name]
|
20
|
+
else
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module Ripple
|
4
|
+
module AttributeMethods
|
5
|
+
module Write
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
attribute_method_suffix "="
|
10
|
+
end
|
11
|
+
|
12
|
+
def []=(attr_name, value)
|
13
|
+
__send__(:attribute=, attr_name, value)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
def attribute=(attr_name, value)
|
18
|
+
if prop = self.class.properties[attr_name]
|
19
|
+
value = prop.type_cast(value)
|
20
|
+
end
|
21
|
+
@attributes[attr_name] = value
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'active_model/callbacks'
|
3
|
+
|
4
|
+
module Ripple
|
5
|
+
# Adds lifecycle callbacks to {Ripple::Document} models, in the typical
|
6
|
+
# ActiveModel fashion.
|
7
|
+
module Callbacks
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
CALLBACK_TYPES = [:create, :update, :save, :destroy, :validation]
|
11
|
+
|
12
|
+
included do
|
13
|
+
extend ActiveModel::Callbacks
|
14
|
+
define_model_callbacks *(CALLBACK_TYPES - [:validation])
|
15
|
+
define_callbacks :validation, :terminator => "result == false", :scope => [:kind, :name]
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
# Defines a callback to be run before validations.
|
20
|
+
def before_validation(*args, &block)
|
21
|
+
options = args.last
|
22
|
+
if options.is_a?(Hash) && options[:on]
|
23
|
+
options[:if] = Array(options[:if])
|
24
|
+
options[:if] << "@_on_validate == :#{options[:on]}"
|
25
|
+
end
|
26
|
+
set_callback(:validation, :before, *args, &block)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Defines a callback to be run after validations.
|
30
|
+
def after_validation(*args, &block)
|
31
|
+
options = args.extract_options!
|
32
|
+
options[:prepend] = true
|
33
|
+
options[:if] = Array(options[:if])
|
34
|
+
options[:if] << "!halted && value != false"
|
35
|
+
options[:if] << "@_on_validate == :#{options[:on]}" if options[:on]
|
36
|
+
set_callback(:validation, :after, *(args << options), &block)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# @private
|
41
|
+
module InstanceMethods
|
42
|
+
# @private
|
43
|
+
def really_save(*args, &block)
|
44
|
+
run_save_callbacks do
|
45
|
+
super
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def run_save_callbacks
|
50
|
+
state = new? ? :create : :update
|
51
|
+
run_callbacks(:save) do
|
52
|
+
run_callbacks(state) do
|
53
|
+
yield
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# @private
|
59
|
+
def destroy(*args, &block)
|
60
|
+
run_callbacks(:destroy) do
|
61
|
+
super
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# @private
|
66
|
+
def valid?(*args, &block)
|
67
|
+
@_on_validate = new? ? :create : :update
|
68
|
+
run_callbacks(:validation) do
|
69
|
+
super
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Ripple
|
2
|
+
module Conflict
|
3
|
+
class BasicResolver
|
4
|
+
delegate :model_class, :document, :siblings, :to => :@main_resolver
|
5
|
+
|
6
|
+
def initialize(main_resolver)
|
7
|
+
@main_resolver = main_resolver
|
8
|
+
end
|
9
|
+
|
10
|
+
def remaining_conflicts
|
11
|
+
@remaining_conflicts ||= []
|
12
|
+
end
|
13
|
+
|
14
|
+
def unexpected_conflicts
|
15
|
+
# if the user didn't specify the conflict they expect,
|
16
|
+
# then don't consider any conflicts unexpected
|
17
|
+
return [] if model_class.expected_conflicts.blank?
|
18
|
+
|
19
|
+
remaining_conflicts - model_class.expected_conflicts
|
20
|
+
end
|
21
|
+
|
22
|
+
def perform
|
23
|
+
process_properties
|
24
|
+
process_embedded_associations
|
25
|
+
process_linked_associations
|
26
|
+
process_stored_key_associations
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def process_properties
|
32
|
+
model_class.properties.each do |name, property|
|
33
|
+
document.send(:"#{name}=", resolved_property_value_for(property))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def process_embedded_associations
|
38
|
+
model_class.embedded_associations.each do |assoc|
|
39
|
+
document.send(:"#{assoc.name}=", resolved_association_value_for(assoc, :load_target))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def process_linked_associations
|
44
|
+
model_class.linked_associations.each do |assoc|
|
45
|
+
document.send(assoc.name).replace_links(resolved_association_value_for(assoc, :links))
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def process_stored_key_associations
|
50
|
+
model_class.stored_key_associations.each do |assoc|
|
51
|
+
document.send(assoc.name).reset_owner_keys if (assoc.type == :many)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def resolved_property_value_for(property)
|
56
|
+
uniq_values = siblings.map(&property.key).uniq
|
57
|
+
|
58
|
+
value = if uniq_values.size == 1
|
59
|
+
uniq_values.first
|
60
|
+
elsif property.key == :updated_at
|
61
|
+
uniq_values.compact.max
|
62
|
+
elsif property.key == :created_at
|
63
|
+
uniq_values.compact.min
|
64
|
+
else
|
65
|
+
remaining_conflicts << property.key
|
66
|
+
property.default
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def resolved_association_value_for(association, proxy_value_method)
|
71
|
+
# the association proxy doesn't uniquify well, so we have to use the target or links directly
|
72
|
+
uniq_values = siblings.map { |s| s.send(association.name).__send__(proxy_value_method) }.uniq
|
73
|
+
|
74
|
+
return uniq_values.first if uniq_values.size == 1
|
75
|
+
remaining_conflicts << association.name
|
76
|
+
|
77
|
+
association.many? ? [] : nil # default value
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Ripple
|
2
|
+
module Conflict
|
3
|
+
module DocumentHooks
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
attr_reader :on_conflict_block
|
8
|
+
|
9
|
+
def on_conflict(*expected_conflicts, &block)
|
10
|
+
@expected_conflicts = expected_conflicts
|
11
|
+
@on_conflict_block = block
|
12
|
+
end
|
13
|
+
|
14
|
+
def expected_conflicts
|
15
|
+
@expected_conflicts ||= []
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'ripple/conflict/basic_resolver'
|
2
|
+
|
3
|
+
module Ripple
|
4
|
+
module Conflict
|
5
|
+
class Resolver
|
6
|
+
include Translation
|
7
|
+
|
8
|
+
attr_reader :document, :model_class
|
9
|
+
|
10
|
+
delegate :expected_conflicts, :on_conflict_block, :to => :model_class
|
11
|
+
|
12
|
+
def self.to_proc
|
13
|
+
@to_proc ||= lambda do |robject|
|
14
|
+
possible_model_classes = robject.siblings.map { |s| s.data['_type'] }.uniq
|
15
|
+
return nil unless possible_model_classes.size == 1
|
16
|
+
|
17
|
+
resolver = new(robject, possible_model_classes.first.constantize)
|
18
|
+
resolver.resolve
|
19
|
+
resolver.document.robject
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
Riak::RObject.on_conflict(&self)
|
24
|
+
|
25
|
+
def initialize(robject, model_class)
|
26
|
+
@robject = robject
|
27
|
+
@model_class = model_class
|
28
|
+
end
|
29
|
+
|
30
|
+
def resolve
|
31
|
+
assert_conflict_block
|
32
|
+
basic_resolver.perform
|
33
|
+
assert_no_unexpected_conflicts
|
34
|
+
document.instance_exec(siblings, basic_resolver.remaining_conflicts, &on_conflict_block)
|
35
|
+
document.update_robject
|
36
|
+
end
|
37
|
+
|
38
|
+
def siblings
|
39
|
+
@siblings ||= @robject.siblings.map { |s| @model_class.send(:instantiate, s) }
|
40
|
+
end
|
41
|
+
|
42
|
+
def document
|
43
|
+
# pick a sibling robject to use as the basis of the document to resolve
|
44
|
+
# which one doesn't really matter.
|
45
|
+
@document ||= @model_class.send(:instantiate, @robject.siblings.first.dup)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def basic_resolver
|
51
|
+
@basic_resolver ||= BasicResolver.new(self)
|
52
|
+
end
|
53
|
+
|
54
|
+
def assert_conflict_block
|
55
|
+
return if on_conflict_block
|
56
|
+
|
57
|
+
raise NotImplementedError, t('conflict_handler_not_implemented', :document => document)
|
58
|
+
end
|
59
|
+
|
60
|
+
def assert_no_unexpected_conflicts
|
61
|
+
return unless basic_resolver.unexpected_conflicts.any?
|
62
|
+
|
63
|
+
raise NotImplementedError, t('unexpected_conflicts',
|
64
|
+
:conflicts => basic_resolver.unexpected_conflicts.inspect,
|
65
|
+
:document => document.inspect
|
66
|
+
)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Ripple
|
2
|
+
module Conflict
|
3
|
+
module TestHelper
|
4
|
+
def create_conflict(main_record, *modifiers)
|
5
|
+
# We have to disable all on conflict resolvers while we create conflict
|
6
|
+
# so that they don't auto-resolve it.
|
7
|
+
orig_hooks = Riak::RObject.on_conflict_hooks.dup
|
8
|
+
Riak::RObject.on_conflict_hooks.clear
|
9
|
+
|
10
|
+
begin
|
11
|
+
klass, key = main_record.class, main_record.key
|
12
|
+
records = modifiers.map { |_| klass.find!(key) }
|
13
|
+
|
14
|
+
records.zip(modifiers).each do |(record, modifier)|
|
15
|
+
# necessary to get conflict, so riak thinks they are being saved by different clients
|
16
|
+
Ripple.client.client_id += 1
|
17
|
+
|
18
|
+
modifier.call(record)
|
19
|
+
record.save!
|
20
|
+
end
|
21
|
+
|
22
|
+
robject = klass.bucket.get(key)
|
23
|
+
raise "#{robject} is not in conflict as expected." unless robject.conflict?
|
24
|
+
ensure
|
25
|
+
orig_hooks.each do |hook|
|
26
|
+
Riak::RObject.on_conflict(&hook)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'active_model/conversion'
|
2
|
+
|
3
|
+
module Ripple
|
4
|
+
# Provides ActionPack compatibility for {Ripple::Document} models.
|
5
|
+
module Conversion
|
6
|
+
include ActiveModel::Conversion
|
7
|
+
|
8
|
+
# True if this is a new document
|
9
|
+
def new_record?
|
10
|
+
new?
|
11
|
+
end
|
12
|
+
|
13
|
+
# True if this is not a new document
|
14
|
+
def persisted?
|
15
|
+
!new?
|
16
|
+
end
|
17
|
+
|
18
|
+
# Converts to a view key
|
19
|
+
def to_key
|
20
|
+
new? ? nil : [key]
|
21
|
+
end
|
22
|
+
|
23
|
+
# Converts to a URL parameter
|
24
|
+
def to_param
|
25
|
+
key
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
require 'active_support/json'
|
2
|
+
require 'active_support/core_ext/object/blank'
|
3
|
+
require 'active_support/core_ext/object/to_json'
|
4
|
+
require 'active_support/core_ext/date/conversions'
|
5
|
+
require 'active_support/core_ext/date/zones'
|
6
|
+
require 'active_support/core_ext/date_time/conversions'
|
7
|
+
require 'active_support/core_ext/date_time/zones'
|
8
|
+
require 'active_support/core_ext/time/conversions'
|
9
|
+
require 'active_support/core_ext/time/zones'
|
10
|
+
require 'active_support/core_ext/string/conversions'
|
11
|
+
require 'ripple/property_type_mismatch'
|
12
|
+
require 'set'
|
13
|
+
|
14
|
+
# @private
|
15
|
+
class Object
|
16
|
+
def self.ripple_cast(value)
|
17
|
+
value
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# @private
|
22
|
+
class Symbol
|
23
|
+
def self.ripple_cast(value)
|
24
|
+
return nil if value.blank?
|
25
|
+
value.respond_to?(:to_s) && value.to_s.intern or raise Ripple::PropertyTypeMismatch.new(self, value)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# @private
|
30
|
+
class Numeric
|
31
|
+
def self.ripple_cast(value)
|
32
|
+
return nil if value.blank?
|
33
|
+
raise Ripple::PropertyTypeMismatch.new(self,value) unless value.respond_to?(:to_i) && value.respond_to?(:to_f)
|
34
|
+
float_value = value.to_f
|
35
|
+
int_value = value.to_i
|
36
|
+
float_value == int_value ? int_value : float_value
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# @private
|
41
|
+
class Integer
|
42
|
+
def self.ripple_cast(value)
|
43
|
+
return nil if value.nil? || (String === value && value.blank?)
|
44
|
+
!value.is_a?(Symbol) && value.respond_to?(:to_i) && value.to_i or raise Ripple::PropertyTypeMismatch.new(self, value)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# @private
|
49
|
+
class Float
|
50
|
+
def self.ripple_cast(value)
|
51
|
+
return nil if value.nil? || (String === value && value.blank?)
|
52
|
+
value.respond_to?(:to_f) && value.to_f or raise Ripple::PropertyTypeMismatch.new(self, value)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# @private
|
57
|
+
class String
|
58
|
+
def self.ripple_cast(value)
|
59
|
+
return nil if value.nil?
|
60
|
+
value.respond_to?(:to_s) && value.to_s or raise Ripple::PropertyTypeMismatch.new(self, value)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
boolean_cast = proc do
|
65
|
+
def self.ripple_cast(value)
|
66
|
+
case value
|
67
|
+
when NilClass
|
68
|
+
nil
|
69
|
+
when Numeric
|
70
|
+
!value.zero?
|
71
|
+
when TrueClass, FalseClass
|
72
|
+
value
|
73
|
+
when /^\s*t/i
|
74
|
+
true
|
75
|
+
when /^\s*f/i
|
76
|
+
false
|
77
|
+
else
|
78
|
+
value.present?
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
unless defined?(::Boolean)
|
84
|
+
# Stand-in for true/false property types.
|
85
|
+
module ::Boolean; end
|
86
|
+
end
|
87
|
+
|
88
|
+
::Boolean.module_eval(&boolean_cast)
|
89
|
+
TrueClass.module_eval(&boolean_cast)
|
90
|
+
FalseClass.module_eval(&boolean_cast)
|
91
|
+
|
92
|
+
# @private
|
93
|
+
class Time
|
94
|
+
def as_json(options={})
|
95
|
+
self.utc.send(Ripple.date_format)
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.ripple_cast(value)
|
99
|
+
return nil if value.blank?
|
100
|
+
value.respond_to?(:to_time) && value.to_time or raise Ripple::PropertyTypeMismatch.new(self, value)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# @private
|
105
|
+
class Date
|
106
|
+
def as_json(options={})
|
107
|
+
self.to_s(Ripple.date_format)
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.ripple_cast(value)
|
111
|
+
return nil if value.blank?
|
112
|
+
value.respond_to?(:to_date) && value.to_date or raise Ripple::PropertyTypeMismatch.new(self, value)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# @private
|
117
|
+
class DateTime
|
118
|
+
def as_json(options={})
|
119
|
+
self.utc.to_s(Ripple.date_format)
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.ripple_cast(value)
|
123
|
+
return nil if value.blank?
|
124
|
+
value.respond_to?(:to_datetime) && value.to_datetime or raise Ripple::PropertyTypeMismatch.new(self, value)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# @private
|
129
|
+
module ActiveSupport
|
130
|
+
class TimeWithZone
|
131
|
+
def as_json(options={})
|
132
|
+
self.utc.send(Ripple.date_format)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# @private
|
138
|
+
class Set
|
139
|
+
def as_json(options = {})
|
140
|
+
map { |e| e.as_json(options) }
|
141
|
+
end
|
142
|
+
|
143
|
+
def self.ripple_cast(value)
|
144
|
+
return nil if value.nil?
|
145
|
+
value.is_a?(Enumerable) && new(value) or raise Ripple::PropertyTypeMismatch.new(self, value)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|