guacamole 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/{config/rubocop.yml → .hound.yml} +1 -12
  4. data/.ruby-version +1 -1
  5. data/.travis.yml +2 -0
  6. data/.yardopts +1 -0
  7. data/CONTRIBUTING.md +3 -3
  8. data/Gemfile.devtools +24 -12
  9. data/Guardfile +1 -1
  10. data/README.md +347 -50
  11. data/Rakefile +10 -0
  12. data/config/reek.yml +18 -5
  13. data/guacamole.gemspec +5 -2
  14. data/lib/guacamole.rb +1 -0
  15. data/lib/guacamole/collection.rb +79 -7
  16. data/lib/guacamole/configuration.rb +56 -2
  17. data/lib/guacamole/document_model_mapper.rb +87 -7
  18. data/lib/guacamole/identity_map.rb +124 -0
  19. data/lib/guacamole/proxies/proxy.rb +42 -0
  20. data/lib/guacamole/proxies/referenced_by.rb +15 -0
  21. data/lib/guacamole/proxies/references.rb +15 -0
  22. data/lib/guacamole/query.rb +11 -0
  23. data/lib/guacamole/railtie.rb +6 -1
  24. data/lib/guacamole/railtie/database.rake +57 -3
  25. data/lib/guacamole/tasks/database.rake +23 -0
  26. data/lib/guacamole/version.rb +1 -1
  27. data/lib/rails/generators/guacamole/collection/collection_generator.rb +19 -0
  28. data/lib/rails/generators/guacamole/collection/templates/collection.rb.tt +5 -0
  29. data/lib/rails/generators/guacamole/config/config_generator.rb +25 -0
  30. data/lib/rails/generators/guacamole/config/templates/guacamole.yml +15 -0
  31. data/lib/rails/generators/guacamole/model/model_generator.rb +25 -0
  32. data/lib/rails/generators/guacamole/model/templates/model.rb.tt +11 -0
  33. data/lib/rails/generators/guacamole_generator.rb +28 -0
  34. data/lib/rails/generators/rails/collection/collection_generator.rb +13 -0
  35. data/lib/rails/generators/rspec/collection/collection_generator.rb +13 -0
  36. data/lib/rails/generators/rspec/collection/templates/collection_spec.rb.tt +7 -0
  37. data/spec/acceptance/association_spec.rb +40 -0
  38. data/spec/acceptance/basic_spec.rb +19 -2
  39. data/spec/acceptance/spec_helper.rb +5 -2
  40. data/spec/fabricators/author.rb +11 -0
  41. data/spec/fabricators/author_fabricator.rb +7 -0
  42. data/spec/fabricators/book.rb +11 -0
  43. data/spec/fabricators/book_fabricator.rb +5 -0
  44. data/spec/unit/collection_spec.rb +265 -18
  45. data/spec/unit/configuration_spec.rb +11 -1
  46. data/spec/unit/document_model_mapper_spec.rb +127 -5
  47. data/spec/unit/identiy_map_spec.rb +140 -0
  48. data/spec/unit/query_spec.rb +37 -16
  49. data/tasks/adjustments.rake +0 -1
  50. metadata +78 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d38e9050d4e439c666b9bdee7a12b7798afd945a
4
- data.tar.gz: 8b007745d612877f577f97135b438788ac89067f
3
+ metadata.gz: 65f6f1c73821edaf59fae8fea0554389f9426a9c
4
+ data.tar.gz: 4eb7f2737b833d830680804d0681b39612e4e27d
5
5
  SHA512:
6
- metadata.gz: 9d807363c5aa0f0ab0fef05d0fca81ce5806b7ac33d199997e5ab50f2a7aa6026736305f4768c8015a2e0c79b889c5400898b504ce98f0189f16720e79cf1361
7
- data.tar.gz: 5e75106be148ca5af2147e985266320f5268e72622c4de98a1d54b38528a816b2bd4ce3110963d0769c106b48619dc97058eb611192c30bcaf8fc05555f4dcd7
6
+ metadata.gz: 3d73b44a1c7111fe19d662f3ede415f2b574d1502b90644050e029ae330869ceec50b32d621d8863c8b45f0ded991997a6753701cfeb4ef976774f94ab9ea63d
7
+ data.tar.gz: 95bed05003e2f5a2796b7e53b03a9c7620e2a325b3b47697be15e451c8be813ecd1c45eaffb0c4c1113fa0777e2d1d2146dded64698bdb7638252e0db265f298
data/.gitignore CHANGED
@@ -16,3 +16,4 @@ spec/reports
16
16
  test/tmp
