untied-consumer-sync 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +21 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +100 -0
- data/Rakefile +6 -0
- data/lib/untied-consumer-sync.rb +55 -0
- data/lib/untied-consumer-sync/backend/backend_shared_example.rb +103 -0
- data/lib/untied-consumer-sync/backend/base.rb +87 -0
- data/lib/untied-consumer-sync/config.rb +18 -0
- data/lib/untied-consumer-sync/observer_helper.rb +136 -0
- data/lib/untied-consumer-sync/payload_proccessor.rb +72 -0
- data/lib/untied-consumer-sync/untied_general_observer.rb +31 -0
- data/lib/untied-consumer-sync/version.rb +7 -0
- data/lib/untied-consumer-sync/zombificator.rb +23 -0
- data/spec/observer_helper_spec.rb +124 -0
- data/spec/payload_proccessor_spec.rb +38 -0
- data/spec/spec_helper.rb +29 -0
- data/spec/support/model_data.yml +18 -0
- data/spec/support/setup_ar_and_schema.rb +39 -0
- data/spec/sync_spec.rb +53 -0
- data/spec/untied-consumer-sync-activerecord/backend/active_record.rb +26 -0
- data/spec/untied_general_observer_spec.rb +44 -0
- data/untied-consumer-sync.gemspec +33 -0
- metadata +201 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Redu Educational Technologies
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
# Untied::Consumer::Sync
|
2
|
+
|
3
|
+
Process the messages comming from your [Untied::Publisher](https://github.com/redu/untied-publisher) and syncs it directly to the database.
|
4
|
+
|
5
|
+
**Build status**
|
6
|
+
|
7
|
+
[![Build Status](https://travis-ci.org/redu/untied-consumer-sync.png?branch=master)](https://travis-ci.org/redu/untied-consumer-sync)
|
8
|
+
|
9
|
+
## Instalation
|
10
|
+
|
11
|
+
Add this line to your Gemfile:
|
12
|
+
```ruby
|
13
|
+
gem 'untied-consumer-sync'
|
14
|
+
```
|
15
|
+
|
16
|
+
Execute bundle:
|
17
|
+
```
|
18
|
+
$ bundle
|
19
|
+
```
|
20
|
+
|
21
|
+
Or install it yourself:
|
22
|
+
```
|
23
|
+
$ gem install untied-consumer-sync
|
24
|
+
```
|
25
|
+
|
26
|
+
## Usage
|
27
|
+
|
28
|
+
You will need to mark which model should be syncronized and what attributes you are interested in. So let's see how to do it.
|
29
|
+
|
30
|
+
Mark your model as a Zombie, so it can be created without all necessary attributes, but to the application it will be as if it does not even exist. When another Untied message completes this Zombie, it will not be a Zombie anymore and your application can count with it.
|
31
|
+
|
32
|
+
**Why this Zombie thing?** It's a good option to syncronize associated models, because Untied does not guarantee the messages order, so you could have an invalid reference (and you don't want it, so zombificate your models!).
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
class User < ActiveRecord::Base
|
36
|
+
include Untied::Consumer::Sync::Zombificator::ActsAsZombie
|
37
|
+
end
|
38
|
+
```
|
39
|
+
|
40
|
+
The next step is specify which attributes you want to store for each model in your application. To do so you need to create a yml file and say to `Untied::Consumer::Sync` where it can find this.
|
41
|
+
|
42
|
+
Say to `Untied::Consumer::Sync` where the file is:
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
Untied::Consumer::Sync.configure do |config|
|
46
|
+
config.model_data = "#{Rails.root}/config/model_data.yml"
|
47
|
+
end
|
48
|
+
# Sets ActiveRecord as backend
|
49
|
+
Untied::Consumer::Sync.backend = :active_record
|
50
|
+
```
|
51
|
+
|
52
|
+
Inform the attributes:
|
53
|
+
|
54
|
+
```yml
|
55
|
+
User: # Payload's type
|
56
|
+
attributes: # Needed attributes
|
57
|
+
- id
|
58
|
+
- login
|
59
|
+
- first_name
|
60
|
+
- last_name
|
61
|
+
mappings:
|
62
|
+
id: core_id # Maps payload's id key to model's core_id column
|
63
|
+
name: User # Model name
|
64
|
+
```
|
65
|
+
|
66
|
+
To know more about the options you can add to this yml, checkout this [wiki article](http://).
|
67
|
+
|
68
|
+
**Note:** If a `destroy` message comes before the `create` one for the entity, the `destoy` message will be ignored.
|
69
|
+
|
70
|
+
## What need to be done?
|
71
|
+
|
72
|
+
- Test Zombificator::ActsAsZombie
|
73
|
+
- Test Backend::Base
|
74
|
+
- Update only the changed attributes in `after_update`
|
75
|
+
- Limit which callbacks should be listened per model
|
76
|
+
- Snake case on the yml
|
77
|
+
- Enable `check_for` to accept more than one attribute for one entity
|
78
|
+
|
79
|
+
## Contributing
|
80
|
+
|
81
|
+
1. Fork it
|
82
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
83
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
84
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
85
|
+
5. Create new Pull Request
|
86
|
+
|
87
|
+
|
88
|
+
<img src="https://github.com/downloads/redu/redupy/redutech-marca.png" alt="Redu Educational Technologies" width="300">
|
89
|
+
|
90
|
+
This project is maintained and funded by [Redu Educational Techologies](http://tech.redu.com.br).
|
91
|
+
|
92
|
+
# Copyright
|
93
|
+
|
94
|
+
Copyright (c) 2012 Redu Educational Technologies
|
95
|
+
|
96
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
97
|
+
|
98
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
99
|
+
|
100
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require "untied-consumer-sync/version"
|
2
|
+
require "untied-consumer-sync/config"
|
3
|
+
require "untied-consumer-sync/payload_proccessor"
|
4
|
+
require "untied-consumer-sync/observer_helper"
|
5
|
+
require "untied-consumer-sync/zombificator"
|
6
|
+
require "untied-consumer-sync/untied_general_observer"
|
7
|
+
require "untied-consumer-sync/backend/base"
|
8
|
+
|
9
|
+
module Untied
|
10
|
+
module Consumer
|
11
|
+
module Sync
|
12
|
+
def self.config
|
13
|
+
@config ||= Config.new
|
14
|
+
end
|
15
|
+
|
16
|
+
# Configures untied-publisher. The options are defined at
|
17
|
+
# lib/untied-consumer-sync/config.rb
|
18
|
+
def self.configure(&block)
|
19
|
+
yield(config) if block_given?
|
20
|
+
|
21
|
+
if config.model_data.blank?
|
22
|
+
raise "Configure where your yml file is."
|
23
|
+
end
|
24
|
+
|
25
|
+
self.init_untied
|
26
|
+
end
|
27
|
+
|
28
|
+
# Loads model data structure
|
29
|
+
def self.model_data
|
30
|
+
@model_data ||= YAML.load_file(Sync.config.model_data)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Initializes Untied Consumer
|
34
|
+
def self.init_untied
|
35
|
+
Untied::Consumer.configure do |config_untied|
|
36
|
+
config_untied.observers = [UntiedGeneralObserver]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Sets the backend that will be used
|
41
|
+
def self.backend=(backend)
|
42
|
+
if backend.is_a? Symbol
|
43
|
+
require "untied-consumer-sync-#{backend.to_s.gsub('_', '')}/backend/#{backend}"
|
44
|
+
backend = "#{Sync}::Backend::#{backend.to_s.classify}::ModelHelper".
|
45
|
+
constantize
|
46
|
+
end
|
47
|
+
@@backend = backend
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.backend
|
51
|
+
@@backend
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
shared_examples_for 'a untied-consumer-sync backend' do
|
2
|
+
let(:subject) { described_class.new(user_config) }
|
3
|
+
let(:user_payload) { { 'my_id'=> 22, 'login' => 'sexy_jedi_3000',
|
4
|
+
'name' => 'Luke Skywalker' } }
|
5
|
+
let(:user_config) do
|
6
|
+
{
|
7
|
+
'attributes' => ['login', 'name', 'id'],
|
8
|
+
'mappings' => { 'id' => 'my_id' },
|
9
|
+
'name' => 'User'
|
10
|
+
}
|
11
|
+
end
|
12
|
+
|
13
|
+
after do
|
14
|
+
User.delete_all
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#find' do
|
18
|
+
context 'when user does not exist yet' do
|
19
|
+
it 'should not find object if it doesnt exist' do
|
20
|
+
subject.find(3333).should be_nil
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'when user exists' do
|
25
|
+
before do
|
26
|
+
User.create do |u|
|
27
|
+
u.my_id = 1
|
28
|
+
u.login = 'luke'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should find object by id' do
|
33
|
+
subject.find(1).should_not be_nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe '#create_zombie' do
|
39
|
+
it 'should create zombie user' do
|
40
|
+
subject.create_zombie(99)
|
41
|
+
User.unscoped.where(:my_id => 99).should exist
|
42
|
+
User.unscoped.where(:my_id => 99).first.should be_zombie
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '#create_model' do
|
47
|
+
context "with valid payload" do
|
48
|
+
context "when user does not exist yet" do
|
49
|
+
it 'should create user' do
|
50
|
+
subject.create_model(user_payload)
|
51
|
+
User.where(:my_id => user_payload['my_id']).should exist
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context "when user exists" do
|
56
|
+
before do
|
57
|
+
User.unscoped.new(:my_id => user_payload['my_id']).
|
58
|
+
save(:validate => false)
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'should update zombie user' do
|
62
|
+
subject.create_model(user_payload)
|
63
|
+
User.where(:my_id => user_payload['my_id']).first.should be_valid
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe '#update_model' do
|
70
|
+
context 'when user does not exist yet' do
|
71
|
+
it 'should create user' do
|
72
|
+
subject.update_model(user_payload)
|
73
|
+
User.where(:my_id => user_payload['my_id']).should exist
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context 'when user exists' do
|
78
|
+
before do
|
79
|
+
User.unscoped.new(:my_id => user_payload['my_id']).
|
80
|
+
save(:validate => false)
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'should update user on database' do
|
84
|
+
subject.update_model(user_payload)
|
85
|
+
User.where(:my_id => user_payload['my_id']).first.should be_valid
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe '#destroy_model' do
|
91
|
+
context 'when user exists' do
|
92
|
+
before do
|
93
|
+
User.unscoped.new(:my_id => user_payload['my_id']).
|
94
|
+
save(:validate => false)
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'should delete from database' do
|
98
|
+
subject.destroy_model(user_payload)
|
99
|
+
User.where(:my_id => user_payload['my_id']).should_not exist
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Untied
|
2
|
+
module Consumer
|
3
|
+
module Sync
|
4
|
+
module Backend
|
5
|
+
module Base
|
6
|
+
@@instances = {}
|
7
|
+
|
8
|
+
def self.included(base)
|
9
|
+
base.extend ClassMethods
|
10
|
+
end
|
11
|
+
|
12
|
+
# Public: Lida com a manipulação dos modelos através de payloads.
|
13
|
+
def initialize(model_data)
|
14
|
+
@model = model_data['name'].constantize
|
15
|
+
@model_data = model_data
|
16
|
+
end
|
17
|
+
|
18
|
+
# Public: Cria um modelo zumbie temporário.
|
19
|
+
#
|
20
|
+
# id - Inteiro que indentifica o objeto de acordo com a configuração
|
21
|
+
#
|
22
|
+
# Retorna o modelo recém criado.
|
23
|
+
def create_zombie(id)
|
24
|
+
zombie = @model.unscoped.new do |z|
|
25
|
+
z.send("#{ @model_data['mappings']['id'] }=", id)
|
26
|
+
end
|
27
|
+
zombie.save(:validate => false)
|
28
|
+
|
29
|
+
zombie
|
30
|
+
end
|
31
|
+
|
32
|
+
# Public: Cria o modelo se o mesmo não existir no banco de dados.
|
33
|
+
#
|
34
|
+
# payload - Hash com os dados a serem inseridos.
|
35
|
+
#
|
36
|
+
# Retorna True se a operação for bem sucedida e False no caso contrário.
|
37
|
+
def create_model(payload)
|
38
|
+
temp_model = (find(payload[@model_data['mappings']['id']]) or
|
39
|
+
@model.unscoped.new)
|
40
|
+
|
41
|
+
# Seta os atributos
|
42
|
+
payload.each_pair { |key, value| temp_model.send("#{key.to_s}=", value) } if temp_model.zombie
|
43
|
+
|
44
|
+
temp_model.save
|
45
|
+
end
|
46
|
+
|
47
|
+
# Public: Atualiza o modelo ou o cria se o mesmo não existir no banco.
|
48
|
+
#
|
49
|
+
# payload - Hash com os dados a serem inseridos.
|
50
|
+
#
|
51
|
+
# Retorna True se a operação for bem sucedida e False no caso contrário.
|
52
|
+
def update_model(payload)
|
53
|
+
temp_model = (find(payload[@model_data['mappings']['id']]) or
|
54
|
+
@model.unscoped.new)
|
55
|
+
payload.each_pair {|key, value| temp_model.send("#{key.to_s}=", value)}
|
56
|
+
|
57
|
+
temp_model.save
|
58
|
+
end
|
59
|
+
|
60
|
+
# Public: Destroi o modelo se o mesmo não existir no banco de dados.
|
61
|
+
#
|
62
|
+
# payload - Hash com os dados do modelo.
|
63
|
+
#
|
64
|
+
# Retorna True se a operação for bem sucedida e False no caso contrário.
|
65
|
+
def destroy_model(payload)
|
66
|
+
temp_model = find(payload[@model_data['mappings']['id']])
|
67
|
+
|
68
|
+
temp_model.destroy if temp_model
|
69
|
+
end
|
70
|
+
|
71
|
+
module ClassMethods
|
72
|
+
def self.new(*args, &block)
|
73
|
+
old_instance = @@instances[args[0]['name']]
|
74
|
+
return old_instance if old_instance
|
75
|
+
|
76
|
+
obj = ModelHelper.allocate
|
77
|
+
obj.send(:initialize, *args, &block)
|
78
|
+
@@instances[args[0]['name']] = obj
|
79
|
+
|
80
|
+
obj
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'configurable'
|
3
|
+
|
4
|
+
module Untied
|
5
|
+
module Consumer
|
6
|
+
module Sync
|
7
|
+
class Config
|
8
|
+
include Configurable
|
9
|
+
|
10
|
+
# Config file with models (and its attributes) that will be sync
|
11
|
+
config :model_data, ""
|
12
|
+
# Publisher's identifier so Sync know where to listen.
|
13
|
+
# Default: untied_publisher
|
14
|
+
config :service_name, 'untied_publisher'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'untied-consumer'
|
2
|
+
|
3
|
+
module Untied
|
4
|
+
module Consumer
|
5
|
+
module Sync
|
6
|
+
class ObserverHelper < Untied::Consumer::Observer
|
7
|
+
# Public: Helper que lida com a logica de manipulção de modelos para o untied.
|
8
|
+
# Observer do untied podem herdar dessa classe para adicionar os callbacks.
|
9
|
+
|
10
|
+
# Public: Metódo proxy que abstrai a complexidade real do create.
|
11
|
+
#
|
12
|
+
# kind - String com o nome do modelo.
|
13
|
+
# payload - Hash com os dados para a criação do modelo.
|
14
|
+
#
|
15
|
+
# Retorna True se a operação foi realizada com sucesso e False no caso
|
16
|
+
# contrário.
|
17
|
+
def create_proxy(kind, payload)
|
18
|
+
call_method("create", kind, payload)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Public: Metódo proxy que abstrai a complexidade real do update.
|
22
|
+
#
|
23
|
+
# kind - String com o nome do modelo.
|
24
|
+
# payload - Hash com os dados para a criação do modelo.
|
25
|
+
#
|
26
|
+
# Retorna True se a operação foi realizada com sucesso e False no caso
|
27
|
+
# contrário.
|
28
|
+
def update_proxy(kind, payload)
|
29
|
+
call_method("update", kind, payload)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Public: Metódo proxy que abstrai a complexidade real do destroy.
|
33
|
+
#
|
34
|
+
# kind - String com o nome do modelo.
|
35
|
+
# payload - Hash com os dados para a criação do modelo.
|
36
|
+
#
|
37
|
+
# Retorna True se a operação foi realizada com sucesso e False no caso.
|
38
|
+
# contrário.
|
39
|
+
def destroy_proxy(kind, payload)
|
40
|
+
call_method("destroy", kind, payload)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Public: Metódo para facilitar o acesso as configurações
|
44
|
+
#
|
45
|
+
# Retorna um Hash com as configurações dos modelos
|
46
|
+
def config
|
47
|
+
Sync.model_data
|
48
|
+
end
|
49
|
+
|
50
|
+
protected
|
51
|
+
|
52
|
+
# Protected: Metodo auxiliar usado pelos proxies, codigo comum ao update, create
|
53
|
+
# e destroy.
|
54
|
+
#
|
55
|
+
# method - String com o nome do metódo a ser chamado: create, update ou destroy.
|
56
|
+
# kind - String com o nome do modelo.
|
57
|
+
# payload - Hash com os dados para a criação do modelo.
|
58
|
+
#
|
59
|
+
# Retorna o True se a operação foi realizada com sucesso e False no caso
|
60
|
+
# contrário.
|
61
|
+
def call_method(method, kind, payload)
|
62
|
+
model_data = config.fetch(kind.classify) {|k| raise "Kind #{k} not found in model_data.yml"}
|
63
|
+
model_helper = Sync.backend.new(model_data)
|
64
|
+
payload_proccessor = PayloadProccessor.new(model_data)
|
65
|
+
new_payload = payload_proccessor.proccess(payload) #Remove dados inuteis
|
66
|
+
|
67
|
+
self.send(method, new_payload, model_helper, payload_proccessor)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Protected: Metódo que cria o modelo.
|
71
|
+
#
|
72
|
+
# payload - Hash com os dados para a criação do modelo.
|
73
|
+
# model_helper - ModelHelper que lida com a criação de modelos usando a payload.
|
74
|
+
# payload_proccessor - PayloadProccessor que adequa a payload remota ao banco
|
75
|
+
# local.
|
76
|
+
#
|
77
|
+
# Retorna True se a operação foi realizada com sucesso e False no caso.
|
78
|
+
# contrário.
|
79
|
+
def create(payload, model_helper, payload_proccessor)
|
80
|
+
new_payload = create_dep(payload, payload_proccessor)
|
81
|
+
|
82
|
+
model_helper.create_model(new_payload)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Protected: Metódo que atauliza o modelo.
|
86
|
+
#
|
87
|
+
# payload - Hash com os dados para a criação do modelo.
|
88
|
+
# model_helper - ModelHelper que lida com a criação de modelos usando a payload.
|
89
|
+
# payload_proccessor - PayloadProccessor que adequa a payload remota ao banco
|
90
|
+
# local.
|
91
|
+
#
|
92
|
+
# Retorna True se a operação foi realizada com sucesso e False no caso.
|
93
|
+
# contrário.
|
94
|
+
def update(payload, model_helper, payload_proccessor)
|
95
|
+
new_payload = create_dep(payload, payload_proccessor)
|
96
|
+
|
97
|
+
model_helper.update_model(new_payload)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Protected: Metódo que destroi o modelo.
|
101
|
+
#
|
102
|
+
# payload - Hash com os dados para a criação do modelo.
|
103
|
+
# model_helper - ModelHelper que lida com a criação de modelos usando a payload.
|
104
|
+
# payload_proccessor - PayloadProccessor que adequa a payload remota ao banco
|
105
|
+
# local.
|
106
|
+
#
|
107
|
+
# Retorna True se a operação foi realizada com sucesso e False no caso.
|
108
|
+
# contrário.
|
109
|
+
def destroy(payload, model_helper, payload_proccessor)
|
110
|
+
model_helper.destroy_model(payload)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Protected: Gera as dependencias para o modelo em questão, cria modelos zombies
|
114
|
+
# se for necesário e traduz as referencias presentes no payload para
|
115
|
+
# referencias locais.
|
116
|
+
#
|
117
|
+
# payload - Hash com os dados para a criação do modelo.
|
118
|
+
# payload_proccessor - PayloadProccessor que adequa a payload remota ao banco
|
119
|
+
# local.
|
120
|
+
#
|
121
|
+
# Retorna um Hash que representa a payload com as dependencias traduzidas
|
122
|
+
def create_dep(payload, payload_proccessor)
|
123
|
+
new_payload = payload.clone
|
124
|
+
payload_proccessor.dependencies.each do |key, value|
|
125
|
+
id = payload[value]
|
126
|
+
aux_helper = Sync.backend.new(config[key.classify])
|
127
|
+
modelo = (aux_helper.find(id) or aux_helper.create_zombie(id))
|
128
|
+
new_payload.merge!(value => modelo.id)
|
129
|
+
end
|
130
|
+
|
131
|
+
new_payload
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Untied
|
2
|
+
module Consumer
|
3
|
+
module Sync
|
4
|
+
class PayloadProccessor
|
5
|
+
# Public: Traduz payload recebido via Untied de acordo com os atributos e
|
6
|
+
# mapeamentos do config/model_data.yml.
|
7
|
+
|
8
|
+
@@instances = {}
|
9
|
+
|
10
|
+
def initialize(model_data)
|
11
|
+
@model_data = model_data
|
12
|
+
end
|
13
|
+
|
14
|
+
# Public: Faz o processamento geral da payload
|
15
|
+
#
|
16
|
+
# payload - Hash com os dados do modelo
|
17
|
+
#
|
18
|
+
# Retorna um novo Hash com os dados processados
|
19
|
+
def proccess(payload)
|
20
|
+
new_payload = slice_useless_attrs(payload)
|
21
|
+
|
22
|
+
map_attrs(new_payload)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Public: Metódo de conveniencia para checar as dependencias
|
26
|
+
#
|
27
|
+
# Retorna um Array com as dependencias presentes na configuração do modelo
|
28
|
+
def dependencies
|
29
|
+
@model_data.fetch('check_for', [])
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.new(*args, &block)
|
33
|
+
old_instance = @@instances[args[0]['name']]
|
34
|
+
return old_instance if old_instance
|
35
|
+
|
36
|
+
obj = PayloadProccessor.allocate
|
37
|
+
obj.send(:initialize, *args, &block)
|
38
|
+
@@instances[args[0]['name']] = obj
|
39
|
+
obj
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
# Protected: Faz mapeamento de atributos
|
45
|
+
#
|
46
|
+
# payload - Hash com os dados do modelo
|
47
|
+
#
|
48
|
+
# Retorna um novo Hash com os atributos mapeados
|
49
|
+
def map_attrs(payload)
|
50
|
+
mappings = @model_data.fetch('mappings', {})
|
51
|
+
|
52
|
+
mappings.each do |k, v|
|
53
|
+
payload.merge!(v => payload.delete(k))
|
54
|
+
end
|
55
|
+
|
56
|
+
payload
|
57
|
+
end
|
58
|
+
|
59
|
+
# Protected: Remove atributos irrelevantes
|
60
|
+
#
|
61
|
+
# payload - Hash com os dados do modelo
|
62
|
+
#
|
63
|
+
# Retorna um novo Hash só com os atributos relevantes
|
64
|
+
def slice_useless_attrs(payload)
|
65
|
+
payload.reject do |key, value|
|
66
|
+
!@model_data['attributes'].include?(key)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Untied
|
2
|
+
module Consumer
|
3
|
+
module Sync
|
4
|
+
# Observes all entities listed on config file
|
5
|
+
class UntiedGeneralObserver < ObserverHelper
|
6
|
+
def initialize
|
7
|
+
super
|
8
|
+
|
9
|
+
elements = self.config.values.collect {|v| v['name'].underscore.to_sym }
|
10
|
+
args = elements << {:from => Sync.config.service_name }
|
11
|
+
self.class.observe(*args)
|
12
|
+
end
|
13
|
+
|
14
|
+
def after_create(payload)
|
15
|
+
kind = payload.keys[0]
|
16
|
+
self.create_proxy(kind, payload.values[0])
|
17
|
+
end
|
18
|
+
|
19
|
+
def after_update(payload)
|
20
|
+
kind = payload.keys[0]
|
21
|
+
self.update_proxy(kind, payload.values[0])
|
22
|
+
end
|
23
|
+
|
24
|
+
def after_destroy(payload)
|
25
|
+
kind = payload.keys[0]
|
26
|
+
self.destroy_proxy(kind, payload.values[0])
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Untied
|
2
|
+
module Consumer
|
3
|
+
module Sync
|
4
|
+
module Zombificator
|
5
|
+
module ActsAsZombie
|
6
|
+
# Modulo que adiciona suporte a modelos zombies. Se um modelo for criado sem validação
|
7
|
+
# ele automaticamente é marcado como zombie, caso a validação seja feita,
|
8
|
+
# o modelo perde essa tag se não ocorrer nenhum erro.
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
included do
|
12
|
+
attr_accessible :zombie
|
13
|
+
|
14
|
+
# Modelos zombies não devem aparecer em consultas normais.
|
15
|
+
default_scope where(:zombie => false)
|
16
|
+
|
17
|
+
after_validation { self.zombie = false if self.errors.empty? }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Untied::Consumer::Sync
|
4
|
+
describe ObserverHelper do
|
5
|
+
let(:subject){ ObserverHelper.instance }
|
6
|
+
|
7
|
+
after do
|
8
|
+
User.delete_all
|
9
|
+
end
|
10
|
+
|
11
|
+
context "#create_proxy" do
|
12
|
+
context "with a complete (valid) User" do
|
13
|
+
let(:user) { { 'id' => 1, 'login' => 'sexy_jedi_3000',
|
14
|
+
'name' => 'Luke Skywalker', :thingy => false } }
|
15
|
+
|
16
|
+
it 'should return true' do
|
17
|
+
subject.create_proxy("user", user).should == true
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should add user to database" do
|
21
|
+
expect{
|
22
|
+
subject.create_proxy("user", user)
|
23
|
+
}.to change(User, :count).by(1)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context "with, for now, invalid references" do
|
28
|
+
let(:toy) { { 'id' => 1, 'user_id' => 1 } }
|
29
|
+
|
30
|
+
it 'should return true' do
|
31
|
+
subject.create_proxy("toy", toy).should == true
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should add toy to database" do
|
35
|
+
expect{
|
36
|
+
subject.create_proxy("toy", toy)
|
37
|
+
}.to change(Toy, :count).by(1)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "creates zombie entity for user_id" do
|
41
|
+
subject.create_proxy("toy", toy)
|
42
|
+
User.unscoped.find_by_my_id(toy['user_id']).should_not be_nil
|
43
|
+
end
|
44
|
+
|
45
|
+
after do
|
46
|
+
Toy.delete_all
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "with a invalid User" do
|
51
|
+
let(:invalid_user) { { 'id' => 1 } }
|
52
|
+
|
53
|
+
it "should return false" do
|
54
|
+
subject.create_proxy("user", invalid_user).should be_false
|
55
|
+
end
|
56
|
+
|
57
|
+
it "shouldn't add user to database" do
|
58
|
+
expect{
|
59
|
+
subject.create_proxy("user", invalid_user)
|
60
|
+
}.to_not change(User, :count)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context "with a existent User zombie" do
|
65
|
+
before do
|
66
|
+
@user_zombie = User.unscoped.new(:my_id => 1)
|
67
|
+
@user_zombie.save(:validate => false)
|
68
|
+
end
|
69
|
+
let(:user) { { 'id' => 1, 'login' => 'sexy_jedi_3000',
|
70
|
+
'name' => 'Luke Skywalker' } }
|
71
|
+
|
72
|
+
it 'should return true' do
|
73
|
+
subject.create_proxy("user", user).should be_true
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'should complete User zombie' do
|
77
|
+
subject.create_proxy("user", user)
|
78
|
+
@user_zombie.reload
|
79
|
+
@user_zombie.login.should == user['login']
|
80
|
+
@user_zombie.name.should == user['name']
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context "#update_proxy" do
|
86
|
+
let(:updated_user) { { 'id' => 1, 'login' => 'sexy_jedi_3000',
|
87
|
+
'name' => 'Luke Vader' } }
|
88
|
+
before do
|
89
|
+
@user = User.create(:my_id => 1, :login => "sexy_jedi_3000",
|
90
|
+
:name => "Luke Skywalker")
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should return true" do
|
94
|
+
subject.update_proxy("user", updated_user).should be_true
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should update user on database" do
|
98
|
+
subject.update_proxy("user", updated_user)
|
99
|
+
@user.reload
|
100
|
+
@user.name.should == updated_user['name']
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
context "#destroy_proxy" do
|
105
|
+
let(:destroyed_user) { { 'id' => 1, 'login' => 'sexy_jedi_3000',
|
106
|
+
'name' => 'Luke Skywalker' } }
|
107
|
+
before do
|
108
|
+
@user = User.create(:my_id => 1, :login => "sexy_jedi_3000",
|
109
|
+
:name => "Luke Skywalker")
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should return true" do
|
113
|
+
subject.destroy_proxy("user", destroyed_user).should be_true
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should erase user from database" do
|
117
|
+
subject.destroy_proxy("user", destroyed_user)
|
118
|
+
expect {
|
119
|
+
@user.reload
|
120
|
+
}.to raise_error
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Untied::Consumer::Sync
|
4
|
+
describe PayloadProccessor do
|
5
|
+
let(:subject) { PayloadProccessor.new(config) }
|
6
|
+
let(:config) { Untied::Consumer::Sync.model_data['User'] }
|
7
|
+
|
8
|
+
describe 'proccess' do
|
9
|
+
let(:payload) {{"id"=> 22, 'login' => 'sexy_jedi_3000',
|
10
|
+
'name' => 'Luke Skywalker','thingy' => 'aaaa', 'useless_thing' => 2}}
|
11
|
+
|
12
|
+
it 'should translate mappings' do
|
13
|
+
new_load = subject.proccess(payload)
|
14
|
+
new_load.fetch("my_id", nil).should == payload["id"]
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should remove useless data' do
|
18
|
+
new_load = subject.proccess(payload)
|
19
|
+
new_load.fetch("useless_thing", nil).should be_nil
|
20
|
+
end
|
21
|
+
|
22
|
+
context "when there are no mappings" do
|
23
|
+
let(:subject) do
|
24
|
+
new_config = config.clone
|
25
|
+
new_config.delete(:mappings)
|
26
|
+
|
27
|
+
PayloadProccessor.new(new_config)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should not raise error" do
|
31
|
+
expect {
|
32
|
+
subject.proccess(payload)
|
33
|
+
}.to_not raise_error
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
4
|
+
# loaded once.
|
5
|
+
#
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
|
8
|
+
require 'untied-consumer-sync'
|
9
|
+
require 'support/setup_ar_and_schema'
|
10
|
+
require 'untied-consumer-sync/backend/backend_shared_example'
|
11
|
+
|
12
|
+
RSpec.configure do |config|
|
13
|
+
|
14
|
+
Untied::Consumer::Sync.configure do |c|
|
15
|
+
c.model_data = "spec/support/model_data.yml"
|
16
|
+
c.service_name = "my_service"
|
17
|
+
end
|
18
|
+
Untied::Consumer::Sync.backend = :active_record
|
19
|
+
|
20
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
21
|
+
config.run_all_when_everything_filtered = true
|
22
|
+
config.filter_run :focus
|
23
|
+
|
24
|
+
# Run specs in random order to surface order dependencies. If you find an
|
25
|
+
# order dependency and want to debug it, you can fix the order by providing
|
26
|
+
# the seed, which is printed after each run.
|
27
|
+
# --seed 1234
|
28
|
+
config.order = 'random'
|
29
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'active_record'
|
3
|
+
|
4
|
+
module SetupActiveRecord
|
5
|
+
# Connection
|
6
|
+
ar_config = { :test => { :adapter => 'sqlite3', :database => ":memory:" } }
|
7
|
+
ActiveRecord::Base.configurations = ar_config
|
8
|
+
ActiveRecord::Base.
|
9
|
+
establish_connection(ActiveRecord::Base.configurations[:test])
|
10
|
+
|
11
|
+
# Schema
|
12
|
+
ActiveRecord::Schema.define do
|
13
|
+
create_table :users, :force => true do |t|
|
14
|
+
t.string :my_id
|
15
|
+
t.string :login
|
16
|
+
t.string :name
|
17
|
+
t.boolean :zombie, :default => true
|
18
|
+
t.timestamps
|
19
|
+
end
|
20
|
+
create_table :toys, :force => true do |t|
|
21
|
+
t.string :my_id
|
22
|
+
t.integer :user_id
|
23
|
+
t.boolean :zombie, :default => true
|
24
|
+
t.timestamps
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Models
|
29
|
+
class ::User < ActiveRecord::Base
|
30
|
+
include Untied::Consumer::Sync::Zombificator::ActsAsZombie
|
31
|
+
attr_accessible :my_id, :login, :name
|
32
|
+
|
33
|
+
validates_presence_of :login
|
34
|
+
end
|
35
|
+
class ::Toy < ActiveRecord::Base
|
36
|
+
include Untied::Consumer::Sync::Zombificator::ActsAsZombie
|
37
|
+
attr_accessible :my_id, :user_id
|
38
|
+
end
|
39
|
+
end
|
data/spec/sync_spec.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Untied::Consumer
|
4
|
+
describe Sync do
|
5
|
+
describe '.configure' do
|
6
|
+
it 'calls init_untied' do
|
7
|
+
Sync.should_receive(:init_untied)
|
8
|
+
Sync.configure
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '.model_data' do
|
13
|
+
it 'returns the read yml' do
|
14
|
+
Sync.model_data.should == {
|
15
|
+
'User' => {
|
16
|
+
'attributes' => ['login', 'name', 'id'],
|
17
|
+
'mappings' => { 'id' => 'my_id' },
|
18
|
+
'name' => 'User'
|
19
|
+
},
|
20
|
+
'Toy' => {
|
21
|
+
'attributes' => ['user_id', 'id'],
|
22
|
+
'mappings' => { 'id' => 'my_id' },
|
23
|
+
'check_for' => { 'User' => 'user_id' },
|
24
|
+
'name' => 'Toy'
|
25
|
+
},
|
26
|
+
}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '.backend=' do
|
31
|
+
before do
|
32
|
+
Sync.backend = nil
|
33
|
+
end
|
34
|
+
|
35
|
+
after do
|
36
|
+
Sync.backend = :active_record
|
37
|
+
end
|
38
|
+
|
39
|
+
let(:klass) { Class.new }
|
40
|
+
|
41
|
+
it 'sets backend to klass' do
|
42
|
+
Sync.backend = klass
|
43
|
+
Sync.backend.should == klass
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'sets backend using symbol' do
|
47
|
+
Sync.backend = :active_record
|
48
|
+
Sync.backend.to_s.should == "#{Sync}::Backend::ActiveRecord" \
|
49
|
+
"::ModelHelper"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "untied-consumer-sync"
|
2
|
+
|
3
|
+
module Untied
|
4
|
+
module Consumer
|
5
|
+
module Sync
|
6
|
+
module Backend
|
7
|
+
module ActiveRecord
|
8
|
+
class ModelHelper
|
9
|
+
include Sync::Backend::Base
|
10
|
+
|
11
|
+
# Public: Procura o modelo pelo id.
|
12
|
+
#
|
13
|
+
# id - Inteiro que indentifica o objeto de acordo a configuração.
|
14
|
+
#
|
15
|
+
# Retorna o caso o modelo seja encontrado ou nil caso o modelo não exista
|
16
|
+
# no banco.
|
17
|
+
def find(id)
|
18
|
+
# Unscoped para encontrar zombies
|
19
|
+
@model.unscoped.send("find_by_#{@model_data['mappings']['id']}", id)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Untied::Consumer::Sync
|
4
|
+
describe UntiedGeneralObserver do
|
5
|
+
let(:subject) { UntiedGeneralObserver.instance }
|
6
|
+
|
7
|
+
describe ".initialize" do
|
8
|
+
before do
|
9
|
+
subject
|
10
|
+
end
|
11
|
+
|
12
|
+
# Observed classes defined at spec/support/model_data.yml
|
13
|
+
it "should define observed classes" do
|
14
|
+
subject.observed_classes.to_set.should == [:toy, :user].to_set
|
15
|
+
end
|
16
|
+
|
17
|
+
# Observed service name defined at spec_helper
|
18
|
+
it "should define observed service" do
|
19
|
+
subject.observed_service.should == :my_service
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "#after_create" do
|
24
|
+
it "should call create_proxy with kind and payload" do
|
25
|
+
subject.should_receive(:create_proxy).with('user', { 'id' => 1})
|
26
|
+
subject.after_create({ 'user' => { 'id' => 1} })
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "#after_update" do
|
31
|
+
it "should call update_proxy with kind and payload" do
|
32
|
+
subject.should_receive(:update_proxy).with('user', { 'id' => 1})
|
33
|
+
subject.after_update({ 'user' => { 'id' => 1} })
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "#after_destroy" do
|
38
|
+
it "should call destroy_proxy with kind and payload" do
|
39
|
+
subject.should_receive(:destroy_proxy).with('user', { 'id' => 1})
|
40
|
+
subject.after_destroy({ 'user' => { 'id' => 1} })
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'untied-consumer-sync/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "untied-consumer-sync"
|
8
|
+
gem.version = Untied::Consumer::Sync::VERSION
|
9
|
+
gem.authors = ["Igor Calabria", "Guilherme Cavalcanti", "Tiago Ferreira", "Juliana Lucena"]
|
10
|
+
gem.email = ["igor.calabria@gmail.com", "guiocavalcanti@gmail.com", "fltiago@gmail.com", "julianalucenaa@gmail.com"]
|
11
|
+
gem.description = %q{Untied Consumer Synchronizer.}
|
12
|
+
gem.summary = %q{Process the messages comming from your Untied::Publisher and syncs it directly to the database.}
|
13
|
+
gem.homepage = "http://github.com/redu/untied-consumer-sync"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(spec)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_development_dependency 'rake'
|
21
|
+
gem.add_development_dependency 'rspec'
|
22
|
+
gem.add_development_dependency 'sqlite3'
|
23
|
+
gem.add_development_dependency 'activerecord'
|
24
|
+
|
25
|
+
gem.add_runtime_dependency 'untied-consumer', '~> 0.0.5'
|
26
|
+
gem.add_runtime_dependency 'configurable'
|
27
|
+
|
28
|
+
if RUBY_VERSION < "1.9"
|
29
|
+
gem.add_development_dependency "ruby-debug"
|
30
|
+
else
|
31
|
+
gem.add_development_dependency "debugger"
|
32
|
+
end
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,201 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: untied-consumer-sync
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Igor Calabria
|
14
|
+
- Guilherme Cavalcanti
|
15
|
+
- Tiago Ferreira
|
16
|
+
- Juliana Lucena
|
17
|
+
autorequire:
|
18
|
+
bindir: bin
|
19
|
+
cert_chain: []
|
20
|
+
|
21
|
+
date: 2013-01-03 00:00:00 Z
|
22
|
+
dependencies:
|
23
|
+
- !ruby/object:Gem::Dependency
|
24
|
+
name: rake
|
25
|
+
prerelease: false
|
26
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
27
|
+
none: false
|
28
|
+
requirements:
|
29
|
+
- - ">="
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
hash: 3
|
32
|
+
segments:
|
33
|
+
- 0
|
34
|
+
version: "0"
|
35
|
+
type: :development
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: rspec
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 3
|
46
|
+
segments:
|
47
|
+
- 0
|
48
|
+
version: "0"
|
49
|
+
type: :development
|
50
|
+
version_requirements: *id002
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
name: sqlite3
|
53
|
+
prerelease: false
|
54
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 3
|
60
|
+
segments:
|
61
|
+
- 0
|
62
|
+
version: "0"
|
63
|
+
type: :development
|
64
|
+
version_requirements: *id003
|
65
|
+
- !ruby/object:Gem::Dependency
|
66
|
+
name: activerecord
|
67
|
+
prerelease: false
|
68
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
hash: 3
|
74
|
+
segments:
|
75
|
+
- 0
|
76
|
+
version: "0"
|
77
|
+
type: :development
|
78
|
+
version_requirements: *id004
|
79
|
+
- !ruby/object:Gem::Dependency
|
80
|
+
name: untied-consumer
|
81
|
+
prerelease: false
|
82
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ~>
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
hash: 21
|
88
|
+
segments:
|
89
|
+
- 0
|
90
|
+
- 0
|
91
|
+
- 5
|
92
|
+
version: 0.0.5
|
93
|
+
type: :runtime
|
94
|
+
version_requirements: *id005
|
95
|
+
- !ruby/object:Gem::Dependency
|
96
|
+
name: configurable
|
97
|
+
prerelease: false
|
98
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
99
|
+
none: false
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
hash: 3
|
104
|
+
segments:
|
105
|
+
- 0
|
106
|
+
version: "0"
|
107
|
+
type: :runtime
|
108
|
+
version_requirements: *id006
|
109
|
+
- !ruby/object:Gem::Dependency
|
110
|
+
name: ruby-debug
|
111
|
+
prerelease: false
|
112
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
hash: 3
|
118
|
+
segments:
|
119
|
+
- 0
|
120
|
+
version: "0"
|
121
|
+
type: :development
|
122
|
+
version_requirements: *id007
|
123
|
+
description: Untied Consumer Synchronizer.
|
124
|
+
email:
|
125
|
+
- igor.calabria@gmail.com
|
126
|
+
- guiocavalcanti@gmail.com
|
127
|
+
- fltiago@gmail.com
|
128
|
+
- julianalucenaa@gmail.com
|
129
|
+
executables: []
|
130
|
+
|
131
|
+
extensions: []
|
132
|
+
|
133
|
+
extra_rdoc_files: []
|
134
|
+
|
135
|
+
files:
|
136
|
+
- .gitignore
|
137
|
+
- .travis.yml
|
138
|
+
- Gemfile
|
139
|
+
- LICENSE.txt
|
140
|
+
- README.md
|
141
|
+
- Rakefile
|
142
|
+
- lib/untied-consumer-sync.rb
|
143
|
+
- lib/untied-consumer-sync/backend/backend_shared_example.rb
|
144
|
+
- lib/untied-consumer-sync/backend/base.rb
|
145
|
+
- lib/untied-consumer-sync/config.rb
|
146
|
+
- lib/untied-consumer-sync/observer_helper.rb
|
147
|
+
- lib/untied-consumer-sync/payload_proccessor.rb
|
148
|
+
- lib/untied-consumer-sync/untied_general_observer.rb
|
149
|
+
- lib/untied-consumer-sync/version.rb
|
150
|
+
- lib/untied-consumer-sync/zombificator.rb
|
151
|
+
- spec/observer_helper_spec.rb
|
152
|
+
- spec/payload_proccessor_spec.rb
|
153
|
+
- spec/spec_helper.rb
|
154
|
+
- spec/support/model_data.yml
|
155
|
+
- spec/support/setup_ar_and_schema.rb
|
156
|
+
- spec/sync_spec.rb
|
157
|
+
- spec/untied-consumer-sync-activerecord/backend/active_record.rb
|
158
|
+
- spec/untied_general_observer_spec.rb
|
159
|
+
- untied-consumer-sync.gemspec
|
160
|
+
homepage: http://github.com/redu/untied-consumer-sync
|
161
|
+
licenses: []
|
162
|
+
|
163
|
+
post_install_message:
|
164
|
+
rdoc_options: []
|
165
|
+
|
166
|
+
require_paths:
|
167
|
+
- lib
|
168
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
169
|
+
none: false
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
hash: 3
|
174
|
+
segments:
|
175
|
+
- 0
|
176
|
+
version: "0"
|
177
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
178
|
+
none: false
|
179
|
+
requirements:
|
180
|
+
- - ">="
|
181
|
+
- !ruby/object:Gem::Version
|
182
|
+
hash: 3
|
183
|
+
segments:
|
184
|
+
- 0
|
185
|
+
version: "0"
|
186
|
+
requirements: []
|
187
|
+
|
188
|
+
rubyforge_project:
|
189
|
+
rubygems_version: 1.8.24
|
190
|
+
signing_key:
|
191
|
+
specification_version: 3
|
192
|
+
summary: Process the messages comming from your Untied::Publisher and syncs it directly to the database.
|
193
|
+
test_files:
|
194
|
+
- spec/observer_helper_spec.rb
|
195
|
+
- spec/payload_proccessor_spec.rb
|
196
|
+
- spec/spec_helper.rb
|
197
|
+
- spec/support/model_data.yml
|
198
|
+
- spec/support/setup_ar_and_schema.rb
|
199
|
+
- spec/sync_spec.rb
|
200
|
+
- spec/untied-consumer-sync-activerecord/backend/active_record.rb
|
201
|
+
- spec/untied_general_observer_spec.rb
|