mirror_mirror 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE +11 -0
- data/README.md +70 -0
- data/Rakefile +2 -0
- data/lib/mirror_mirror/active_record_base.rb +136 -0
- data/lib/mirror_mirror/railtie.rb +11 -0
- data/lib/mirror_mirror/version.rb +3 -0
- data/lib/mirror_mirror.rb +30 -0
- data/mirror_mirror.gemspec +17 -0
- metadata +64 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
New BSD License
|
2
|
+
Copyright (c) 1999 - 2012, Daniel Doezema
|
3
|
+
All rights reserved.
|
4
|
+
|
5
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
8
|
+
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
9
|
+
* The names of the contributors and/or copyright holder may not be used to endorse or promote products derived from this software without specific prior written permission.
|
10
|
+
|
11
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DANIEL DOEZEMA BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# MirrorMirror
|
2
|
+
|
3
|
+
A Ruby gem that allows for easier interactions and integration with external REST resources.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
gem 'mirror_mirror'
|
8
|
+
|
9
|
+
## Usage Example
|
10
|
+
|
11
|
+
### Environment
|
12
|
+
|
13
|
+
# App controlled resource, reliant on external resource.
|
14
|
+
class Timesheet < ActiveRecord::Base
|
15
|
+
belongs_to :contractor, :auto_reflect => true
|
16
|
+
end
|
17
|
+
|
18
|
+
# Extenal Resource
|
19
|
+
class Contractor < ActiveRecord::Base
|
20
|
+
has_many :timesheets
|
21
|
+
|
22
|
+
mirror_mirror "http://api.example.com/v1/contractors",
|
23
|
+
:request => :resource_request, :find => true
|
24
|
+
|
25
|
+
def resource_request(verb, url, params)
|
26
|
+
response = RestClient.send(verb, url + '.json', params)
|
27
|
+
ActiveSupport::JSON.decode(response.to_str)["result"] if response.code == 200
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# API Resource Example
|
32
|
+
{
|
33
|
+
result: [{
|
34
|
+
id: 1,
|
35
|
+
first_name: "Daniel",
|
36
|
+
last_name: "Doezema"
|
37
|
+
},
|
38
|
+
{
|
39
|
+
id: 2,
|
40
|
+
first_name: "John",
|
41
|
+
last_name: "Doe"
|
42
|
+
}]
|
43
|
+
}
|
44
|
+
|
45
|
+
### External API Scenario
|
46
|
+
|
47
|
+
class ContractorsController < ApiController
|
48
|
+
# api/contractors/:id/clock_out
|
49
|
+
def clock_out
|
50
|
+
# Automatically found & created b/c of the mirror_mirror :find => true option
|
51
|
+
contractor = Contractor.find(params[:id])
|
52
|
+
timesheet = contractor.timesheets.last
|
53
|
+
result = timesheet.update_attributes(:ended_at => Time.now)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
### The "We've only got an id" Scenario
|
58
|
+
|
59
|
+
In this scenario a `contractor_id` is present, but we don't know if the `Contractor` record exists locally yet -- this is where the `:auto_reflect` option helps out. If a local `Contractor` record is not found then a request will automatically be made to fetch and create it.
|
60
|
+
|
61
|
+
# Controller
|
62
|
+
@timesheets = Timesheet.where("started_at > ?", Time.now.beginning_of_day)
|
63
|
+
|
64
|
+
# View
|
65
|
+
<h1>Today's Punched-In Contractors</h1>
|
66
|
+
<ul>
|
67
|
+
<% @timesheets.each do |timesheet| %>
|
68
|
+
<li><%= "#{timesheet.worker.first_name} #{timesheet.worker.last_name}"</li>
|
69
|
+
<% end %>
|
70
|
+
</ul>
|
data/Rakefile
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
module MirrorMirror::ActiveRecordBase
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
|
6
|
+
def reflect!
|
7
|
+
hash = self.class.mirror_request!(:get, mirror_url)
|
8
|
+
raise RecordNotFound, "Non-Hash returned from resource: #{mirror_url} => #{hash.inspect}" unless hash.is_a?(Hash)
|
9
|
+
raise RecordNotFound, "Empty hash returned from resource." unless hash.any?
|
10
|
+
hash.each {|k,v| send("#{k}=", v) if respond_to?("#{k}=") }
|
11
|
+
self.updated_at = Time.now
|
12
|
+
save!
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
def mirror_url
|
17
|
+
self.class.mirror_url(id)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
module ClassMethods
|
23
|
+
|
24
|
+
def mirror_mirror(*args)
|
25
|
+
options = args.extract_options!
|
26
|
+
raise ArgumentError, "url is blank." if args[0].blank?
|
27
|
+
options[:url] = args[0]
|
28
|
+
@mirror_mirrior_options = options
|
29
|
+
end
|
30
|
+
|
31
|
+
def mirroring?
|
32
|
+
@mirror_mirrior_options.present?
|
33
|
+
end
|
34
|
+
|
35
|
+
def mirror_mirror_options(option)
|
36
|
+
@mirror_mirrior_options ||= {}
|
37
|
+
@mirror_mirrior_options[option]
|
38
|
+
end
|
39
|
+
|
40
|
+
def mirror_find?
|
41
|
+
mirroring? ? @mirror_mirrior_options[:find] : false
|
42
|
+
end
|
43
|
+
|
44
|
+
def mirror_url(id = nil)
|
45
|
+
url = @mirror_mirrior_options[:url]
|
46
|
+
url += "/#{id.to_i}" if id.to_i > 0
|
47
|
+
url
|
48
|
+
end
|
49
|
+
|
50
|
+
def mirror_request!(verb, url, params = {})
|
51
|
+
method = @mirror_mirrior_options[:request]
|
52
|
+
if method.present?
|
53
|
+
raise ArgumentError, "url is blank." if url.blank?
|
54
|
+
raise ArgumentError, "verb is invalid." unless verb.in?([:get, :put, :post, :delete])
|
55
|
+
raise ArgumentError, "params is not a Hash." unless params.is_a?(Hash)
|
56
|
+
begin
|
57
|
+
send(method, verb, url, params)
|
58
|
+
rescue => e
|
59
|
+
raise FailedRequest, "#{verb.to_s.upcase} #{url} :: Params: #{params} :: Initial Exception: #{e.class.name}: #{e}"
|
60
|
+
end
|
61
|
+
elsif MirrorMirror.request?
|
62
|
+
MirrorMirror.request(verb, url, params)
|
63
|
+
else
|
64
|
+
raise FailedRequest, "No request method specified."
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def reflect_all!(params = {})
|
69
|
+
array = mirror_request(:get, mirror_url, params)
|
70
|
+
raise RecordNotFound, "Non-Array returned from resource: #{mirror_url} => #{hash.inspect}" unless array.is_a?(Array)
|
71
|
+
if array.any?
|
72
|
+
transaction do
|
73
|
+
array.each do |hash|
|
74
|
+
if (id = hash["id"]).present?
|
75
|
+
record = find_or_initialize_by_id(id)
|
76
|
+
hash.each {|k,v| record.send("#{k}=", v) if record.respond_to?("#{k}=") }
|
77
|
+
end
|
78
|
+
record.updated_at = Time.now
|
79
|
+
record.save!
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
all
|
84
|
+
end
|
85
|
+
|
86
|
+
# Active Record Polymorphing
|
87
|
+
def find(*args)
|
88
|
+
return super unless mirror_find?
|
89
|
+
begin
|
90
|
+
super
|
91
|
+
rescue ActiveRecord::RecordNotFound
|
92
|
+
if (id = args[0].to_i) > 0
|
93
|
+
record = self.new
|
94
|
+
record.id = id
|
95
|
+
record.reflect!
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def reflect_by_id!(id)
|
101
|
+
record = self.new
|
102
|
+
record.id = id
|
103
|
+
record.reflect!
|
104
|
+
record
|
105
|
+
end
|
106
|
+
|
107
|
+
def find_or_reflect_by_id!(*args)
|
108
|
+
record = find_or_initialize_by_id(*args)
|
109
|
+
record.reflect! if record.new_record?
|
110
|
+
record
|
111
|
+
end
|
112
|
+
|
113
|
+
def belongs_to(*args)
|
114
|
+
options = args.extract_options!
|
115
|
+
association_name = args[0].to_s
|
116
|
+
options.delete(:auto_reflect) if auto_reflect = options[:auto_reflect].presence
|
117
|
+
if auto_reflect
|
118
|
+
self.class_eval <<-RUBY
|
119
|
+
def #{association_name}
|
120
|
+
if (result = super).blank?
|
121
|
+
model = send("#{association_name}_type").constantize
|
122
|
+
id = send("#{association_name}_id")
|
123
|
+
if id.present? && model.mirroring?
|
124
|
+
model.reflect_by_id!(id)
|
125
|
+
result = super(true)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
result
|
129
|
+
end
|
130
|
+
RUBY
|
131
|
+
end
|
132
|
+
super(*(args << options))
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require "mirror_mirror/version"
|
2
|
+
|
3
|
+
module MirrorMirror
|
4
|
+
autoload :ActiveRecordBase, 'mirror_mirror/active_record_base'
|
5
|
+
|
6
|
+
class RecordNotFound < ::StandardError; end
|
7
|
+
class FailedRequest < ::StandardError; end
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def request!(verb = :get, url = nil, params = {}, &block)
|
11
|
+
unless block_given?
|
12
|
+
raise ArgumentError, "url is blank." if url.blank?
|
13
|
+
raise ArgumentError, "verb is invalid." unless verb.in?([:get, :put, :post, :delete])
|
14
|
+
raise ArgumentError, "params is not a Hash." unless params.is_a?(Hash)
|
15
|
+
begin
|
16
|
+
return @request_proc.call(verb, url, params)
|
17
|
+
rescue => e
|
18
|
+
raise FailedRequest, "#{verb.to_s.upcase} #{url} :: Params: #{params} :: Initial Exception: #{e.class.name}: #{e}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
@request_proc = Proc.new(&block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def request?
|
25
|
+
@request_proc.present?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
require 'mirror_mirror/railtie' if defined?(Rails)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/mirror_mirror/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Daniel Doezema"]
|
6
|
+
gem.email = ["daniel.doezema@gmail.com"]
|
7
|
+
gem.description = %q{Allows for easier interactions and integration with external REST resources.}
|
8
|
+
gem.summary = %q{Allows for easier interactions and integration with external REST resources.}
|
9
|
+
gem.homepage = "https://github.com/veloper/mirror_mirror"
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "mirror_mirror"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = MirrorMirror::VERSION
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mirror_mirror
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Daniel Doezema
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2012-06-17 00:00:00 Z
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Allows for easier interactions and integration with external REST resources.
|
17
|
+
email:
|
18
|
+
- daniel.doezema@gmail.com
|
19
|
+
executables: []
|
20
|
+
|
21
|
+
extensions: []
|
22
|
+
|
23
|
+
extra_rdoc_files: []
|
24
|
+
|
25
|
+
files:
|
26
|
+
- .gitignore
|
27
|
+
- Gemfile
|
28
|
+
- LICENSE
|
29
|
+
- README.md
|
30
|
+
- Rakefile
|
31
|
+
- lib/mirror_mirror.rb
|
32
|
+
- lib/mirror_mirror/active_record_base.rb
|
33
|
+
- lib/mirror_mirror/railtie.rb
|
34
|
+
- lib/mirror_mirror/version.rb
|
35
|
+
- mirror_mirror.gemspec
|
36
|
+
homepage: https://github.com/veloper/mirror_mirror
|
37
|
+
licenses: []
|
38
|
+
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options: []
|
41
|
+
|
42
|
+
require_paths:
|
43
|
+
- lib
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
45
|
+
none: false
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: "0"
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: "0"
|
56
|
+
requirements: []
|
57
|
+
|
58
|
+
rubyforge_project:
|
59
|
+
rubygems_version: 1.8.24
|
60
|
+
signing_key:
|
61
|
+
specification_version: 3
|
62
|
+
summary: Allows for easier interactions and integration with external REST resources.
|
63
|
+
test_files: []
|
64
|
+
|