17
17
  test/version_tmp
18
18
  tmp
19
+ cache
@@ -6,9 +6,7 @@ AllCops:
6
6
  - 'Gemfile'
7
7
  - 'Gemfile.devtools'
8
8
  Excludes:
9
- - '**/spec/setup/**'
10
- - '**/vendor/**'
11
- - '**/benchmarks/**'
9
+ - !ruby/regexp /spec\/setup/
12
10
 
13
11
  # Avoid parameter lists longer than five parameters.
14
12
  ParameterLists:
@@ -31,15 +29,6 @@ SignalException:
31
29
  # Valid values are: semantic, only_raise and only_fail
32
30
  EnforcedStyle: only_raise
33
31
 
34
- # Do not force public/protected/private keyword to be indented at the same
35
- # level as the def keyword. My personal preference is to outdent these keywords
36
- # because I think when scanning code it makes it easier to identify the
37
- # sections of code and visually separate them. When the keyword is at the same
38
- # level I think it sort of blends in with the def keywords and makes it harder
39
- # to scan the code and see where the sections are.
40
- AccessControl:
41
- Enabled: false
42
-
43
32
  # Limit line length
44
33
  LineLength:
45
34
  Max: 120
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- ruby-2.0.0-p247
1
+ ruby-2.0.0-p353
data/.travis.yml CHANGED
@@ -13,6 +13,8 @@ env:
13
13
  - ARANGODB_DISABLE_AUTHENTIFICATION=true VERSION=1.4.0-beta2
14
14
  matrix:
15
15
  allow_failures:
16
+ - rvm: jruby-19mode
16
17
  - rvm: ruby-head
17
18
  - rvm: jruby-head
19
+ - rvm: rbx-19mode
18
20
  script: "bundle exec rake ci"
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --markup markdown
data/CONTRIBUTING.md CHANGED
@@ -2,12 +2,12 @@
2
2
 
3
3
  When you want to write code for the project, please follow these guidelines:
4
4
 
