la_maquina 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +173 -24
- data/lib/la_maquina/error_notifier/honeybadger_notifier.rb +1 -1
- data/lib/la_maquina/version.rb +1 -1
- data/test/dummy/config/la_maquina/dependency_maps/deep_map.yml +7 -0
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/unit/dependency_maps/yamp_map_test.rb +6 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cfe629f6f7df101c2c54c842e0fdbd8c05e0f663
|
4
|
+
data.tar.gz: 0f7cacda20c45ae5a0bad8db868cecc60ad4a455
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 18482c2158276e90c16d474ae744f2431d6ced0a3a2ff4111914016becf9fdbd2330ba86837fdf732ce14b793cdb7bebe7c482916a26a8330dbaea8f983e1458
|
7
|
+
data.tar.gz: 7b2626cbb36fac8bed0e69b4418a33be3a63d5f996cefb924b616d7ac3f21ce7e343a8907005454a7b62a7f98c548c87a98116372accab23bbb077084adb1355
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,31 +1,96 @@
|
|
1
1
|
# La Maquina
|
2
2
|
|
3
|
-
|
3
|
+
Non-database-based arbitrary updates of `belongs_to` associated ActiveRecord models.
|
4
4
|
|
5
|
-
|
5
|
+
Let's say you have 2 models
|
6
|
+
```ruby
|
7
|
+
class DannyTrejo < ActiveRecord::Base
|
8
|
+
has_many :machetes
|
9
|
+
end
|
10
|
+
```
|
11
|
+
and
|
12
|
+
```ruby
|
13
|
+
class Machete < ActiveRecord::Base
|
14
|
+
belongs_to :danny_trejo
|
15
|
+
end
|
16
|
+
```
|
17
|
+
and you want to let `DannyTrejo` know when a `Machete` has been updated, but you don't want to use `ActiveRecord`'s `touch`, you can use this gem to execute arbitrary code when `Machete` updates.
|
6
18
|
|
7
|
-
|
19
|
+
## Example
|
8
20
|
|
9
|
-
|
21
|
+
Using the example above, let's say that when a `Machete` is added, we want its corresponding `DannyTrejo` object to be reindexed by Solr, using the Sunspot interface. With a little bit of config magic, described at the end of this document, we have this:
|
10
22
|
|
11
|
-
|
23
|
+
```ruby
|
24
|
+
class DannyTrejo < ActiveRecord::Base
|
25
|
+
has_many :machetes
|
12
26
|
|
13
|
-
|
27
|
+
searchable do
|
28
|
+
double :machete_sharpness, multiple: true do
|
29
|
+
machetes.map(&:sharpness)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
```
|
14
34
|
|
15
|
-
|
35
|
+
```ruby
|
36
|
+
class Machete < ActiveRecord::Base
|
37
|
+
belongs_to :danny_trejo
|
16
38
|
|
17
|
-
|
39
|
+
include LaMaquina::Notifier
|
40
|
+
notifies_about :danny_trejo
|
41
|
+
end
|
42
|
+
```
|
18
43
|
|
19
44
|
## Usage
|
20
45
|
|
46
|
+
There are 4 main components to this gem:
|
47
|
+
|
48
|
+
* `notifies_about`: defines the association (eg `Machete` -> `DannyTrejo`)
|
49
|
+
* Pistons: the plugins that define the behavior (eg `DannyTrejo.find(id).solr_index!`)
|
50
|
+
* ErrorNotifier: defines what you do with the errors once they come up
|
51
|
+
* DependencyMap: define the mapping between the notifier and notified classes. *OPTIONAL*
|
21
52
|
|
53
|
+
### notifies_about options
|
54
|
+
|
55
|
+
`notifies_about target, options ` is an interface that and mirrors ActiveRecord associations.
|
56
|
+
to use it, in your `ActiveRecord::Base` model
|
57
|
+
```ruby
|
58
|
+
include LaMaquina::Notifier
|
59
|
+
```
|
60
|
+
It can either notify LaMaquina about the object itself with `notifies_about :self`, or about a `belongs_to` association with the following options:
|
61
|
+
|
62
|
+
* `:through`: same as rails. Note: expects the through object belongs_to the target object
|
63
|
+
* `:polymorphic`: same as rails. Note: expects rails default target `_type` and targe `_id` fields to be present
|
64
|
+
* `:class_name`: takes a modulized camelcased string name of the target class
|
65
|
+
* `:class`: takes a class constant of the target class type
|
66
|
+
* `:using`: this allows you to send update messages through a ruby interface (eg [JsonApiClient](https://github.com/chingor13/json_api_client)). The interface has to respond to `notify( params = {} )`.
|
67
|
+
The params are as follows:
|
68
|
+
* `:notified_class`: demodulized snake_cased name of class that is notified about (eg "danny_trejo")
|
69
|
+
* `notified_id`: the id of thje object that's being notified about. So if `Machete`(8) belongs to `DannyTrejo`(1), it will be 1
|
70
|
+
* `notifier_class`: demodulized snake_cased name of the notifier class (eg "machete")
|
71
|
+
**Note:** This requires the receiving side to call `LaMaquina::Engine.notify( notifier_class, id, notified_class = "" )`, so you'll have something like
|
72
|
+
```ruby
|
73
|
+
class LaMaquinaController < ApplicationController
|
74
|
+
def notify
|
75
|
+
notified_class = params[:notified_class]
|
76
|
+
notified_id = params[:notified_id]
|
77
|
+
notifier_class = params[:notifier_class]
|
78
|
+
|
79
|
+
LaMaquina::Engine.notify notifier_class, id, notified_class
|
80
|
+
|
81
|
+
render json: {success: true}
|
82
|
+
end
|
83
|
+
end
|
84
|
+
```
|
85
|
+
Note: `class` and `class_name` options aren't stricly checked; they're formatted and sent through
|
22
86
|
|
23
87
|
### Pistons
|
24
88
|
|
25
|
-
|
89
|
+
A piston is a plugin that can be fired on update.
|
26
90
|
|
27
|
-
|
91
|
+
Once a model with a `notifies_about :whatever` gets updated and a commit happens, it will fire off a call to wherever LaMaquina is installed (either locally, or on another server if you're notifying through `JsonApiClient` or similar). Once LaMaquina::Engine receives the call it will fire all of its pistons in no particualr order.
|
28
92
|
|
93
|
+
So using the example from above:
|
29
94
|
```ruby
|
30
95
|
class DannyTrejo < ActiveRecord::Base
|
31
96
|
has_many :machetes
|
@@ -45,15 +110,14 @@ class Machete < ActiveRecord::Base
|
|
45
110
|
notifies_about :danny_trejo
|
46
111
|
end
|
47
112
|
```
|
48
|
-
|
113
|
+
We want to set up a piston that will reindex `DannyTrejo` on `Machete` update.
|
49
114
|
|
50
|
-
`Machete` is set up to fire on update, so we'll set up a listener Piston that looks like this:
|
51
115
|
|
52
116
|
```ruby
|
53
117
|
class SunspotPiston < LaMaquina::Piston::Base
|
54
118
|
class << self
|
55
119
|
def fire!( notified_class, id, notifier_class = "" )
|
56
|
-
indexed_class(notified_class).find(id).
|
120
|
+
indexed_class(notified_class).find(id).solr_index!
|
57
121
|
end
|
58
122
|
|
59
123
|
private
|
@@ -65,26 +129,78 @@ class SunspotPiston < LaMaquina::Piston::Base
|
|
65
129
|
end
|
66
130
|
|
67
131
|
```
|
68
|
-
|
132
|
+
All it needs to do is respond ot `fire!( notified_class, id, notifier_class = "" )` and what happend from there is entirely up to you.
|
69
133
|
|
70
|
-
### Setup
|
71
134
|
|
72
|
-
The
|
135
|
+
The piston above doesn't use `notifier_class`, but that comes in quite handy should you want to do complex manipulations.For example, if you have a model that has several associations that respond to `complex_code` and you want to cache that code under a composite cache key of "#{top_object}/#{association}:#{id}", you can do something like:
|
136
|
+
|
137
|
+
```ruby
|
138
|
+
class CompositeCachePison < LaMaquina::Piston::Base
|
139
|
+
class << self
|
140
|
+
class_attribute :redis, :map
|
141
|
+
|
142
|
+
def fire!( notified_class, id, notifier_class )
|
143
|
+
key = "#{notified_class}/#{notifier_class}:#{id}"
|
144
|
+
|
145
|
+
klass = map.mapping_for notified_class
|
146
|
+
object = klass.find(id)
|
73
147
|
|
74
|
-
|
75
|
-
|
148
|
+
# because notifier_class is already snaked we can just send it as an association
|
149
|
+
result = object.send(notifier_class).complex_code
|
150
|
+
|
151
|
+
redis.set key, result
|
152
|
+
end
|
153
|
+
|
154
|
+
# explained below
|
155
|
+
self.map = LaMaquina::DependencyMap::ConstantMap.new
|
156
|
+
end
|
157
|
+
```
|
158
|
+
|
159
|
+
### DependencyMap
|
160
|
+
|
161
|
+
`DependencyMap` is a way to abstract away the dependency structure from the gem and the piston (like you have in the first piston example).
|
162
|
+
|
163
|
+
The interface looks like
|
76
164
|
|
77
165
|
```ruby
|
78
|
-
LaMaquina::
|
79
|
-
|
80
|
-
|
166
|
+
class Map < LaMaquina::DependencyMap::Base
|
167
|
+
# defined in Base
|
168
|
+
# initialize( yaml_path = nil)
|
169
|
+
|
170
|
+
def mapping_for(*args)
|
171
|
+
# your code here
|
172
|
+
end
|
173
|
+
|
174
|
+
# also defined in Base
|
175
|
+
# attr_accessor :map
|
176
|
+
end
|
177
|
+
```
|
178
|
+
LaMaquina comes with 2 default maps: `ConstantMap` and `YamlMap`.
|
179
|
+
|
180
|
+
`LaMaquina::DependencyMap::ConstantMap` takes a string and tries to constantize it. It's not strictly speaking a map, but it works as you would expect:
|
181
|
+
```ruby
|
182
|
+
map = LaMaquina::DependencyMap::ConstantMap.new
|
183
|
+
map.mapping_for "danny_trejo" # => DannyTrejo(id: integer, ...)
|
184
|
+
```
|
185
|
+
`LaMaquina::DependencyMap::YamlMap` get initialized with a yaml path, parses the yaml and spits out a dependency at any depth, meaning:
|
186
|
+
```yml
|
187
|
+
# map.yml
|
188
|
+
danny_trejo:
|
189
|
+
machete:
|
190
|
+
1: favorite
|
191
|
+
2: dull
|
81
192
|
```
|
82
|
-
|
83
|
-
|
193
|
+
```ruby
|
194
|
+
map = LaMaquina::DependencyMap::YamlMap.new "#{Rail.root}/config/map.yml"
|
195
|
+
map.mapping_for "danny_trejo", "machete", 1 # => "favorite"
|
196
|
+
```
|
197
|
+
|
198
|
+
### ErrorNotifier
|
199
|
+
LaMaquina by default comes with an `ErrorNotifier::Base` that will explode in a very unhelpful manner. To override it, you need to change it in the config above and roll a new `ErrorNotifier` that responds to `notify(error, details)`. For example, if you're using Honeybadger, you can use the included `LaMaquina::ErrorNotifiers::HoneybadgerNotifier`, which looks like:
|
84
200
|
```ruby
|
85
201
|
class HoneybadgerNotifier < LaMaquina::ErrorNotifier::Base
|
86
202
|
self.notify(error = nil, details = {})
|
87
|
-
Honeybadger.notify( :error_class => "
|
203
|
+
Honeybadger.notify( :error_class => "LaMaquinaError: #{error.class.name}",
|
88
204
|
:error_message => error.message,
|
89
205
|
:parameters => details
|
90
206
|
)
|
@@ -96,6 +212,39 @@ If you *don't* care about your exceptions and want to ignore them, there's a not
|
|
96
212
|
LaMaquina::Cinegual.error_notifier = LaMaquina::ErrorNotifier::SilentNotifier
|
97
213
|
```
|
98
214
|
|
215
|
+
## Setup
|
216
|
+
|
217
|
+
The setup is pretty straightforward: you do all the setting up in `config/initializers/la_maquina.rb`.
|
218
|
+
|
219
|
+
The things you have to do are: set up the pistons(if they need configuring), install them, and configure the error_handler.
|
220
|
+
For example, if you're using the CompositeCachePison and need to set up Redis, here's how your `la_maquina.rb` will look
|
221
|
+
|
222
|
+
```ruby
|
223
|
+
CompositeCachePison.redis = Redis::Namespace.new(:cache_piston, redis: Redis.new)
|
224
|
+
# you would initialize the map here, not in the piston
|
225
|
+
CompositeCachePison.map = LaMaquina::DependencyMap::ConstantMap.new
|
226
|
+
|
227
|
+
LaMaquina::Engine.install CompositeCachePison
|
228
|
+
|
229
|
+
LaMaquina.error_notifier = LaMaquina::ErrorNotifier::HoneybadgerNotifier
|
230
|
+
```
|
231
|
+
|
232
|
+
|
233
|
+
## Installation
|
234
|
+
|
235
|
+
Add this line to your application's Gemfile:
|
236
|
+
|
237
|
+
gem 'la_maquina'
|
238
|
+
|
239
|
+
And then execute:
|
240
|
+
|
241
|
+
$ bundle
|
242
|
+
|
243
|
+
Or install it yourself as:
|
244
|
+
|
245
|
+
$ gem install la_maquina
|
246
|
+
|
247
|
+
|
99
248
|
## Contributing
|
100
249
|
|
101
250
|
1. Fork it ( https://github.com/[my-github-username]/la_maquina/fork )
|
@@ -2,7 +2,7 @@ module LaMaquina
|
|
2
2
|
module ErrorNotifier
|
3
3
|
class HoneybadgerNotifier < LaMaquina::ErrorNotifier::Base
|
4
4
|
def notify(error, details = {})
|
5
|
-
Honeybadger.notify( :error_class => "
|
5
|
+
Honeybadger.notify( :error_class => "LaMaquinaError: #{error.class.name}",
|
6
6
|
:error_message => error.message,
|
7
7
|
:parameters => details
|
8
8
|
)
|
data/lib/la_maquina/version.rb
CHANGED
data/test/dummy/db/test.sqlite3
CHANGED
Binary file
|
@@ -23,4 +23,10 @@ class YamlMapTest < ActiveSupport::TestCase
|
|
23
23
|
assert 'admin', @map.mapping_for(:admin)
|
24
24
|
assert :admin, @map.mapping_for(:admin)
|
25
25
|
end
|
26
|
+
|
27
|
+
def test_can_go_to_any_depth
|
28
|
+
map = LaMaquina::DependencyMap::YamlMap.new( Rails.root + 'config/la_maquina/dependency_maps/deep_map.yml' )
|
29
|
+
assert_equal "trait", map.mapping_for( "admin", "trait" )
|
30
|
+
assert_equal "b", map.mapping_for( "guest", "trait", 1 )
|
31
|
+
end
|
26
32
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: la_maquina
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Greg Orlov
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-08-
|
11
|
+
date: 2015-08-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -113,6 +113,7 @@ files:
|
|
113
113
|
- test/dummy/config/initializers/session_store.rb
|
114
114
|
- test/dummy/config/initializers/wrap_parameters.rb
|
115
115
|
- test/dummy/config/la_maquina/dependency_maps/bad_map.yml
|
116
|
+
- test/dummy/config/la_maquina/dependency_maps/deep_map.yml
|
116
117
|
- test/dummy/config/la_maquina/dependency_maps/sunspot_piston.yml
|
117
118
|
- test/dummy/config/locales/en.yml
|
118
119
|
- test/dummy/config/routes.rb
|
@@ -206,6 +207,7 @@ test_files:
|
|
206
207
|
- test/dummy/config/initializers/session_store.rb
|
207
208
|
- test/dummy/config/initializers/wrap_parameters.rb
|
208
209
|
- test/dummy/config/la_maquina/dependency_maps/bad_map.yml
|
210
|
+
- test/dummy/config/la_maquina/dependency_maps/deep_map.yml
|
209
211
|
- test/dummy/config/la_maquina/dependency_maps/sunspot_piston.yml
|
210
212
|
- test/dummy/config/locales/en.yml
|
211
213
|
- test/dummy/config/routes.rb
|