cardiac 0.2.0.pre2
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.
- checksums.yaml +7 -0
- data/.rspec +2 -0
- data/LICENSE +22 -0
- data/Rakefile +66 -0
- data/cardiac-0.2.0.pre2.gem +0 -0
- data/cardiac.gemspec +48 -0
- data/lib/cardiac/declarations.rb +70 -0
- data/lib/cardiac/errors.rb +65 -0
- data/lib/cardiac/log_subscriber.rb +55 -0
- data/lib/cardiac/model/attributes.rb +146 -0
- data/lib/cardiac/model/base.rb +161 -0
- data/lib/cardiac/model/callbacks.rb +47 -0
- data/lib/cardiac/model/declarations.rb +106 -0
- data/lib/cardiac/model/dirty.rb +117 -0
- data/lib/cardiac/model/locale/en.yml +7 -0
- data/lib/cardiac/model/operations.rb +49 -0
- data/lib/cardiac/model/persistence.rb +171 -0
- data/lib/cardiac/model/querying.rb +129 -0
- data/lib/cardiac/model/validations.rb +124 -0
- data/lib/cardiac/model.rb +17 -0
- data/lib/cardiac/operation_builder.rb +75 -0
- data/lib/cardiac/operation_handler.rb +215 -0
- data/lib/cardiac/railtie.rb +20 -0
- data/lib/cardiac/reflections.rb +85 -0
- data/lib/cardiac/representation.rb +124 -0
- data/lib/cardiac/resource/adapter.rb +178 -0
- data/lib/cardiac/resource/builder.rb +107 -0
- data/lib/cardiac/resource/codec_methods.rb +58 -0
- data/lib/cardiac/resource/config_methods.rb +39 -0
- data/lib/cardiac/resource/extension_methods.rb +115 -0
- data/lib/cardiac/resource/request_methods.rb +138 -0
- data/lib/cardiac/resource/subresource.rb +88 -0
- data/lib/cardiac/resource/uri_methods.rb +176 -0
- data/lib/cardiac/resource.rb +77 -0
- data/lib/cardiac/util.rb +120 -0
- data/lib/cardiac/version.rb +3 -0
- data/lib/cardiac.rb +61 -0
- data/spec/rails-3.2/Gemfile +9 -0
- data/spec/rails-3.2/Gemfile.lock +136 -0
- data/spec/rails-3.2/Rakefile +10 -0
- data/spec/rails-3.2/app_root/app/assets/javascripts/application.js +15 -0
- data/spec/rails-3.2/app_root/app/assets/stylesheets/application.css +13 -0
- data/spec/rails-3.2/app_root/app/controllers/application_controller.rb +3 -0
- data/spec/rails-3.2/app_root/app/helpers/application_helper.rb +2 -0
- data/spec/rails-3.2/app_root/app/views/layouts/application.html.erb +14 -0
- data/spec/rails-3.2/app_root/config/application.rb +29 -0
- data/spec/rails-3.2/app_root/config/boot.rb +13 -0
- data/spec/rails-3.2/app_root/config/database.yml +25 -0
- data/spec/rails-3.2/app_root/config/environment.rb +5 -0
- data/spec/rails-3.2/app_root/config/environments/development.rb +10 -0
- data/spec/rails-3.2/app_root/config/environments/production.rb +11 -0
- data/spec/rails-3.2/app_root/config/environments/test.rb +11 -0
- data/spec/rails-3.2/app_root/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/rails-3.2/app_root/config/initializers/inflections.rb +15 -0
- data/spec/rails-3.2/app_root/config/initializers/mime_types.rb +5 -0
- data/spec/rails-3.2/app_root/config/initializers/secret_token.rb +7 -0
- data/spec/rails-3.2/app_root/config/initializers/session_store.rb +8 -0
- data/spec/rails-3.2/app_root/config/initializers/wrap_parameters.rb +14 -0
- data/spec/rails-3.2/app_root/config/locales/en.yml +5 -0
- data/spec/rails-3.2/app_root/config/routes.rb +2 -0
- data/spec/rails-3.2/app_root/db/test.sqlite3 +0 -0
- data/spec/rails-3.2/app_root/log/test.log +2403 -0
- data/spec/rails-3.2/app_root/public/404.html +26 -0
- data/spec/rails-3.2/app_root/public/422.html +26 -0
- data/spec/rails-3.2/app_root/public/500.html +25 -0
- data/spec/rails-3.2/app_root/public/favicon.ico +0 -0
- data/spec/rails-3.2/app_root/script/rails +6 -0
- data/spec/rails-3.2/spec/spec_helper.rb +25 -0
- data/spec/rails-4.0/Gemfile +9 -0
- data/spec/rails-4.0/Gemfile.lock +132 -0
- data/spec/rails-4.0/Rakefile +10 -0
- data/spec/rails-4.0/app_root/app/assets/javascripts/application.js +15 -0
- data/spec/rails-4.0/app_root/app/assets/stylesheets/application.css +13 -0
- data/spec/rails-4.0/app_root/app/controllers/application_controller.rb +3 -0
- data/spec/rails-4.0/app_root/app/helpers/application_helper.rb +2 -0
- data/spec/rails-4.0/app_root/app/views/layouts/application.html.erb +14 -0
- data/spec/rails-4.0/app_root/config/application.rb +28 -0
- data/spec/rails-4.0/app_root/config/boot.rb +13 -0
- data/spec/rails-4.0/app_root/config/database.yml +25 -0
- data/spec/rails-4.0/app_root/config/environment.rb +5 -0
- data/spec/rails-4.0/app_root/config/environments/development.rb +9 -0
- data/spec/rails-4.0/app_root/config/environments/production.rb +11 -0
- data/spec/rails-4.0/app_root/config/environments/test.rb +10 -0
- data/spec/rails-4.0/app_root/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/rails-4.0/app_root/config/initializers/inflections.rb +15 -0
- data/spec/rails-4.0/app_root/config/initializers/mime_types.rb +5 -0
- data/spec/rails-4.0/app_root/config/initializers/secret_token.rb +7 -0
- data/spec/rails-4.0/app_root/config/initializers/session_store.rb +8 -0
- data/spec/rails-4.0/app_root/config/initializers/wrap_parameters.rb +14 -0
- data/spec/rails-4.0/app_root/config/locales/en.yml +5 -0
- data/spec/rails-4.0/app_root/config/routes.rb +2 -0
- data/spec/rails-4.0/app_root/db/test.sqlite3 +0 -0
- data/spec/rails-4.0/app_root/log/development.log +50 -0
- data/spec/rails-4.0/app_root/log/test.log +2399 -0
- data/spec/rails-4.0/app_root/public/404.html +26 -0
- data/spec/rails-4.0/app_root/public/422.html +26 -0
- data/spec/rails-4.0/app_root/public/500.html +25 -0
- data/spec/rails-4.0/app_root/public/favicon.ico +0 -0
- data/spec/rails-4.0/app_root/script/rails +6 -0
- data/spec/rails-4.0/spec/spec_helper.rb +25 -0
- data/spec/shared/cardiac/declarations_spec.rb +103 -0
- data/spec/shared/cardiac/model/base_spec.rb +446 -0
- data/spec/shared/cardiac/operation_builder_spec.rb +96 -0
- data/spec/shared/cardiac/operation_handler_spec.rb +82 -0
- data/spec/shared/cardiac/representation/reflection_spec.rb +73 -0
- data/spec/shared/cardiac/resource/adapter_spec.rb +83 -0
- data/spec/shared/cardiac/resource/builder_spec.rb +52 -0
- data/spec/shared/cardiac/resource/codec_methods_spec.rb +63 -0
- data/spec/shared/cardiac/resource/config_methods_spec.rb +52 -0
- data/spec/shared/cardiac/resource/extension_methods_spec.rb +215 -0
- data/spec/shared/cardiac/resource/request_methods_spec.rb +186 -0
- data/spec/shared/cardiac/resource/uri_methods_spec.rb +212 -0
- data/spec/shared/support/client_execution.rb +28 -0
- data/spec/spec_helper.rb +24 -0
- metadata +463 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: ffcee4b9c9da2df77d5d3530d5562848b497fdab
|
|
4
|
+
data.tar.gz: d0dc3f4297ef67ddc38f139be6b64b00b7ff4dc9
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: d01919339fec4e2519bc8b92479383d7c259247fcae5055e1a2722baceca9f91e070398660c04103a6fb0ffdb70c55faf23a9d04973e0faf24f8fdbf45cc9715
|
|
7
|
+
data.tar.gz: 55c38c5f11b07b8d92fc6d314d6c3a674bf8bfed80a936ab54b755042d185745d6fa76d217551d851772a00a418b664f24f24293f8df95fc0cb048df1b77f767
|
data/.rspec
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2014 Joe Khoobyar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
data/Rakefile
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
require 'rake'
|
|
2
|
+
require 'bundler/gem_tasks'
|
|
3
|
+
|
|
4
|
+
desc 'Default: Run all specs.'
|
|
5
|
+
task :default => 'all:spec'
|
|
6
|
+
|
|
7
|
+
namespace :all do
|
|
8
|
+
|
|
9
|
+
desc "Run specs on all spec apps"
|
|
10
|
+
task :spec do
|
|
11
|
+
success = true
|
|
12
|
+
for_each_directory_of('spec/**/Rakefile') do |directory|
|
|
13
|
+
env = "SPEC=../../#{ENV['SPEC']} " if ENV['SPEC']
|
|
14
|
+
success &= system("cd #{directory} && #{env} bundle exec rake spec")
|
|
15
|
+
end
|
|
16
|
+
fail "Tests failed" unless success
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
namespace :bundle do
|
|
20
|
+
|
|
21
|
+
desc "Bundle all spec apps"
|
|
22
|
+
task :install do
|
|
23
|
+
for_each_directory_of('spec/**/Gemfile') do |directory|
|
|
24
|
+
Bundler.with_clean_env do
|
|
25
|
+
system("cd #{directory} && bundle install")
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
desc "Update all gems, or a list of gem given by the GEM environment variable"
|
|
31
|
+
task :update do
|
|
32
|
+
for_each_directory_of('spec/**/Gemfile') do |directory|
|
|
33
|
+
Bundler.with_clean_env do
|
|
34
|
+
system("cd #{directory} && bundle update #{ENV['GEM']}")
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
#desc "Bundle the gem"
|
|
44
|
+
#task :bundle => [:bundle_install] do
|
|
45
|
+
# sh 'gem build *.gemspec'
|
|
46
|
+
# sh 'gem install *.gem'
|
|
47
|
+
# sh 'rm *.gem'
|
|
48
|
+
#end
|
|
49
|
+
#
|
|
50
|
+
#desc "Runs bundle install"
|
|
51
|
+
#task :bundle_install do
|
|
52
|
+
# sh('bundle install')
|
|
53
|
+
#end
|
|
54
|
+
|
|
55
|
+
desc "generate rdoc"
|
|
56
|
+
task :rdoc do
|
|
57
|
+
sh "yardoc"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def for_each_directory_of(path, &block)
|
|
61
|
+
Dir[path].sort.each do |rakefile|
|
|
62
|
+
directory = File.dirname(rakefile)
|
|
63
|
+
puts '', "\033[44m#{directory}\033[0m", ''
|
|
64
|
+
block.call(directory)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
Binary file
|
data/cardiac.gemspec
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
|
3
|
+
require "cardiac/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |s|
|
|
6
|
+
s.name = "cardiac"
|
|
7
|
+
s.version = Cardiac::VERSION
|
|
8
|
+
s.authors = ["Joe Khoobyar"]
|
|
9
|
+
s.email = ["joe@khoobyar.name"]
|
|
10
|
+
s.homepage = "http://github.com/cardiac/cardiac"
|
|
11
|
+
s.license = 'MIT'
|
|
12
|
+
s.summary = %q{ Cardiac: a REST modeling framework for Ruby }
|
|
13
|
+
s.description = %q{
|
|
14
|
+
This gem provides a thin facade around REST-ful resources, aiming to be closer to ActiveRecord than ActiveResource.
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
s.rubyforge_project = "cardiac"
|
|
18
|
+
|
|
19
|
+
s.required_ruby_version = '>= 1.9.2'
|
|
20
|
+
|
|
21
|
+
s.files = (Dir.glob("*") + Dir.glob("lib/**/*")).delete_if do |item|
|
|
22
|
+
item.include?("rdoc") ||
|
|
23
|
+
item.include?(".git")
|
|
24
|
+
end
|
|
25
|
+
s.require_paths = ["lib"]
|
|
26
|
+
|
|
27
|
+
s.test_files = (['.rspec'] + Dir.glob("spec/**/*")).delete_if do |item|
|
|
28
|
+
item.include?("rdoc") ||
|
|
29
|
+
item.include?(".git")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
s.add_development_dependency 'rake', '~> 10.1'
|
|
33
|
+
s.add_development_dependency 'rspec', '~> 3.0', '< 3.1'
|
|
34
|
+
s.add_development_dependency 'rspec-rails', '~> 3.0', '< 3.1'
|
|
35
|
+
s.add_development_dependency 'rspec-collection_matchers', '~> 1.0'
|
|
36
|
+
|
|
37
|
+
s.add_runtime_dependency "rack", '>= 1.4.5'
|
|
38
|
+
s.add_runtime_dependency "rack-cache", '~> 1.2'
|
|
39
|
+
s.add_runtime_dependency "rack-client", '~> 0.4.2'
|
|
40
|
+
s.add_runtime_dependency "activesupport", '>= 3.2', '< 4.1'
|
|
41
|
+
s.add_runtime_dependency "multi_json", '~> 1.0'
|
|
42
|
+
s.add_runtime_dependency "json", '> 1.8.0'
|
|
43
|
+
s.add_runtime_dependency "mime-types", '> 1.1'
|
|
44
|
+
s.add_runtime_dependency "i18n", '~> 0.6', '>= 0.6.4'
|
|
45
|
+
s.add_runtime_dependency "activemodel", '>= 3.2', '< 4.1'
|
|
46
|
+
s.add_runtime_dependency "active_attr", '>= 0.8.2'
|
|
47
|
+
|
|
48
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
module Cardiac
|
|
2
|
+
class DeclarationBuilder < ::Cardiac::ExtensionBuilder
|
|
3
|
+
# Overridden to build directly on a resource/subresource.
|
|
4
|
+
def initialize(base, extension_module=nil)
|
|
5
|
+
case base
|
|
6
|
+
when ::Cardiac::Resource
|
|
7
|
+
builder = ::Cardiac::Subresource.new(base)
|
|
8
|
+
when ::URI, ::String
|
|
9
|
+
builder = ::Cardiac::Resource.new(base)
|
|
10
|
+
else
|
|
11
|
+
raise ArgumentError, 'a base URI or Resource must be provided'
|
|
12
|
+
end
|
|
13
|
+
super builder, extension_module
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Overridden to return an extended resource/subresource,
|
|
17
|
+
# but skip building the extension module if no block is given.
|
|
18
|
+
def extension_exec(*args, &block)
|
|
19
|
+
builder = super(*args, &block)
|
|
20
|
+
builder.extending(__extension_module__) if block
|
|
21
|
+
builder
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
module DeclarationMethods
|
|
26
|
+
|
|
27
|
+
# Declares a new resource off of the given base, using the given extension block.
|
|
28
|
+
def resource base, &declaration
|
|
29
|
+
DeclarationBuilder.new(base).extension_eval(&declaration)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Dynamically declares a new subresource (optionally targetting a given sub-url),
|
|
33
|
+
# and yields a new OperationProxy which targets that subresource.
|
|
34
|
+
def with_resource(at=nil)
|
|
35
|
+
yield OperationProxy.new(at ? Subresource.new(base_resource).at(at) : base_resource)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
module Declarations
|
|
40
|
+
extend ActiveSupport::Concern
|
|
41
|
+
|
|
42
|
+
included do
|
|
43
|
+
if respond_to? :class_attribute
|
|
44
|
+
class_attribute :base_resource, instance_reader: false, instance_writer: false
|
|
45
|
+
else
|
|
46
|
+
mattr_accessor :base_resource, instance_reader: false, instance_writer: false
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Extensions for a class or even a module, so it may declare resources.
|
|
51
|
+
module ClassMethods
|
|
52
|
+
include DeclarationMethods
|
|
53
|
+
|
|
54
|
+
delegate :base_url, to: :base_resource
|
|
55
|
+
|
|
56
|
+
# Overridden to always write to the base_resource
|
|
57
|
+
def resource base=base_resource, &declaration
|
|
58
|
+
self.base_resource = super(base, &declaration)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Instance-level extensions for declaring resources.
|
|
63
|
+
include DeclarationMethods
|
|
64
|
+
|
|
65
|
+
# These start out as private, especially since an instance-level :with_resource
|
|
66
|
+
# would need an instance-level implementation of :base_resource
|
|
67
|
+
private :resource, :with_resource
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
module Cardiac
|
|
2
|
+
|
|
3
|
+
# Exception classes.
|
|
4
|
+
class ProtocolError < StandardError
|
|
5
|
+
end
|
|
6
|
+
class ResourceError < StandardError
|
|
7
|
+
end
|
|
8
|
+
class UnresolvableResourceError < ResourceError
|
|
9
|
+
end
|
|
10
|
+
class InvalidOperationError < ResourceError
|
|
11
|
+
end
|
|
12
|
+
class InvalidRepresentationError < ResourceError
|
|
13
|
+
end
|
|
14
|
+
class OperationAbortError < ResourceError
|
|
15
|
+
end
|
|
16
|
+
class OperationFailError < ResourceError
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Thrown when a request has failed, due to a non-2xx status code.
|
|
20
|
+
class RequestFailedError < ResourceError
|
|
21
|
+
attr_reader :response
|
|
22
|
+
def initialize(response,message=nil)
|
|
23
|
+
@response = response
|
|
24
|
+
super(message || Rack::Utils::HTTP_STATUS_CODES[@response.status])
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @see ActiveRecord::RecordInvalid
|
|
29
|
+
class RecordInvalid < ResourceError
|
|
30
|
+
attr_reader :record # :nodoc:
|
|
31
|
+
def initialize(record) # :nodoc:
|
|
32
|
+
@record = record
|
|
33
|
+
|
|
34
|
+
errors = @record.errors.full_messages.join(", ")
|
|
35
|
+
remote_errors = @record.remote_errors.full_messages.join(", ")
|
|
36
|
+
|
|
37
|
+
if remote_errors.present?
|
|
38
|
+
remote_errors = "(previous remote operation) #{remote_errors}"
|
|
39
|
+
errors += ' ' if errors.present?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
super I18n.t(:"#{@record.class.i18n_scope}.errors.messages.record_invalid",
|
|
43
|
+
errors: errors,
|
|
44
|
+
remote_errors: remote_errors,
|
|
45
|
+
default: :"errors.messages.record_invalid")
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @see ActiveRecord::RecordNotFound
|
|
50
|
+
class RecordNotFound < OperationFailError
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @see ActiveRecord::RecordNotSaved
|
|
54
|
+
class RecordNotSaved < OperationFailError
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @see ActiveRecord::RecordNotDestroyed
|
|
58
|
+
class RecordNotDestroyed < OperationFailError
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @see ActiveRecord::ReadOnlyRecord
|
|
62
|
+
class ReadOnlyRecord < OperationFailError
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module Cardiac
|
|
2
|
+
|
|
3
|
+
class LogSubscriber < ActiveSupport::LogSubscriber
|
|
4
|
+
|
|
5
|
+
delegate :logger, to: '::Cardiac::Model::Base'
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
super
|
|
9
|
+
@odd_or_even = false
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def operation(event)
|
|
13
|
+
return unless logger.debug?
|
|
14
|
+
|
|
15
|
+
payload = event.payload
|
|
16
|
+
|
|
17
|
+
url = payload[:url]
|
|
18
|
+
stats = "#{event.duration.round(1)}ms"
|
|
19
|
+
stats = "CACHED #{stats}" if /fresh/ === payload[:response_headers].try(:[],'X-Rack-Client-Cache')
|
|
20
|
+
name = "#{payload[:name]} #{payload[:verb]} (#{stats})"
|
|
21
|
+
|
|
22
|
+
if extra = payload.except(:name, :verb, :url, :response_headers).presence
|
|
23
|
+
extra = " " + extra.map{|key,value|
|
|
24
|
+
key = key.to_s.underscore.upcase
|
|
25
|
+
"#{key}: #{key=='PAYLOAD' ? value : value.inspect}"
|
|
26
|
+
}.join(",\n\t +")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
if odd?
|
|
30
|
+
name = color(name, CYAN, true)
|
|
31
|
+
url = color(url, nil, true)
|
|
32
|
+
else
|
|
33
|
+
name = color(name, MAGENTA, true)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
debug " #{name} #{url}#{extra}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def identity(event)
|
|
40
|
+
return unless logger.debug?
|
|
41
|
+
|
|
42
|
+
name = color(event.payload[:name], odd? ? CYAN : MAGENTA, true)
|
|
43
|
+
line = odd? ? color(event.payload[:line], nil, true) : event.payload[:line]
|
|
44
|
+
|
|
45
|
+
debug " #{name} #{line}"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def odd?
|
|
49
|
+
@odd_or_even = !@odd_or_even
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Make sure that the log subscriber is listening for events.
|
|
54
|
+
LogSubscriber.attach_to :cardiac
|
|
55
|
+
end
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
module Cardiac
|
|
2
|
+
module Model
|
|
3
|
+
|
|
4
|
+
# Cardiac::Model attribute methods.
|
|
5
|
+
# Some of this has been "borrowed" from ActiveRecord.
|
|
6
|
+
module Attributes
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
module ClassMethods
|
|
10
|
+
|
|
11
|
+
# Overridden to support passing in aliases at the same time.
|
|
12
|
+
# This could decrease the code size for attributes declarations by almost 50% for models with
|
|
13
|
+
# non-friendly remote attribute names.
|
|
14
|
+
def attribute(name,options={})
|
|
15
|
+
aliases = Array(options[:aliases])
|
|
16
|
+
super name, options.except(:aliases)
|
|
17
|
+
aliases.each{|k| alias_attribute k, name } if aliases
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
protected
|
|
21
|
+
|
|
22
|
+
# Unwraps a payload returned by the remote, requiring it to be present.
|
|
23
|
+
#
|
|
24
|
+
# Callers could pass <code>{allow_empty: true}</code> in the options to prevent
|
|
25
|
+
# empty results from being converted to <code>nil</code>.
|
|
26
|
+
def unwrap_remote_data(data,options={})
|
|
27
|
+
data = data.values.first if Hash===data && data.keys.size==1 && data.keys.first.to_s!='errors'
|
|
28
|
+
data = data.presence unless options[:allow_empty]
|
|
29
|
+
data
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
included do
|
|
34
|
+
class_attribute :readonly_attributes, instance_writer: false
|
|
35
|
+
class_attribute :key_attributes, instance_writer: false
|
|
36
|
+
class_attribute :id_delimiter
|
|
37
|
+
|
|
38
|
+
self.readonly_attributes = []
|
|
39
|
+
self.key_attributes = [:id]
|
|
40
|
+
self.id_delimiter = '-'
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Retrieves the most recently unpacked/decoded remote attributes.
|
|
44
|
+
def remote_attributes
|
|
45
|
+
@remote_attributes.with_indifferent_access
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Overridden to use this model's key_attributes to build the key.
|
|
49
|
+
# Returns an array of the key values, if they are all present, otherwise, returns nil.
|
|
50
|
+
def to_key
|
|
51
|
+
keys = key_attributes.presence and keys.map do |key|
|
|
52
|
+
return unless query_attribute(key)
|
|
53
|
+
read_attribute(key)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# This baseline implementation uses this model's id_delimiter to join what is returned by to_key.
|
|
58
|
+
# If the id_delimiter is set to nil, it will simply return an array instead.
|
|
59
|
+
#
|
|
60
|
+
# NOTE: Defining an :id attribute on your model will override this implementation.
|
|
61
|
+
def id
|
|
62
|
+
delim, values = id_delimiter, to_key
|
|
63
|
+
(delim && values) ? values.join(delim) : values
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
protected
|
|
67
|
+
|
|
68
|
+
# Stores the attributes returned by the remote, after performing any unpacking/decoding.
|
|
69
|
+
def assign_remote_attributes(data,options={})
|
|
70
|
+
@remote_attributes = Hash[
|
|
71
|
+
decode_remote_attributes(unwrap_remote_attributes(data, options), options).map do |key,value|
|
|
72
|
+
[key, value.duplicable? ? value.clone : value]
|
|
73
|
+
end
|
|
74
|
+
]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
alias remote_attributes= assign_remote_attributes
|
|
78
|
+
|
|
79
|
+
# Returns a copy of this model's attributes, cloning the duplicable values.
|
|
80
|
+
def clone_attributes(reader_method = :read_attribute, attributes = {}) # :nodoc:
|
|
81
|
+
attribute_names.each do |name|
|
|
82
|
+
attributes[name] = clone_attribute_value(reader_method, name)
|
|
83
|
+
end
|
|
84
|
+
attributes
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Reads an attribute value and returns a clone, if it is duplicable, or the original value, if not.
|
|
88
|
+
def clone_attribute_value(reader_method, attribute_name) # :nodoc:
|
|
89
|
+
value = send(reader_method, attribute_name)
|
|
90
|
+
value.duplicable? ? value.clone : value
|
|
91
|
+
rescue TypeError, NoMethodError
|
|
92
|
+
value
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Unwraps attributes returned by the remote, requiring the data to be non-empty.
|
|
96
|
+
#
|
|
97
|
+
# Callers could pass <code>{allow_empty: true}</code> in the options to prevent
|
|
98
|
+
# empty results from being converted to <code>nil</code>.
|
|
99
|
+
#
|
|
100
|
+
# NOTE: The baseline implementation just delegates to the class method: unwrap_remote_data
|
|
101
|
+
def unwrap_remote_attributes(data,options={})
|
|
102
|
+
self.class.send :unwrap_remote_data, data, options
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Decodes attributes returned by the remote, requiring the data to be non-nil.
|
|
106
|
+
# Callers could pass <code>{only: ...}</code> or <code>{except: ...}</code> in the options
|
|
107
|
+
# to filter the attributes by key.
|
|
108
|
+
#
|
|
109
|
+
# If the remote did not return a Hash, the data is first wrapped in a single key: <code>:data</code>
|
|
110
|
+
def decode_remote_attributes(data,options={})
|
|
111
|
+
unless data.nil?
|
|
112
|
+
data = Hash===data ? data.with_indifferent_access : {data: data}
|
|
113
|
+
data = data.slice(*options[:only]) if options[:only]
|
|
114
|
+
data = data.except(*options[:except]) if options[:except]
|
|
115
|
+
end
|
|
116
|
+
data
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
|
|
121
|
+
# Filters the primary keys and readonly attributes from the attribute names.
|
|
122
|
+
def attributes_for_update(attribute_names)
|
|
123
|
+
attributes.slice(*attribute_names).except(*(readonly_attributes+key_attributes))
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Filters out the primary keys, from the attribute names, when the primary
|
|
127
|
+
# key is to be generated (e.g. the id attribute has no value).
|
|
128
|
+
def attributes_for_create(attribute_names)
|
|
129
|
+
attributes.slice(*attribute_names).except(*key_attributes.reject{|k| query_attribute(k) })
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def readonly_attribute?(name)
|
|
133
|
+
self.class.readonly_attributes.include?(name)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def key_attribute?(name)
|
|
137
|
+
self.class.key_attributes.include?(name)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# No seralized attribute support yet.
|
|
141
|
+
def serialized_attribute_value(name)
|
|
142
|
+
read_attribute(name)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
|
2
|
+
require 'active_attr'
|
|
3
|
+
|
|
4
|
+
module Cardiac
|
|
5
|
+
module Model
|
|
6
|
+
class Base
|
|
7
|
+
include ActiveAttr::Model
|
|
8
|
+
include Cardiac::Model::Attributes
|
|
9
|
+
include Cardiac::Model::Querying
|
|
10
|
+
include Cardiac::Model::Persistence
|
|
11
|
+
include Cardiac::Model::Validations
|
|
12
|
+
include Cardiac::Model::Dirty
|
|
13
|
+
include Cardiac::Model::Callbacks
|
|
14
|
+
include Cardiac::Model::Declarations
|
|
15
|
+
include Cardiac::Model::Operations
|
|
16
|
+
include ActiveSupport::Configurable
|
|
17
|
+
|
|
18
|
+
# Instances may not explicitly define their own base resource.
|
|
19
|
+
undef_method :resource if method_defined? :resource
|
|
20
|
+
|
|
21
|
+
# Expose instance-level with_resource since there is a useful instance-level implementation of base_resource.
|
|
22
|
+
public :with_resource
|
|
23
|
+
|
|
24
|
+
config_accessor :operation_context
|
|
25
|
+
self.operation_context = {}.with_indifferent_access
|
|
26
|
+
|
|
27
|
+
# @see ActiveRecord::Core
|
|
28
|
+
mattr_accessor :logger, instance_writer: false
|
|
29
|
+
|
|
30
|
+
# Configure whether or not to treat all instances as read-only.
|
|
31
|
+
class_attribute :readonly, instance_writer: false
|
|
32
|
+
|
|
33
|
+
# Subclasses have an internationalization scope separate from active model.
|
|
34
|
+
def self.i18n_scope
|
|
35
|
+
:cardiac_model
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @see ActiveRecord::Core#initialize
|
|
39
|
+
def initialize(attributes = nil, options = {})
|
|
40
|
+
super
|
|
41
|
+
init_internals
|
|
42
|
+
init_changed_attributes
|
|
43
|
+
run_callbacks :initialize unless _initialize_callbacks.empty?
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @see ActiveRecord::Core#init_with
|
|
47
|
+
def init_with(coder)
|
|
48
|
+
_remote = coder['remote']
|
|
49
|
+
self.attributes = _remote ? {} : coder['attributes']
|
|
50
|
+
init_internals
|
|
51
|
+
@new_record = false
|
|
52
|
+
if _remote
|
|
53
|
+
_remote = {} unless Hash===_remote
|
|
54
|
+
self.attributes = decode_remote_attributes( unwrap_remote_attributes(coder['attributes'], _remote),
|
|
55
|
+
_remote )
|
|
56
|
+
@changed_attributes.clear
|
|
57
|
+
end
|
|
58
|
+
run_callbacks :find
|
|
59
|
+
run_callbacks :initialize
|
|
60
|
+
self
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# @see ActiveRecord::Core#slice
|
|
64
|
+
def slice(*methods)
|
|
65
|
+
Hash[methods.map { |method| [method, public_send(method)] }].with_indifferent_access
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# @see ActiveRecord::Core#inspect
|
|
69
|
+
def inspect
|
|
70
|
+
defined?(@attributes) && @attributes ? super : "#<#{self.class} not initialized>"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# @see ActiveRecord::Core#encode_with
|
|
74
|
+
def encode_with(coder)
|
|
75
|
+
coder['attributes'] = attributes
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# @see ActiveRecord::Core#==
|
|
79
|
+
def ==(comparison_object)
|
|
80
|
+
super ||
|
|
81
|
+
comparison_object.instance_of?(self.class) &&
|
|
82
|
+
id.present? &&
|
|
83
|
+
comparison_object.id == id
|
|
84
|
+
end
|
|
85
|
+
alias :eql? :==
|
|
86
|
+
|
|
87
|
+
# @see ActiveRecord::Core#hash
|
|
88
|
+
def hash
|
|
89
|
+
id.hash
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# @see ActiveRecord::Core#freeze
|
|
93
|
+
def freeze
|
|
94
|
+
@attributes = @attributes.clone.freeze
|
|
95
|
+
self
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# @see ActiveRecord::Core#frozen
|
|
99
|
+
def frozen?
|
|
100
|
+
@attributes.frozen?
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# @see ActiveRecord::Core#<=>
|
|
104
|
+
def <=>(other_object)
|
|
105
|
+
if other_object.is_a?(self.class)
|
|
106
|
+
self.to_key <=> other_object.to_key
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# @see ActiveRecord::Core#readonly?
|
|
111
|
+
def readonly?
|
|
112
|
+
@readonly
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# @see ActiveRecord::Core#readonly!
|
|
116
|
+
def readonly!
|
|
117
|
+
@readonly = true
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
private
|
|
121
|
+
|
|
122
|
+
# @see ActiveRecord::Core#init_changed_attributes
|
|
123
|
+
def init_changed_attributes
|
|
124
|
+
attribute_defaults.each do |name,value|
|
|
125
|
+
@changed_attributes[name] = value if _field_changed?(name, value, @attributes[name])
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Under Ruby 1.9, Array#flatten will call #to_ary (recursively) on each of the elements
|
|
130
|
+
# of the array, and then rescues from the possible NoMethodError. If those elements are
|
|
131
|
+
# ActiveRecord::Base's, then this triggers the various method_missing's that we have,
|
|
132
|
+
# which significantly impacts upon performance.
|
|
133
|
+
#
|
|
134
|
+
# So we can avoid the method_missing hit by explicitly defining #to_ary as nil here.
|
|
135
|
+
#
|
|
136
|
+
# See also http://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary.html
|
|
137
|
+
def to_ary # :nodoc:
|
|
138
|
+
nil
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# @see ActiveRecord::Core#init_internals
|
|
142
|
+
def init_internals
|
|
143
|
+
@attributes ||= {}
|
|
144
|
+
|
|
145
|
+
self.class.key_attributes.each do |key|
|
|
146
|
+
@attributes[key] = nil unless @attributes.key?(key)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
@previously_changed = {}
|
|
150
|
+
@changed_attributes = {}
|
|
151
|
+
@readonly = !! self.class.readonly?
|
|
152
|
+
@destroyed = false
|
|
153
|
+
@new_record = true
|
|
154
|
+
@remote_attributes = {}
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
ActiveSupport.run_load_hooks(:cardiac, Base)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
end
|