5
- 1. **Claim** the ticket: Tell us that you want to work on a certain ticket, we will assign it to you (We don't want two people to work on the same thing ;) )
6
- 2. **Fork** your feature branch from the `development` branch (not the `master` branch)
5
+ 1. **Claim** the ticket: Tell us that you want to work on a certain ticket, we will assign it to you (We don't want two people to work on the same thing :wink: )
6
+ 2. **Fork** your feature branch from the `master` branch
7
7
  3. Write an **acceptance test**: Describe what you want to do (our acceptance tests touch the database)
8
8
  4. **Implement** it: Write a unit test, check that it fails, make the test pass – repeat (our unit tests don't touch the database)
9
9
  5. Write **documentation** for it.
10
- 6. Check with `bundle exec rake ci` (you need to have ArangoDB running for that) that everything is fine and send the pull request to the `development` branch :)
10
+ 6. Check with `bundle exec rake ci` (you need to have ArangoDB running for that) that everything is fine and send the pull request to the `master` branch :)
11
11
 
12
12
  ## Setup
13
13
 
data/Gemfile.devtools CHANGED
@@ -4,21 +4,23 @@ group :development do
4
4
  gem 'rake', '~> 10.1.0'
5
5
  gem 'rspec', '~> 2.14.1'
6
6
  gem 'yard', '~> 0.8.7'
7
+
8
+ platform :rbx do
9
+ gem 'rubysl-singleton', '~> 2.0.0'
10
+ end
7
11
  end
8
12
 
9
13
  group :yard do
10
- gem 'kramdown', '~> 1.2.0'
14
+ gem 'kramdown', '~> 1.3.2'
11
15
  end
12
16
 
13
17
  group :guard do
14
- gem 'guard', '~> 1.8.1'
15
- gem 'guard-bundler', '~> 1.0.0'
16
- gem 'guard-rspec', '~> 3.0.2'
17
- gem 'guard-rubocop', '~> 0.2.0'
18
- gem 'guard-mutant', '~> 0.0.1'
18
+ gem 'guard', '~> 2.4.0'
19
+ gem 'guard-bundler', '~> 2.0.0'
20
+ gem 'guard-rspec', '~> 4.2.6'
19
21
 
20
22
  # file system change event handling
21
- gem 'listen', '~> 1.3.0'
23
+ gem 'listen', '~> 2.5.0'
22
24
  gem 'rb-fchange', '~> 0.0.6', require: false
23
25
  gem 'rb-fsevent', '~> 0.9.3', require: false
24
26
  gem 'rb-inotify', '~> 0.9.0', require: false
@@ -32,16 +34,26 @@ end
32
34
  group :metrics do
33
35
  gem 'coveralls', '~> 0.7.0'
34
36
  gem 'flay', '~> 2.4.0'
35
- gem 'flog', '~> 4.1.1'
37
+ gem 'flog', '~> 4.2.0'
36
38
  gem 'reek', '~> 1.3.2'
37
- gem 'rubocop', '~> 0.14.1'
38
- gem 'simplecov', '~> 0.7.1'
39
- gem 'yardstick', '~> 0.9.7', git: 'https://github.com/dkubb/yardstick.git'
39
+ gem 'simplecov', '~> 0.8.2'
40
+ gem 'yardstick', '~> 0.9.9'
41
+
42
+ # platforms :mri do
43
+ # gem 'mutant', '~> 0.3.6'
44
+ # end
40
45
 
41
46
  platforms :ruby_19, :ruby_20 do
42
- gem 'mutant', git: 'https://github.com/mbj/mutant.git'
43
47
  gem 'yard-spellcheck', '~> 0.1.5'
44
48
  end
49
+
50
+ platform :rbx do
51
+ gem 'json', '~> 1.8.1'
52
+ gem 'racc', '~> 1.4'
53
+ gem 'rubysl-logger', '~> 2.0.0'
54
+ gem 'rubysl-open-uri', '~> 2.0.0'
55
+ gem 'rubysl-prettyprint', '~> 2.0.2'
56
+ end
45
57
  end
46
58
 
47
59
  group :benchmarks do
data/Guardfile CHANGED
@@ -3,7 +3,7 @@ guard 'bundler' do
3
3
  watch(/^.+\.gemspec/)
4
4
  end
5
5
 
6
- guard 'rspec', spec_paths: 'spec/unit' do
6
+ guard 'rspec', spec_paths: ['spec/unit'] do
7
7
  watch(%r{spec/.+\.rb})
8
8
  watch(%r{lib/guacamole/(.+)\.rb$}) { |m| "spec/unit/#{m[1]}_spec.rb" }
9
9
  end
data/README.md CHANGED
@@ -1,24 +1,31 @@
1
+ [![RubyDoc](http://img.shields.io/badge/📄-RubyDoc-be1d77.svg)](http://rubydoc.info/gems/guacamole/frames)
2
+ [![Build Status](http://img.shields.io/travis/triAGENS/guacamole.svg)](https://travis-ci.org/triAGENS/guacamole)
3
+ [![Code Climate](http://img.shields.io/codeclimate/github/triAGENS/guacamole.svg)](https://codeclimate.com/github/triAGENS/guacamole)
4
+ [![Gem Version](http://img.shields.io/gem/v/guacamole.svg)](https://rubygems.org/gems/guacamole)
5
+
1
6
  # Guacamole
2
7
 
3
- | Project | Guacamole
4
- |:----------------|:--------------------------------------------------
5
- | Homepage | https://github.com/triAGENS/guacamole
6
- | Documentation | **TODO: Add RubyDoc URL**
7
- | CI | [![Build Status](https://travis-ci.org/triAGENS/guacamole.png)](https://travis-ci.org/triAGENS/guacamole)
8
- | Code Metrics | [![Code Climate](https://codeclimate.com/github/triAGENS/guacamole.png)](https://codeclimate.com/github/triAGENS/guacamole)
9
- | Gem Version | **TODO: Add Badge Fury Badge**
10
- | Dependencies | [![Dependency Status](https://gemnasium.com/triAGENS/guacamole.png)](https://gemnasium.com/triAGENS/guacamole)
11
- | Ready Stories | [![Stories in Ready](https://badge.waffle.io/triagens/guacamole.png?label=ready)](https://waffle.io/triagens/guacamole)
8
+ Guacamole is an Object Document Mapper (ODM) for the multi-model NoSQL database [ArangoDB](https://www.arangodb.org/). Its main goal is to support easy integration into Ruby on Rails but will likely work in other Rack-based frameworks as well. There are a couple of design goals behind Guacamole which should drive all our development effort:
9
+
10
+ * Easy integration on the View layer (i.e. form builders)
11
+ * Reflect the nature of NoSQL in general and ArangoDB in particular
12
+ * Focus on long-term maintainability of your application
13
+
14
+ While the first two points don't need any further explanation we want to lay out the motivation behind the last point: 'Ease of use' is very important to us, but we made some fundamental decisions which will cause a stepper learning curve than other libraries, notably ActiveRecord. If you have a traditional Rails background you will find some things quite different. We decided to go this direction, because we think it better suites the features of ArangoDB. Applying the semantics of a different environment maybe helps with the first steps but will become problematic if you further advance in your understanding of the possibilities.
12
15
 
13
- Guacamole is an ODM for ArangoDB that offers integration for Ruby on Rails.
16
+ That said we still think we provide a sufficient API that is quite easy to get hold of. It is just a bit different from what you were doing with ActiveRecord.
14
17
 
15
- All tests run on Travis CI for the following versions of Ruby:
18
+ For a high-level introduction you can also refer to [this presentation](https://speakerdeck.com/railsbros_dirk/how-to-make-guacamole).
16
19
 
17
- * MRI 1.9.3 and 2.0.0
18
- * Rubinius 1.9 mode
19
- * JRuby 1.9 mode
20
+ ## Getting started (with a fresh Rails application)
20
21
 
21
- ## Installation
22
+ Since Guacamole is in an alpha state we suggest you create a new Rails application to play around with it. We don't recommend adding it to a production application.
23
+
24
+ First of all create your shiny new application, without ActiveRecord of course:
25
+
26
+ ```shell
27
+ rails new -O $my_awesome_app
28
+ ```
22
29
 
23
30
  Add this line to your application's Gemfile:
24
31
 
@@ -26,47 +33,261 @@ Add this line to your application's Gemfile:
26
33
  gem 'guacamole'
27
34
  ```
28
35
 
29
- And then execute:
36
+ And then install the new dependencies:
37
+
38
+ ```shell
39
+ bundle install
40
+ ```
41
+
42
+ ### Configuration
43
+
44
+ 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:
30
45
 
31
46
  ```shell
32
- bundle
47
+ bundle exec rails generate guacamole:config
48
+ ```
49
+
50
+ This will create a default configuration at `config/guacamole.yml`:
51
+
52
+ ```yaml
53
+ development:
54
+ protocol: 'http'
55
+ host: 'localhost'
56
+ port: 8529
57
+ password: ''
58
+ username: ''
59
+ database: 'pony_blog_development'
33
60
  ```
34
61
 
35
- Or install it yourself as:
62
+ After you created a configuration file you can create the database as in any other Rails project:
36
63
 
37
64
  ```shell
38
- gem install guacamole
65
+ bundle exec rake db:create
39
66
  ```
40
67
 
68
+ If you're using Capistrano or something else make sure you change your deployment recipes accordingly to use the `guacamole.yml` and not the `database.yml`. Of course you would want to add [authentication](https://www.arangodb.org/manuals/2/DbaManualAuthentication.html) for the production environment. Additionally you may want to consider putting ArangoDB behind a SSL-proxy or use the [built in SSL support](https://www.arangodb.org/manuals/2/CommandLine.html#CommandLineArangoEndpoint).
69
+
70
+ Now where everything is set up we can go ahead and create our application's logic. Before we give you some code to copy and paste we first give you a general usage and design overview.
71
+
41
72
  ## Usage
42
73
 
43
- There are two main concepts you have to be familiar with in Guacamole: Collections and models. Both of these are modules that you can mixed in to your classes:
74
+ One of the key features of Guacamole is the implementation of the [Data Mapper Patter](http://martinfowler.com/eaaCatalog/dataMapper.html). This brings a lot of good things along, like
75
+
76
+ * Improved testability
77
+ * Separation of Concern and
78
+ * Easier to support database
features like embedded objects
79
+
80
+ The gist of the pattern is you have two classes where you would have one when you use ActiveRecord: A `Collection` and a `Model`. The `Collection` is responsible for getting data from and writing data to the database. The `Model` represents the domain logic (i.e. attributes) and has no idea what a database is. Due to this you could far easier test the domain logic without a database dependency. But you have always two (or more) classes around. The following will introduce you to both those classes.
44
81
 
45
82
  ### Models
46
83
 
47
- Models are representations of your data. They are not aware of the database but work independent of it. A simple example for a model:
84
+ Models are representations of your data. They are not aware of the database but work independently of it. Guacamole ships with a generator for models:
85
+
86
+ ```shell
87
+ bundle exec rails generate model pony name:string birthday:date color:string
88
+ ```
89
+
90
+ This will generate both a `Model` **and** a `Collection` (more on that later). If you don't want a `Collection` to be created just add the `--skip-collection` flag to the generator. The `Model` will be written to `app/models/pony.rb` and it will have the following content:
48
91
 
49
92
  ```ruby
50
- class Article
93
+ class Pony
51
94
  include Guacamole::Model
52
95
 
53
- attribute :title, String
54
- attribute :comments, Array[Comment]
96
+ attribute :name, String
97
+ attribute :birthday, Date
98
+ attribute :color, String
99
+ end
100
+ ```
101
+
102
+ Since the database doesn't know anything about a schema we must define the attributes in the model class itself. At the same time this has the advantage to open the model class and see what attributes it has. An attribute is defined with the `attribute` class method. We use [Virtus](https://github.com/solnic/virtus) for this purpose. Basically you add give the attribute a name and a type. The type have to be the actual class and **not** a string representation of the class. You could even define collection classes:
103
+
104
+ ```ruby
105
+ class Pony
106
+ include Guacamole::Model
55
107
 
56
- validates :title, presence: true
108
+ attribute :type, Array[String]
57
109
  end
58
110
  ```
59
111
 
60
- This example defines a model called Article, which has a title represented by a String and an array of comments. Comment in this case is another `Guacamole::Model`. The `Model` mixin will also add validation from ActiveModel to your model it works as you know it from ActiveRecord for example.
112
+ For further reference what is possible please refer to the [Virtus documentation](http://rubydoc.info/gems/virtus/1.0.2/frames). One thing to add here, whenever you assign a value to an attribute Virtus will perform a type coercion:
113
+
114
+ ```ruby
115
+ pinkie_pie = Pony.new
116
+ pinkie_pie.color = :pink
117
+ # => "pink"
118
+ pinkie_pie.type = "Earthpony"
119
+ # => ["Earthpony"]
120
+ ```
121
+
122
+ #### Timestamps
123
+
124
+ We will automatically add time stamp columns to all models when you include `Guacamole::Model`. We eventually will make this configurable, but for now it is not.
61
125
 
62
- In a Rails application, they are stored in the `app/models` directory by convention.
126
+ #### The ID of a model
127
+
128
+ In ArangoDB a document has three internal fields: `_id`, `_key` and `_rev`. For a detailed explanation how these three work together please refer to the [ArangoDB documentation](https://www.arangodb.org/manuals/2/HandlingDocuments.html#HandlingDocumentsIntro). Within Guacamole we will always you the `_key` because it is enough the identify any document within a collection. Both the `_key` and `_rev` attribute are available through the `Guacamole::Model#key` and `Guacamole::Model#rev` attribute. You don't have to do anything for this, we will take care of this for you.
129
+
130
+ Additionally you will find an `id` method on you models. This is just an alias for `key`. This was added for `ActiveModel::Conversion` compliance. You **should always** use `key`.
131
+
132
+ #### Validations
133
+
134
+ When including `Guacamole::Model` you will not only get the functionality of Virtus but some ActiveModel love, too. Besides the [`ActiveModel::Naming`](http://api.rubyonrails.org/classes/ActiveModel/Naming.html) and [`ActiveModel::Conversion`](http://api.rubyonrails.org/classes/ActiveModel/Conversion.html) module you will get [Validations](http://api.rubyonrails.org/classes/ActiveModel/Validations.html) as well. Thus you could just write something like this:
135
+
136
+ ```ruby
137
+ class Pony
138
+ include Guacamole::Model
139
+
140
+ attribute :name, String
141
+ attribute :color, String
142
+
143
+ validates :color, presence: true
144
+ end
145
+
146
+ transparent_pony = Pony.new
147
+ transparent_pony.valid?
148
+ # => false
149
+ transparent_pony.errors[:color]
150
+ # => ["can't be blank"]
151
+ ```
152
+
153
+ As the model doesn't know anything about the database you cannot define database-dependent validations here (i.e.: uniqueness). This logic has to be handled in the `Collection`. That said, we have no strategy how to model this in the `Collection`. If you have any idea about this we would love to hear about it.
63
154
 
64
155
  ### Collections
65
156
 
66
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 according collection:
67
158
 
68
159
  ```ruby
69
- class ArticlesCollection
160
+ class PoniesCollection
161
+ include Guacamole::Collection
162
+ end
163
+ ```
164
+
165
+ As with the models we provide a generator to help you creating your collection classes. In most cases you won't need to invoke this generator due to the model generator already created a collection for you. But if for any reason you need another collection without a model you could do it like this:
166
+
167
+ ```shell
168
+ bundle exec rails generate collection ponies
169
+ ```
170
+
171
+ Currently your options what you can do with a collection are quire limited. We will eventually add more features, but for now you basically have this features:
172
+
173
+ * CRUD operations for your models
174
+ * Where the "Read"-part is limited to [Simple Queries](https://www.arangodb.org/manuals/2/SimpleQueries.html). But more on this later.
175
+ * Mapping embedded models
176
+ * Realizing basic associations
177
+
178
+ For all the mapping related parts you don't have any configuration options yet, but have to stick with the conventions. Obviously this will change in the future but for now there more important parts to work on. Before we dig deeper into the mapping of embedded or associated models let us look at the CRUD functionality.
179
+
180
+ #### Create models
181
+
182
+ To create a model just pass it to the `save` method of the `Collection` in charge:
183
+
184
+ ```ruby
185
+ pinkie = Pony.new(name: "Pinkie Pie")
186
+ PoniesCollection.save pinkie
187
+ # => #<Pony:0x124 …>
188
+ ```
189
+
190
+ The `save` method will trigger model validation before writing it to the database. If the model is not valid `false` will be returned. All validation errors can be retrieved from the model itself. They are stored in `errors` attribute which is provided by `ActiveModel::Validations`.
191
+
192
+ Every model has a `persisted?` method which will return `false` unless the model is saved to the database and thus has a `key` assigned.
193
+
194
+ #### Update models
195
+
196
+ Updating models is just the same as creating models in the first place:
197
+
198
+ ```ruby
199
+ existing_pony.name = "Applejack"
200
+ PoniesCollection.save existing_pony
201
+ # => #<Pony:0x1451 …>
202
+ ```
203
+
204
+ **Note**: As of today there is **no dirty tracking**. Models will always be updated in the database when you call `save` – no matter if they have changed or not.
205
+
206
+ #### Delete models
207
+
208
+ You can `delete` models from the database by either passing the model to be deleted or just its key. In both cases the key will be returned:
209
+
210
+ ```ruby
211
+ PoniesCollection.delete existing_pony
212
+ # => `existing_pony.key`
213
+ ```
214
+
215
+ #### Retrieve models
216
+
217
+ As mentioned before querying for models is quite limited as of now. We only support [Simple Queries](https://www.arangodb.org/manuals/2/SimpleQueries.html) at this point. You can perform the following basic operations with them:
218
+
219
+ * Getting a single model `by_key`
220
+ * Getting `all` models from a collection.
221
+ * Query models `by_example`. You can **only** perform equality checks with this.
222
+ * You can `skip` and `limit` the results
223
+
224
+ You always need to start a query by either calling `all` or `by_example`. You could chain those with `skip` and `limit`. The query to the database will only be performed when you actually access the documents:
225
+
226
+ ```ruby
227
+ some_ponies = PoniesCollection.by_example(color: 'green').limit(10)
228
+ # => #<Guacamole::Query:0x1212 …>
229
+ some_ponies.first
230
+ # The request to the database is made
231
+ # => #<Pony:0x90u81 …>
232
+ ```
233
+
234
+ We're well aware this is not sufficient for building sophisticated applications. We're are working on something to make [AQL](https://www.arangodb.org/manuals/2/Aql.html) usable from Guacamole.
235
+
236
+ ### Mapping
237
+
238
+ As the name "Data Mapper" suggests there is some sort of mapping going on behind the scenes. The mapping relates to the process of _mapping_ documents from the database to the domain models.
239
+
240
+ The `Collection` class will lookup the appropriate `Model` class based on its own name (i.e.: the `PoniesCollection` will look for a `Pony` class). Currently there is no option to configure this so you're stuck with our conventions (for now):
241
+
242
+ * Collections in ArangoDB are the plural form of the `Model` class name
243
+ * The `Collection` class is the plural form of the `Model` class name with the suffix `Collection`
244
+
245
+ Without any configuration we will just map the attributes present in your domain model. If you retrieve a document from the database that contains other attributes then your domain model they will be silently discarded. To illustrate this imagine we have a document in the `ponies` collection which looks like this:
246
+
247
+ ```json
248
+ {
249
+ "_key": "303",
250
+ "_rev": "1019391",
251
+ "name": "Applejack",
252
+ "color": "green",
253
+ "occupation": "Farmer"
254
+ }
255
+ ```
256
+
257
+ When we receive this document and map it against the above mentioned model there will be no `occupation` attribute be present:
258
+
259
+ ```ruby
260
+ pony = PoniesCollection.by_key "303"
261
+ pony.occupation
262
+ # => NoMethodError: undefined method `occupation' for #<Pony:0x00000105fc77f8>
263
+ ```
264
+
265
+ Currently there is not option to change the mapping of attributes. If you want to map more or less attributes you should create another model for that purpose.
266
+
267
+ #### Associations
268
+
269
+ Besides simple attributes we want to handle associations between models. To add an association between your models you have two options: __embedded__ and __referenced__.
270
+
271
+ #### Embedded references
272
+
273
+ If you go with the `embeds` option the embedded model will be stored within the **same** document in the database. The comments of a blog post are a good example where this can be handy. While the database will have only one document the domain can still know about a `Comment` and a `Post`. In this case you would end up with two models and one collection:
274
+
275
+ ```ruby
276
+ class Comment
277
+ include Guacamole::Model
278
+
279
+ attribute :text, String
280
+ end
281
+
282
+ class Post
283
+ include Guacamole::Model
284
+
285
+ attribute :title, String
286
+ attribute :body, String
287
+ attribute :comments, Array[Comment]
288
+ end
289
+
290
+ class PostsCollection
70
291
  include Guacamole::Collection
71
292
 
72
293
  map do
@@ -75,48 +296,124 @@ class ArticlesCollection
75
296
  end
76
297
  ```
77
298
 
78
- As you can see above, you don't need to explicitly state that you are mapping to the `Article` class, because this is the naming convention. But what does `map` do?
79
-
80
- In the block you provide to `map` you can configure things that should happen when you map from the raw data to the model and vice versa. In a document store like ArangoDB you can have nested data – so the JSON stored in ArangoDB's `articles` collection could look something like this:
299
+ As you can see, from the model perspective there is nothing special about an embedded association. It is just another attribute on the `Post` class. How this is stored will be configured where it is handled: In the `PostsCollection`. Within the `map` block you put all the mapping related configuration. The `embeds` method will make sure that `Comment`s are correctly stored and received within the database. Be aware that embedded models will not have any `_key`, `_id` or `_rev` attribute. But they will have the time stamp attributes correctly populated. Within ArangoDB the resulting document will look like this:
81
300
 
82
301
  ```json
83
302
  {
84
- 'title': 'The grand blog post',
85
- 'comments': [
303
+ "_id": [...],
304
+ "_rev": [...],
305
+ "_key": [...],
306
+ "title": "The grand blog post",
307
+ "body": "Lorem ipsum [...]",
308
+ "create_at": "2014-05-03T16:55:43+02:00",
309
+ "updated_at": "2014-05-03T16:55:43+02:00"
310
+ "comments": [
86
311
  {
87
- 'text': 'This was really a grand blog post'
312
+ "text": "This was really a grand blog post",
313
+ "create_at": "2014-05-08T16:55:43+02:00",
314
+ "updated_at": "2014-05-08T16:55:43+02:00"
88
315
  },
89
316
  {
90
- 'text': 'I don't think it was that great'
317
+ "text": "I don't think it was that great",
318
+ "create_at": "2014-05-04T16:55:43+02:00",
319
+ "updated_at": "2014-05-04T16:55:43+02:00"
91
320
  }
92
321
  ]
93
322
  ```
94
323
 
95
- With the `map` configuration above it would take each of the objects in the comments hash and create instances of the `Comment` model from them. Then it would set the `comments` attribute of the new article and set it to the array of those comments.
324
+ **Note**: Again this will only work if you stick with the convention. So far there is no support to configure this more fine grained.
325
+
326
+ #### References
327
+
328
+ While there are perfect use cases to embed documents into each other there are still plenty of use cases where referencing documents makes perfect sense. In fact this one feature where ArangoDB can really shine: Instead of just getting all referenced documents with dedicated calls to the server and without the possibility to perform any functions like filtering or sorting the data, ArangoDB can perform joins over your data just like a RDBMS.
96
329
 
97
- In a Rails application, they are stored in the `app/collections` directory by convention. **Note:** As of now you do have to add the `app/collections` path manually to the load path in your `config/application.rb`:
330
+ **Note**: In the current version we're not using this power since we need to support AQL before that. As of now references are realized with dedicated calls to the database.
331
+
332
+ To define references between models you just add the appropriate attributes to the `Model` classes:
98
333
 
99
334
  ```ruby
100
- config.autoload_paths += Dir[Rails.root.join('app', 'collections', '*.rb').to_s]
335
+ class Author
336
+ include Guacamole::Model
337
+
338
+ attribute :name, String
339
+ attribute :posts, Array[Post]
340
+ end
341
+
342
+ class Post
343
+ include Guacamole::Model
344
+
345
+ attribute :title, String
346
+ attribute :author, Author
347
+ end
101
348
  ```
102
349
 
103
- ### Configuration
350
+ As with the embedded models the real work happens in the `Collection` classes:
104
351
 
105
- You configure the connection to ArangoDB in the same fashion as you would configure a connection to a relational database in a Rails application: Just create a YAML file which holds the required parameters for each of your environment:
352
+ ```ruby
353
+ class AuthorsCollection
354
+ include Guacamole::Collection
106
355
 
107
- ```yaml
108
- development:
109
- protocol: 'http'
110
- host: 'localhost'
111
- port: 8529
112
- password: ''
113
- username: ''
114
- database: 'planet_express_development'
356
+ map do
357
+ referenced_by :posts
358
+ end
359
+ end
360
+
361
+ class PostsCollection
362
+ include Guacamole::Collection
363
+
364
+ map do
365
+ references :user
366
+ end
367
+ end
368
+ ```
369
+
370
+ Under the hood we will add an `author_id` to all posts holding the reference to the author. As a user this will be completely transparent for you:
371
+
372
+ ```ruby
373
+ author = AuthorsCollection.by_key "23124"
374
+ author.posts
375
+ # => [#<Post:0x12341 …>, …]
115
376
  ```
116
377
 
117
- We're looking at `config/guacamole.yml` to read this configuration. If you're using Capistrano or something else make sure you change your deployment recipes accordingly to use the `guacamole.yml` and not the `database.yml`.
378
+ The same goes for saving the data. Just add `Post`s to an `Author` as you would in plain Ruby. Passing one of the models to its `Collection` class will take care of the rest:
379
+
380
+ ```ruby
381
+ author = Author.new(name: "Lauren Faust")
382
+ author.posts << Post.new(title: "This is amazing")
383
+
384
+ AuthorsCollection.save author
385
+ # => Will save both the author and the post
386
+ ```
387
+
388
+ ## Integration into the Rails Ecosystem™
389
+
390
+ 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:
391
+
392
+ ### Testing
393
+
394
+ 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.
395
+
396
+ For test data generation we're using the awesome [Fabrication gem](http://www.fabricationgem.org/). Again you find some usage examples in under Guacamole's own acceptance tests. We didn't tested Factory Girl yet, but it eventually will work, too.
397
+
398
+ ### Authentication
399
+
400
+ Any integration into an authentication framework need to be done by you. At this time we have nothing to share with you about this topic.
401
+
402
+ ### Forms
403
+
404
+ While we not tested them they should probably work due to the ActiveModel compliance. But again, this not confirmed and you need to try it out by yourself.
405
+
406
+ If you give Guacamole a try, please feel free to ask us any question or give us feedback to anything on your mind. This is really crucial for us and we would be more than happy to hear back from you.
407
+
408
+ ## Todos
409
+
410
+ While there are a lot of open issues we would like to present you a high level overview of upcoming features:
118
411
 
119
- **Note:** 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` of Guacamole for inspiration of how to do this.
412
+ * Basic AQL support for more useful queries
413
+ * Configuration of mapping
414
+ * Callbacks and dirty tracking for models
415
+ * An example Rails application to be used as both an acceptance test suite and a head start for Guacamole and ArangoDB
416
+ * An AQL query builder
120
417
 
121
418
  ## Issues or Questions
122
419