guacamole 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.hound.yml +3 -0
- data/{config/reek.yml → .reek.yml} +5 -0
- data/.travis.yml +3 -0
- data/CHANGELOG.md +19 -0
- data/GOALS.md +20 -0
- data/Gemfile +1 -11
- data/Guardfile +0 -4
- data/README.md +147 -11
- data/Rakefile +33 -3
- data/guacamole.gemspec +12 -1
- data/lib/guacamole.rb +1 -0
- data/lib/guacamole/callbacks.rb +259 -0
- data/lib/guacamole/collection.rb +50 -32
- data/lib/guacamole/configuration.rb +92 -15
- data/lib/guacamole/model.rb +39 -1
- data/lib/guacamole/railtie.rb +9 -3
- data/lib/guacamole/version.rb +1 -1
- data/lib/rails/generators/guacamole/callbacks/callbacks_generator.rb +26 -0
- data/lib/rails/generators/guacamole/callbacks/templates/callbacks.rb.tt +13 -0
- data/lib/rails/generators/rspec/callbacks/callbacks_generator.rb +14 -0
- data/lib/rails/generators/rspec/callbacks/templates/callbacks_spec.rb.tt +7 -0
- data/lib/rails/generators/test_unit/callbacks/callbacks_generator.rb +13 -0
- data/lib/rails/generators/test_unit/callbacks/templates/callbacks_test.rb.tt +9 -0
- data/lib/rails/generators/test_unit/collection/collection_generator.rb +13 -0
- data/lib/rails/generators/test_unit/collection/templates/collection_test.rb.tt +10 -0
- data/spec/acceptance/aql_spec.rb +0 -12
- data/spec/acceptance/basic_spec.rb +16 -25
- data/spec/acceptance/callbacks_spec.rb +181 -0
- data/spec/acceptance/config/guacamole.yml +1 -1
- data/spec/acceptance/spec_helper.rb +24 -6
- data/spec/fabricators/article.rb +12 -0
- data/spec/fabricators/comment.rb +7 -0
- data/spec/fabricators/pony.rb +6 -4
- data/spec/fabricators/pony_fabricator.rb +7 -0
- data/spec/spec_helper.rb +5 -4
- data/spec/support/guacamole.yml.erb +5 -0
- data/spec/unit/callbacks_spec.rb +139 -0
- data/spec/unit/collection_spec.rb +85 -66
- data/spec/unit/configuration_spec.rb +165 -21
- data/spec/unit/identiy_map_spec.rb +2 -2
- data/spec/unit/model_spec.rb +36 -3
- metadata +181 -12
- data/Gemfile.devtools +0 -67
- data/config/devtools.yml +0 -4
- data/config/flay.yml +0 -3
- data/config/flog.yml +0 -2
- data/config/mutant.yml +0 -3
- data/config/yardstick.yml +0 -2
- data/tasks/adjustments.rake +0 -34
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: da19a1adc7e260053f9749aaaa5af277e706fbda
|
4
|
+
data.tar.gz: 08392381e86a454d91a3da30efac6b86599234be
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 40ec254c0113a1f8ee0bc2dbdb8624c2d5f3c0ea55cb1e981e45d91b38eb4167f590896cce392d9c5a8419ddea208ce29600f116b24fbf7f2108aa0181c54b47
|
7
|
+
data.tar.gz: b7b301e8c84692d620316a29e02ee654b6358427e5e92016957a7f66aeb9b249268eacffffe9d715bb28f7508f9ec3ca8746ef92f043326c56ecc7a0f4cab708
|
data/.hound.yml
CHANGED
@@ -22,6 +22,8 @@ DuplicateMethodCall:
|
|
22
22
|
exclude:
|
23
23
|
- Guacamole::DocumentModelMapper#document_to_model
|
24
24
|
- Guacamole::DocumentModelMapper#model_to_document
|
25
|
+
- Guacamole::Configuration#create_database_connection
|
26
|
+
- Guacamole::Callbacks::CallbackProxy#run_callbacks
|
25
27
|
max_calls: 1
|
26
28
|
allow_calls: []
|
27
29
|
FeatureEnvy:
|
@@ -59,6 +61,7 @@ TooManyInstanceVariables:
|
|
59
61
|
enabled: true
|
60
62
|
exclude:
|
61
63
|
- Guacamole::DocumentModelMapper
|
64
|
+
- Guacamole::Configuration::ConfigStruct
|
62
65
|
max_instance_variables: 3
|
63
66
|
TooManyMethods:
|
64
67
|
enabled: true
|
@@ -72,6 +75,8 @@ TooManyStatements:
|
|
72
75
|
- Guacamole::DocumentModelMapper#model_to_document
|
73
76
|
- Guacamole::Collection::ClassMethods#create_document_from
|
74
77
|
- Guacamole::Collection::ClassMethods#create_referenced_by_models_of
|
78
|
+
- Guacamole::Configuration#create_database_connection
|
79
|
+
- Guacamole::Configuration::ConfigStruct#init_from_uri_string
|
75
80
|
max_statements: 5
|
76
81
|
UncommunicativeMethodName:
|
77
82
|
enabled: true
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,22 @@
|
|
1
|
+
## Version 0.3.0
|
2
|
+
|
3
|
+
**Codename: Spread your Wings**
|
4
|
+
|
5
|
+
This release improved the configurability of Guacamole, updated Ashikawa::Core, enhanced the README and added callbacks.
|
6
|
+
|
7
|
+
Notable changes are:
|
8
|
+
|
9
|
+
* Implemented external callbacks
|
10
|
+
* Added support for `DATABASE_URL`
|
11
|
+
* Parse `guacamole.yml` with ERB
|
12
|
+
* Updated Ashikawa::Core to 0.12.0
|
13
|
+
* Improved README
|
14
|
+
* Fixed generators with `test_unit`
|
15
|
+
* Internal changes
|
16
|
+
* Removed `debugger` in favor of `pry`
|
17
|
+
* Removed `devtools`
|
18
|
+
|
19
|
+
|
1
20
|
## Version 0.2.0
|
2
21
|
|
3
22
|
**Codename: Into the Storm**
|
data/GOALS.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# Goals of this project
|
2
|
+
|
3
|
+
* Support building web applications with Ruby on Rails or other Rack-based frameworks
|
4
|
+
* Support for Rails Views for example
|
5
|
+
* Allow Rails developers to get into Guacamole as easy as possible
|
6
|
+
* Reflect the nature of NoSQL in general and ArangoDB in particular
|
7
|
+
* Use the Datamapper pattern, because it allows us to leverage ArangoDB features like nesting in a really nice way
|
8
|
+
* Support ArangoDB features like transactions, the query language and graphs
|
9
|
+
|
10
|
+
*The two main goals may conflict from time to time.*
|
11
|
+
|
12
|
+
## Features of Guacamole 1.0
|
13
|
+
|
14
|
+
**This is definitely a moving target and up to discussion. For non-moving targets refer to our [milestones](https://github.com/triAGENS/guacamole/issues/milestones).**
|
15
|
+
|
16
|
+
* **Embrace the Multi Model nature of ArangoDB:** It will be possible to connect entities with edges. These edges will have custom classes. It will be possible to do graph queries using these connections. There is already a [discussion about how to do this](https://github.com/triAGENS/guacamole/issues/74).
|
17
|
+
* **Support for the ArangoDB Query Language:** ArangoDB has a powerful querying language. We want to provide support for it in the most Ruby-way possible. There is already a [discussion about how to do this](https://github.com/moonglum/brazil/issues/8).
|
18
|
+
* **Support for Transactions:** ArangoDB has support for transactions, we want to make it as easy as possible to use them.
|
19
|
+
* **Support for Migrations:** Migrations work differently in a database with flexible schema. Some migrations are possible on the fly, but there will still be migrations that need to be executed explicitly. We want to support Rails-like migrations for these types of queries.
|
20
|
+
* **Support for flexible mapping:** Right now the mapping is very strict, we will allow more flexibility here including renaming or excluding certain attributes.
|
data/Gemfile
CHANGED
@@ -3,14 +3,4 @@ source 'https://rubygems.org'
|
|
3
3
|
|
4
4
|
gemspec
|
5
5
|
|
6
|
-
|
7
|
-
group :development, :test do
|
8
|
-
gem 'devtools', git: 'https://github.com/rom-rb/devtools.git'
|
9
|
-
gem 'guard-yard'
|
10
|
-
end
|
11
|
-
eval_gemfile 'Gemfile.devtools'
|
12
|
-
|
13
|
-
# Local debugging
|
14
|
-
group :debug do
|
15
|
-
gem 'debugger'
|
16
|
-
end
|
6
|
+
gem 'rspec-its', git: 'https://github.com/rspec/rspec-its.git'
|
data/Guardfile
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
[![RubyDoc](http://img.shields.io/badge/📄-RubyDoc-be1d77.svg)](http://rubydoc.info/gems/guacamole/frames)
|
2
2
|
[![Build Status](http://img.shields.io/travis/triAGENS/guacamole.svg)](https://travis-ci.org/triAGENS/guacamole)
|
3
3
|
[![Code Climate](http://img.shields.io/codeclimate/github/triAGENS/guacamole.svg)](https://codeclimate.com/github/triAGENS/guacamole)
|
4
|
+
[![Inline docs](http://inch-ci.org/github/triAGENS/guacamole.svg)](http://inch-ci.org/github/triAGENS/guacamole)
|
4
5
|
[![Gem Version](http://img.shields.io/gem/v/guacamole.svg)](https://rubygems.org/gems/guacamole)
|
5
6
|
|
6
7
|
# Guacamole
|
@@ -24,7 +25,7 @@ Since Guacamole is in an alpha state we suggest you to create a new Rails applic
|
|
24
25
|
First of all create your shiny new application, without ActiveRecord of course:
|
25
26
|
|
26
27
|
```shell
|
27
|
-
rails new -
|
28
|
+
rails new --skip-active-record $my_awesome_app
|
28
29
|
```
|
29
30
|
|
30
31
|
Add this line to your application's Gemfile:
|
@@ -39,6 +40,24 @@ And then install the new dependencies:
|
|
39
40
|
bundle install
|
40
41
|
```
|
41
42
|
|
43
|
+
### Adding Guacamole to an existing Rails application
|
44
|
+
|
45
|
+
Maybe you're bold and want to add Guacamole to an existing Rails application. In this case some things are different, because you already have an ORM configured. Throughout the remaining README you will find examples of using generators and rake tasks to support you. All the generators must be invoked with the `--orm guacamole` flag. Without this you will generate both, ActiveRecord and Guacamole files:
|
46
|
+
|
47
|
+
```shell
|
48
|
+
bundle exec rails generate model pony name:string birthday:date color:string --orm guacamole
|
49
|
+
```
|
50
|
+
|
51
|
+
Guacamole will not overwrite existing rake tasks and thus you need to invoke them with under the guacamole namespace:
|
52
|
+
|
53
|
+
```shell
|
54
|
+
rake db:guacamole:create
|
55
|
+
rake db:guacamole:purge
|
56
|
+
rake db:guacamole:drop
|
57
|
+
```
|
58
|
+
|
59
|
+
Everything else should work as described in the README. If you encounter any errors while working with an existing Rails application, please let us know.
|
60
|
+
|
42
61
|
### Configuration
|
43
62
|
|
44
63
|
After you created the application and installed the dependencies the first thing you need is a configuration file. The database connection is pretty much configured as expected: With a YAML file. Luckily you don't have to create this file by yourself but you can use a generator to do it for you:
|
@@ -59,6 +78,8 @@ development:
|
|
59
78
|
database: 'pony_blog_development'
|
60
79
|
```
|
61
80
|
|
81
|
+
**Note**: If you use something like [dotenv](https://github.com/bkeepers/dotenv) we will process the config file with ERB before loading the YAML. Another way to configure the database connection is to provide a connection URI like this: `http://user:pass@localhost:8529/_db/pony_ville_db`. If you don't use authentication, just skip the user/password part. The connection URI must be provided as the environment variable `DATABASE_URL` and has precedence over the config file.
|
82
|
+
|
62
83
|
After you created a configuration file you can create the database as in any other Rails project:
|
63
84
|
|
64
85
|
```shell
|
@@ -154,7 +175,7 @@ As the model doesn't know anything about the database you cannot define database
|
|
154
175
|
|
155
176
|
### Collections
|
156
177
|
|
157
|
-
Collections are your gateway to the database. They persist your models and offer querying for them. They will translate the raw data from the database to your domain models and vice versa. By convention they are the pluralized version of the model with the suffix `Collection`. So given the model from above, this could be the
|
178
|
+
Collections are your gateway to the database. They persist your models and offer querying for them. They will translate the raw data from the database to your domain models and vice versa. By convention they are the pluralized version of the model with the suffix `Collection`. So given the model from above, this could be the following collection:
|
158
179
|
|
159
180
|
```ruby
|
160
181
|
class PoniesCollection
|
@@ -309,13 +330,13 @@ As you can see, from the model perspective there is nothing special about an emb
|
|
309
330
|
|
310
331
|
```json
|
311
332
|
{
|
312
|
-
"_id":
|
313
|
-
"_rev":
|
314
|
-
"_key":
|
333
|
+
"_id": "...",
|
334
|
+
"_rev": "...",
|
335
|
+
"_key": "...",
|
315
336
|
"title": "The grand blog post",
|
316
337
|
"body": "Lorem ipsum [...]",
|
317
338
|
"create_at": "2014-05-03T16:55:43+02:00",
|
318
|
-
"updated_at": "2014-05-03T16:55:43+02:00"
|
339
|
+
"updated_at": "2014-05-03T16:55:43+02:00",
|
319
340
|
"comments": [
|
320
341
|
{
|
321
342
|
"text": "This was really a grand blog post",
|
@@ -328,6 +349,7 @@ As you can see, from the model perspective there is nothing special about an emb
|
|
328
349
|
"updated_at": "2014-05-04T16:55:43+02:00"
|
329
350
|
}
|
330
351
|
]
|
352
|
+
}
|
331
353
|
```
|
332
354
|
|
333
355
|
**Note**: Again this will only work if you stick with the convention. So far there is no support to configure this more fine grained.
|
@@ -371,7 +393,7 @@ class PostsCollection
|
|
371
393
|
include Guacamole::Collection
|
372
394
|
|
373
395
|
map do
|
374
|
-
references :
|
396
|
+
references :author
|
375
397
|
end
|
376
398
|
end
|
377
399
|
```
|
@@ -394,6 +416,120 @@ AuthorsCollection.save author
|
|
394
416
|
# => Will save both the author and the post
|
395
417
|
```
|
396
418
|
|
419
|
+
### Callbacks
|
420
|
+
|
421
|
+
Guacamole allows you to define callbacks for various actions performed on a model. Those callbacks need to be defined in a dedicate class per model. For example to hash the password of a user prior creating the document in the database you would write something like the following:
|
422
|
+
|
423
|
+
```ruby
|
424
|
+
class UserCallbacks
|
425
|
+
include Guacamole::Callbacks
|
426
|
+
|
427
|
+
before_create :encrypt_password
|
428
|
+
|
429
|
+
def encrypt_password
|
430
|
+
object.encrypted_password = BCrypt::Password.create(object.password)
|
431
|
+
end
|
432
|
+
end
|
433
|
+
```
|
434
|
+
|
435
|
+
Whenever callbacks needs to be executed for a model we create an instance of the specified callback with that model. You have access to the model via the `object` method. To specify a callback for a model you must use the `callbacks` method in that model:
|
436
|
+
|
437
|
+
```ruby
|
438
|
+
class User
|
439
|
+
include Guacamole::Model
|
440
|
+
|
441
|
+
callbacks :user_callbacks
|
442
|
+
end
|
443
|
+
```
|
444
|
+
|
445
|
+
You can define define callbacks for the following actions:
|
446
|
+
|
447
|
+
* `before_validate`
|
448
|
+
* `around_validate`
|
449
|
+
* `after_validate`
|
450
|
+
* `before_save`
|
451
|
+
* `around_save`
|
452
|
+
* `after_save`
|
453
|
+
* `before_create`
|
454
|
+
* `around_create`
|
455
|
+
* `after_create`
|
456
|
+
* `before_update`
|
457
|
+
* `around_update`
|
458
|
+
* `after_update`
|
459
|
+
* `before_delete`
|
460
|
+
* `around_delete`
|
461
|
+
* `after_delete`
|
462
|
+
|
463
|
+
The order of the callback execution is as follows:
|
464
|
+
|
465
|
+
**Creating an object**
|
466
|
+
|
467
|
+
* `before_validation`
|
468
|
+
* `after_validation`
|
469
|
+
* `before_save`
|
470
|
+
* `around_save`
|
471
|
+
* `before_create`
|
472
|
+
* `around_create`
|
473
|
+
* `after_create`
|
474
|
+
* `after_save`
|
475
|
+
|
476
|
+
**Updating an object**
|
477
|
+
|
478
|
+
* `before_validation`
|
479
|
+
* `after_validation`
|
480
|
+
* `before_save`
|
481
|
+
* `around_save`
|
482
|
+
* `before_update`
|
483
|
+
* `around_update`
|
484
|
+
* `after_update`
|
485
|
+
* `after_save`
|
486
|
+
|
487
|
+
**Destroying an object**
|
488
|
+
|
489
|
+
* `before_delete`
|
490
|
+
* `around_delete`
|
491
|
+
* `after_delete`
|
492
|
+
|
493
|
+
The order of the callback execution is as follows:
|
494
|
+
|
495
|
+
**Creating an object**
|
496
|
+
|
497
|
+
1. `before_validation`
|
498
|
+
2. `after_validation`
|
499
|
+
3. `before_save`
|
500
|
+
4. `around_save`
|
501
|
+
5. `before_create`
|
502
|
+
6. `around_create`
|
503
|
+
7. `after_create`
|
504
|
+
8. `after_save`
|
505
|
+
|
506
|
+
**Updating an object**
|
507
|
+
|
508
|
+
1. `before_validation`
|
509
|
+
2. `after_validation`
|
510
|
+
3. `before_save`
|
511
|
+
4. `around_save`
|
512
|
+
5. `before_update`
|
513
|
+
6. `around_update`
|
514
|
+
7. `after_update`
|
515
|
+
8. `after_save`
|
516
|
+
|
517
|
+
**Destroying an object**
|
518
|
+
|
519
|
+
1. `before_delete`
|
520
|
+
2. `around_delete`
|
521
|
+
3. `after_delete`
|
522
|
+
|
523
|
+
#### Generator Support
|
524
|
+
|
525
|
+
Of course we provide a generator to help you setting the required files up:
|
526
|
+
|
527
|
+
```shell
|
528
|
+
rails generate guacamole:callbacks pony
|
529
|
+
```
|
530
|
+
|
531
|
+
This will create the callback file, a test template and adds the appropriate register call to the model file.
|
532
|
+
|
397
533
|
## Integration into the Rails Ecosystem™
|
398
534
|
|
399
535
|
Guacamole is a very young project. A lot of stuff is missing but still, if you want to get started with ArangoDB and are using Ruby/Rails it will give you a nice head start. Besides a long TODO list we want to hint to some points to help you integrate Guacamole with the rest of the Rails ecosystem:
|
@@ -402,17 +538,17 @@ Guacamole is a very young project. A lot of stuff is missing but still, if you w
|
|
402
538
|
|
403
539
|
Currently we're not providing any testing helper, thus you need to make sure to cleanup the database yourself before each run. You can look at the [`spec/acceptance/spec_helper.rb`](https://github.com/triAGENS/guacamole/blob/master/spec/acceptance/spec_helper.rb) of Guacamole for inspiration of how to do that.
|
404
540
|
|
405
|
-
For test data generation we're using the awesome [Fabrication gem](http://www.fabricationgem.org/). Again you find some usage examples in Guacamole's own acceptance tests. We
|
541
|
+
For test data generation we're using the awesome [Fabrication gem](http://www.fabricationgem.org/). Again you find some usage examples in Guacamole's own acceptance tests. We haven't tested Factory Girl yet but it eventually will work too.
|
406
542
|
|
407
543
|
### Authentication
|
408
544
|
|
409
|
-
Any integration into an authentication framework
|
545
|
+
Any integration into an authentication framework needs to be done by you. At this time we have nothing to share with you about this topic.
|
410
546
|
|
411
547
|
### Forms
|
412
548
|
|
413
|
-
While we
|
549
|
+
While we haven't tested them, they should probably work due to the ActiveModel compliance. But again, this is not confirmed and you need to try it out yourself.
|
414
550
|
|
415
|
-
If you give Guacamole a try, please feel free to ask us any question or give us feedback
|
551
|
+
If you give Guacamole a try, please feel free to ask us any question or give us feedback about anything on your mind. This is really crucial for us and we would be more than happy to hear back from you.
|
416
552
|
|
417
553
|
## Todos
|
418
554
|
|
data/Rakefile
CHANGED
@@ -1,10 +1,37 @@
|
|
1
1
|
# -*- encoding : utf-8 -*-
|
2
2
|
require 'bundler/gem_tasks'
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
require 'yard/rake/yardoc_task'
|
5
|
+
require 'inch/rake'
|
6
|
+
require 'reek/rake/task'
|
3
7
|
|
4
|
-
|
5
|
-
|
8
|
+
desc 'Run all specs'
|
9
|
+
task spec: ['spec:unit', 'spec:acceptance']
|
6
10
|
|
7
|
-
|
11
|
+
namespace :spec do
|
12
|
+
desc 'Run unit specs'
|
13
|
+
RSpec::Core::RakeTask.new(:unit) do |task|
|
14
|
+
task.pattern = 'spec/unit/**/*_spec.rb'
|
15
|
+
end
|
16
|
+
|
17
|
+
desc 'Run acceptance specs – requires running instance of ArangoDB'
|
18
|
+
RSpec::Core::RakeTask.new(:acceptance) do |task|
|
19
|
+
task.pattern = 'spec/acceptance/**/*_spec.rb'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
YARD::Rake::YardocTask.new(:doc)
|
24
|
+
|
25
|
+
namespace :metrics do
|
26
|
+
Inch::Rake::Suggest.new do |t|
|
27
|
+
t.args << '--pedantic'
|
28
|
+
end
|
29
|
+
|
30
|
+
Reek::Rake::Task.new do |t|
|
31
|
+
t.fail_on_error = true
|
32
|
+
t.config_files = '.reek.yml'
|
33
|
+
end
|
34
|
+
end
|
8
35
|
|
9
36
|
desc 'Start a REPL with guacamole loaded (not the Rails part)'
|
10
37
|
task :console do
|
@@ -15,3 +42,6 @@ task :console do
|
|
15
42
|
ARGV.clear
|
16
43
|
Pry.start
|
17
44
|
end
|
45
|
+
|
46
|
+
task default: :spec
|
47
|
+
task ci: ['spec', 'metrics:reek']
|
data/guacamole.gemspec
CHANGED
@@ -18,14 +18,25 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(spec)/})
|
19
19
|
spec.require_paths = ['lib']
|
20
20
|
|
21
|
-
spec.add_dependency 'ashikawa-core', '~> 0.
|
21
|
+
spec.add_dependency 'ashikawa-core', '~> 0.12.0'
|
22
22
|
spec.add_dependency 'virtus', '~> 1.0.1'
|
23
23
|
spec.add_dependency 'activesupport', '>= 4.0.0'
|
24
24
|
spec.add_dependency 'activemodel', '>= 4.0.0'
|
25
25
|
spec.add_dependency 'hamster', '~> 1.0.1.pre.rc.1'
|
26
26
|
|
27
|
+
spec.add_development_dependency 'bcrypt', '3.1.7'
|
28
|
+
spec.add_development_dependency 'codeclimate-test-reporter', '~> 0.3.0'
|
27
29
|
spec.add_development_dependency 'fabrication', '~> 2.8.1'
|
28
30
|
spec.add_development_dependency 'faker', '~> 1.2.0'
|
31
|
+
spec.add_development_dependency 'guard', '~> 2.6.1'
|
32
|
+
spec.add_development_dependency 'guard-bundler', '~> 2.0.0'
|
33
|
+
spec.add_development_dependency 'guard-rspec', '~> 4.2.10'
|
34
|
+
spec.add_development_dependency 'inch', '~> 0.4.6'
|
29
35
|
spec.add_development_dependency 'logging', '~> 1.8.1'
|
30
36
|
spec.add_development_dependency 'pry', '~> 0.9.12'
|
37
|
+
spec.add_development_dependency 'rake', '~> 10.3.2'
|
38
|
+
spec.add_development_dependency 'reek', '~> 1.3.7'
|
39
|
+
spec.add_development_dependency 'rspec', '~> 3.0.0'
|
40
|
+
spec.add_development_dependency 'timecop', '~> 0.7.1'
|
41
|
+
spec.add_development_dependency 'yard', '~> 0.8.7.4'
|
31
42
|
end
|
data/lib/guacamole.rb
CHANGED
@@ -0,0 +1,259 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/concern'
|
5
|
+
require 'active_support/core_ext/string/inflections'
|
6
|
+
require 'active_model' # Cherry Pick not possible
|
7
|
+
|
8
|
+
module Guacamole
|
9
|
+
# Define callbacks for different life cycle events of a model
|
10
|
+
#
|
11
|
+
# Callbacks in Guacamole are defined in dedicated classes only. There will be no interface to implement them
|
12
|
+
# in the model or the collection context. This was done due to the nature of the data mapper pattern:
|
13
|
+
#
|
14
|
+
# * The model doesn't know anything about the database thus defining persistence related callbacks (i.e. `before_create`)
|
15
|
+
# would weaken this separation.
|
16
|
+
# * Validation happens in the context of the model and thus defining all callbacks in the collection would still
|
17
|
+
# be somewhat awkward.
|
18
|
+
#
|
19
|
+
# Due to those reasons Guacamole employs the concept of **external callbacks**. Just define a class and include
|
20
|
+
# the `Guacamole::Callbacks` module and you can define all kinds of callbacks. Under the hood `ActiveModel::Callbacks`
|
21
|
+
# is used to provide the callback execution functionality.
|
22
|
+
#
|
23
|
+
# @note Wether you define a callback class for your model or not, internally there always will be a callback.
|
24
|
+
#
|
25
|
+
# Since maintaining the time stamp attributes of your models are implemented as callbacks as well your callback
|
26
|
+
# class will have those methods included.
|
27
|
+
#
|
28
|
+
# Binding a callback class to a model will happen in the `Model.callbacks` method.
|
29
|
+
#
|
30
|
+
# Each callback class will be instantiated with the appropriate model instance. That instance is accessible through
|
31
|
+
# the `object` method.
|
32
|
+
#
|
33
|
+
# @example Define a callback to hash the password prior creation
|
34
|
+
# class UserCallbacks
|
35
|
+
# include Guacamole::Callbacks
|
36
|
+
#
|
37
|
+
# before_create :encrypt_password
|
38
|
+
#
|
39
|
+
# def encrypt_password
|
40
|
+
# object.encrypted_password = BCrypt::Password.create(object.password)
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# @!method self.before_validate(method_name)
|
45
|
+
# Registers a method to be run before the validation will happen
|
46
|
+
#
|
47
|
+
# @param [Symbol] method_name The name of the method to be executed
|
48
|
+
# @api public
|
49
|
+
#
|
50
|
+
# @!method self.around_validate(method_name)
|
51
|
+
# Registers a method to be run before and after the validation will happen.
|
52
|
+
#
|
53
|
+
# @param [Symbol] method_name The name of the method to be executed
|
54
|
+
# @api public
|
55
|
+
# @note You must `yield` at some point in the method.
|
56
|
+
#
|
57
|
+
# @!method self.after_validate(method_name)
|
58
|
+
# Registers a method to be run after the validation happened
|
59
|
+
#
|
60
|
+
# @param [Symbol] method_name The name of the method to be executed
|
61
|
+
# @api public
|
62
|
+
#
|
63
|
+
# @!method self.before_save(method_name)
|
64
|
+
# Registers a method to be run before the collection class saves the model
|
65
|
+
#
|
66
|
+
# Saving a model will always happen, no matter if the model will be created or
|
67
|
+
# updated.
|
68
|
+
#
|
69
|
+
# @param [Symbol] method_name The name of the method to be executed
|
70
|
+
# @api public
|
71
|
+
#
|
72
|
+
# @!method self.around_save(method_name)
|
73
|
+
# Registers a method to be run before and after saving the model
|
74
|
+
#
|
75
|
+
# Saving a model will always happen, no matter if the model will be created or
|
76
|
+
# updated.
|
77
|
+
#
|
78
|
+
# @param [Symbol] method_name The name of the method to be executed
|
79
|
+
# @api public
|
80
|
+
# @note You must `yield` at some point in the method.
|
81
|
+
#
|
82
|
+
# @!method self.after_save(method_name)
|
83
|
+
# Registers a method to be run after the collection class has saved the model
|
84
|
+
#
|
85
|
+
# Saving a model will always happen, no matter if the model will be created or
|
86
|
+
# updated.
|
87
|
+
#
|
88
|
+
# @param [Symbol] method_name The name of the method to be executed
|
89
|
+
# @api public
|
90
|
+
#
|
91
|
+
# @!method self.before_create(method_name)
|
92
|
+
# Registers a method to be run before initially creating the model in the database
|
93
|
+
#
|
94
|
+
# @param [Symbol] method_name The name of the method to be executed
|
95
|
+
# @api public
|
96
|
+
#
|
97
|
+
# @!method self.around_create(method_name)
|
98
|
+
# Registers a method to be run before and after creating the model
|
99
|
+
#
|
100
|
+
# @param [Symbol] method_name The name of the method to be executed
|
101
|
+
# @api public
|
102
|
+
# @note You must `yield` at some point in the method.
|
103
|
+
#
|
104
|
+
# @!method self.after_create(method_name)
|
105
|
+
# Registers a method to be run after the creation of the model
|
106
|
+
#
|
107
|
+
# @param [Symbol] method_name The name of the method to be executed
|
108
|
+
# @api public
|
109
|
+
#
|
110
|
+
# @!method self.before_update(method_name)
|
111
|
+
# Registers a method to be run before updating the model
|
112
|
+
#
|
113
|
+
# @param [Symbol] method_name The name of the method to be executed
|
114
|
+
# @api public
|
115
|
+
#
|
116
|
+
# @!method self.around_update(method_name)
|
117
|
+
# Registers a method to be run before and after updating the model
|
118
|
+
#
|
119
|
+
# @param [Symbol] method_name The name of the method to be executed
|
120
|
+
# @api public
|
121
|
+
# @note You must `yield` at some point in the method.
|
122
|
+
#
|
123
|
+
# @!method self.after_update(method_name)
|
124
|
+
# Registers a method to be run after the model has been updated
|
125
|
+
#
|
126
|
+
# @param [Symbol] method_name The name of the method to be executed
|
127
|
+
# @api public
|
128
|
+
#
|
129
|
+
# @!method self.before_delete(method_name)
|
130
|
+
# Registers a method to be run before deleting the model
|
131
|
+
#
|
132
|
+
# @param [Symbol] method_name The name of the method to be executed
|
133
|
+
# @api public
|
134
|
+
#
|
135
|
+
# @!method self.around_delete(method_name)
|
136
|
+
# Registers a method to be run before and after the deletion
|
137
|
+
#
|
138
|
+
# @param [Symbol] method_name The name of the method to be executed
|
139
|
+
# @api public
|
140
|
+
# @note You must `yield` at some point in the method.
|
141
|
+
#
|
142
|
+
# @!method self.after_delete(method_name)
|
143
|
+
# Registers a method to be run after the deletion of the model has happened
|
144
|
+
#
|
145
|
+
# @param [Symbol] method_name The name of the method to be executed
|
146
|
+
# @api public
|
147
|
+
module Callbacks
|
148
|
+
extend ActiveSupport::Concern
|
149
|
+
# @!parse extend ActiveModel:Callbacks
|
150
|
+
|
151
|
+
included do
|
152
|
+
extend ActiveModel::Callbacks
|
153
|
+
|
154
|
+
define_model_callbacks :validate, :save, :create, :update, :delete
|
155
|
+
|
156
|
+
before_create :add_create_timestamps
|
157
|
+
before_update :update_updated_at_timestamp
|
158
|
+
end
|
159
|
+
|
160
|
+
# The default callback to be used if no custom callback was defined. This is done because it simplifies
|
161
|
+
# the callback invocation code and allows us to use callbacks for adding time stamps to the models.
|
162
|
+
class DefaultCallback
|
163
|
+
include Guacamole::Callbacks
|
164
|
+
end
|
165
|
+
|
166
|
+
# A proxy class around the callback class itself.
|
167
|
+
#
|
168
|
+
# The sole reason for its existence is to specify multiple callback runs at once. The alternative
|
169
|
+
# would have been to nest the `run_callbacks` calls within the caller. It was decided to have bit
|
170
|
+
# more complex proxy class to hide those details from the caller.
|
171
|
+
#
|
172
|
+
# @example
|
173
|
+
# callbacks = Callbacks.callbacks_for(model)
|
174
|
+
# callbacks.run_callbacks :save, :create do
|
175
|
+
# CakeCollection.create model
|
176
|
+
# end
|
177
|
+
# @private
|
178
|
+
class CallbackProxy
|
179
|
+
attr_reader :callbacks
|
180
|
+
|
181
|
+
# Create a new proxy with the original callbacks class as input
|
182
|
+
#
|
183
|
+
# @param [Callbacks] callbacks The original callback class to be executed
|
184
|
+
def initialize(callbacks)
|
185
|
+
@callbacks = callbacks
|
186
|
+
end
|
187
|
+
|
188
|
+
# Runs the given kinds of callbacks
|
189
|
+
#
|
190
|
+
# @param [Array<Symbol>] callbacks_to_run One or more kinds of callbacks to be run
|
191
|
+
# @yield Will call the code block wrapped by the given callbacks
|
192
|
+
def run_callbacks(*callbacks_to_run, &block)
|
193
|
+
outer = callbacks_to_run.pop
|
194
|
+
|
195
|
+
if callbacks_to_run.empty?
|
196
|
+
@callbacks.run_callbacks(outer, &block)
|
197
|
+
else
|
198
|
+
@callbacks = run_callbacks(*callbacks_to_run) do
|
199
|
+
@callbacks.run_callbacks(outer, &block)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
class << self
|
206
|
+
# Register a callback class to be used with the model
|
207
|
+
#
|
208
|
+
# @api private
|
209
|
+
# @param [Model] model_class The class of a model
|
210
|
+
# @param [Callbacks] callback_class The class of the callbacks
|
211
|
+
def register_callback(model_class, callback_class)
|
212
|
+
registry[model_class] = callback_class
|
213
|
+
end
|
214
|
+
|
215
|
+
# Retrieve the callback instance for the given model
|
216
|
+
#
|
217
|
+
# @api private
|
218
|
+
# @params [Model] model The model instance for which callbacks must be executed
|
219
|
+
# @return [Callbacks] A callback instance with the given model accessible via `object`
|
220
|
+
def callbacks_for(model)
|
221
|
+
CallbackProxy.new registry[model.class].new(model)
|
222
|
+
end
|
223
|
+
|
224
|
+
# The internal storage of the callback-model pairs
|
225
|
+
#
|
226
|
+
# @api private
|
227
|
+
# @return [Hash] A hash with the default set to the `DefaultCallback`
|
228
|
+
def registry
|
229
|
+
@registry ||= Hash.new(DefaultCallback)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
# Create a new callback instance with the given model instance
|
234
|
+
#
|
235
|
+
# @param [Model] model_instance The model instance the callbacks should be executed for
|
236
|
+
def initialize(model_instance)
|
237
|
+
@object = model_instance
|
238
|
+
end
|
239
|
+
|
240
|
+
# Provides access to the model instance.
|
241
|
+
#
|
242
|
+
# @retun [Model] A model instance
|
243
|
+
# @api public
|
244
|
+
def object
|
245
|
+
@object
|
246
|
+
end
|
247
|
+
|
248
|
+
# Sets `created_at` and `updated_at` to `Time.now`
|
249
|
+
def add_create_timestamps
|
250
|
+
object.created_at = Time.now
|
251
|
+
update_updated_at_timestamp
|
252
|
+
end
|
253
|
+
|
254
|
+
# Sets `updated_at` to `Time.now`
|
255
|
+
def update_updated_at_timestamp
|
256
|
+
object.updated_at = Time.now
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|