guacamole 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.travis.yml +18 -0
- data/CONTRIBUTING.md +26 -0
- data/Gemfile +16 -0
- data/Gemfile.devtools +55 -0
- data/Guardfile +13 -0
- data/LICENSE +202 -0
- data/README.md +130 -0
- data/Rakefile +7 -0
- data/config/devtools.yml +4 -0
- data/config/flay.yml +3 -0
- data/config/flog.yml +2 -0
- data/config/mutant.yml +3 -0
- data/config/reek.yml +104 -0
- data/config/rubocop.yml +68 -0
- data/config/yardstick.yml +2 -0
- data/guacamole.gemspec +28 -0
- data/lib/guacamole.rb +19 -0
- data/lib/guacamole/collection.rb +246 -0
- data/lib/guacamole/configuration.rb +123 -0
- data/lib/guacamole/document_model_mapper.rb +95 -0
- data/lib/guacamole/model.rb +210 -0
- data/lib/guacamole/query.rb +72 -0
- data/lib/guacamole/railtie.rb +27 -0
- data/lib/guacamole/railtie/database.rake +5 -0
- data/lib/guacamole/version.rb +5 -0
- data/log/.gitkeep +0 -0
- data/spec/acceptance/.gitkeep +0 -0
- data/spec/acceptance/basic_spec.rb +112 -0
- data/spec/acceptance/config/guacamole.yml +11 -0
- data/spec/acceptance/spec_helper.rb +61 -0
- data/spec/fabricators/article_fabricator.rb +10 -0
- data/spec/fabricators/comment_fabricator.rb +5 -0
- data/spec/setup/arangodb.sh +65 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/unit/.gitkeep +0 -0
- data/spec/unit/collection_spec.rb +380 -0
- data/spec/unit/configuration_spec.rb +119 -0
- data/spec/unit/document_model_mapper_spec.rb +120 -0
- data/spec/unit/example_spec.rb +8 -0
- data/spec/unit/model_spec.rb +116 -0
- data/spec/unit/query_spec.rb +140 -0
- data/tasks/adjustments.rake +35 -0
- metadata +191 -0
@@ -0,0 +1,123 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
require 'forwardable'
|
5
|
+
require 'ashikawa-core'
|
6
|
+
require 'active_support/core_ext'
|
7
|
+
|
8
|
+
require 'guacamole/document_model_mapper'
|
9
|
+
|
10
|
+
module Guacamole
|
11
|
+
|
12
|
+
class << self
|
13
|
+
# Configure Guacamole
|
14
|
+
#
|
15
|
+
# Takes a block in which you can configure Guacamole.
|
16
|
+
# @return [Configuration] the resulting configuration
|
17
|
+
def configure(&config_block)
|
18
|
+
config_block.call configuration
|
19
|
+
|
20
|
+
configuration
|
21
|
+
end
|
22
|
+
|
23
|
+
# Contains the configuration of Guacamole
|
24
|
+
#
|
25
|
+
# @return [Configuration]
|
26
|
+
def configuration
|
27
|
+
@configuration ||= Configuration
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Current configuration
|
32
|
+
#
|
33
|
+
# You can receive the configuration by calling Guacamole.configuration
|
34
|
+
#
|
35
|
+
# @!attribute self.database
|
36
|
+
# The raw Database object
|
37
|
+
#
|
38
|
+
# The Database implementation is part of Ashikawa::Core
|
39
|
+
#
|
40
|
+
# @see http://rubydoc.info/gems/ashikawa-core/Ashikawa/Core/Database
|
41
|
+
# @return [Ashikawa::Core::Database]
|
42
|
+
#
|
43
|
+
# @!attribute self.default_mapper
|
44
|
+
# The Mapper class that is used by default. This defaults to
|
45
|
+
# DocumentModelMapper
|
46
|
+
#
|
47
|
+
# @return [Class] the default mapper class
|
48
|
+
#
|
49
|
+
# @!attribute self.logger
|
50
|
+
# The logger
|
51
|
+
#
|
52
|
+
# This defaults to the Rails logger
|
53
|
+
#
|
54
|
+
# @return [Object] the logger
|
55
|
+
#
|
56
|
+
# @!attribute [r] self.current_environment
|
57
|
+
# The current environment Guacamole is running in
|
58
|
+
#
|
59
|
+
# If you are running in Rails, this will return the current Rails environment
|
60
|
+
#
|
61
|
+
# @return [Object] current environment
|
62
|
+
class Configuration
|
63
|
+
# @!visibility protected
|
64
|
+
attr_accessor :database, :default_mapper, :logger
|
65
|
+
|
66
|
+
class << self
|
67
|
+
extend Forwardable
|
68
|
+
|
69
|
+
def_delegators :configuration, :database, :database=, :default_mapper=, :logger=
|
70
|
+
|
71
|
+
def default_mapper
|
72
|
+
configuration.default_mapper || (self.default_mapper = Guacamole::DocumentModelMapper)
|
73
|
+
end
|
74
|
+
|
75
|
+
def logger
|
76
|
+
configuration.logger ||= (rails_logger || default_logger)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Load a YAML configuration file to configure Guacamole
|
80
|
+
#
|
81
|
+
# @param [String] file_name The file name of the configuration
|
82
|
+
def load(file_name)
|
83
|
+
config = YAML.load_file(file_name)[current_environment.to_s]
|
84
|
+
|
85
|
+
self.database = create_database_connection_from(config)
|
86
|
+
end
|
87
|
+
|
88
|
+
def current_environment
|
89
|
+
return Rails.env if defined?(Rails)
|
90
|
+
ENV['RACK_ENV'] || ENV['GUACAMOLE_ENV']
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def configuration
|
96
|
+
@configuration ||= new
|
97
|
+
end
|
98
|
+
|
99
|
+
def create_database_connection_from(config)
|
100
|
+
Ashikawa::Core::Database.new do |arango_config|
|
101
|
+
arango_config.url = db_url_from(config)
|
102
|
+
arango_config.username = config['username']
|
103
|
+
arango_config.password = config['password']
|
104
|
+
arango_config.logger = logger
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def db_url_from(config)
|
109
|
+
"#{config['protocol']}://#{config['host']}:#{config['port']}/_db/#{config['database']}"
|
110
|
+
end
|
111
|
+
|
112
|
+
def rails_logger
|
113
|
+
return Rails.logger if defined?(Rails)
|
114
|
+
end
|
115
|
+
|
116
|
+
def default_logger
|
117
|
+
default_logger = Logger.new(STDOUT)
|
118
|
+
default_logger.level = Logger::INFO
|
119
|
+
default_logger
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
module Guacamole
|
4
|
+
# This is the default mapper class to map between Ashikawa::Core::Document and
|
5
|
+
# Guacamole::Model instances.
|
6
|
+
#
|
7
|
+
# If you want to build your own mapper, you have to build at least the
|
8
|
+
# `document_to_model` and `model_to_document` methods.
|
9
|
+
class DocumentModelMapper
|
10
|
+
# The class to map to
|
11
|
+
#
|
12
|
+
# @return [class] The class to map to
|
13
|
+
attr_reader :model_class
|
14
|
+
|
15
|
+
# The arrays embedded in this model
|
16
|
+
#
|
17
|
+
# @return [Array] An array of embedded models
|
18
|
+
attr_reader :models_to_embed
|
19
|
+
|
20
|
+
# Create a new instance of the mapper
|
21
|
+
#
|
22
|
+
# You have to provide the model class you want to map to.
|
23
|
+
# The Document class is always Ashikawa::Core::Document
|
24
|
+
#
|
25
|
+
# @param [Class] model_class
|
26
|
+
def initialize(model_class)
|
27
|
+
@model_class = model_class
|
28
|
+
@models_to_embed = []
|
29
|
+
end
|
30
|
+
|
31
|
+
# Map a document to a model
|
32
|
+
#
|
33
|
+
# Sets the revision, key and all attributes on the model
|
34
|
+
#
|
35
|
+
# @param [Ashikawa::Core::Document] document
|
36
|
+
# @return [Model] the resulting model with the given Model class
|
37
|
+
def document_to_model(document)
|
38
|
+
model = model_class.new(document.hash)
|
39
|
+
|
40
|
+
model.key = document.key
|
41
|
+
model.rev = document.revision
|
42
|
+
|
43
|
+
model
|
44
|
+
end
|
45
|
+
|
46
|
+
# Map a model to a document
|
47
|
+
#
|
48
|
+
# This will include all embedded models
|
49
|
+
#
|
50
|
+
# @param [Model] model
|
51
|
+
# @return [Ashikawa::Core::Document] the resulting document
|
52
|
+
def model_to_document(model)
|
53
|
+
document = model.attributes.dup.except(:key, :rev)
|
54
|
+
models_to_embed.each do |attribute_name|
|
55
|
+
document[attribute_name] = model.send(attribute_name).map do |embedded_model|
|
56
|
+
embedded_model.attributes.except(:key, :rev)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
document
|
60
|
+
end
|
61
|
+
|
62
|
+
# Declare a model to be embedded
|
63
|
+
#
|
64
|
+
# With embeds you can specify that the document in the
|
65
|
+
# collection embeds a document that should be mapped to
|
66
|
+
# a certain model. Your model has to specify an attribute
|
67
|
+
# with the type Array (of this model).
|
68
|
+
#
|
69
|
+
# @param [Symbol] model_name Pluralized name of the model class to embed
|
70
|
+
# @example A blogpost with embedded comments
|
71
|
+
# class BlogpostsCollection
|
72
|
+
# include Guacamole::Collection
|
73
|
+
#
|
74
|
+
# map do
|
75
|
+
# embeds :comments
|
76
|
+
# end
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# class Blogpost
|
80
|
+
# include Guacamole::Model
|
81
|
+
#
|
82
|
+
# attribute :comments, Array[Comment]
|
83
|
+
# end
|
84
|
+
#
|
85
|
+
# class Comment
|
86
|
+
# include Guacamole::Model
|
87
|
+
# end
|
88
|
+
#
|
89
|
+
# blogpost = BlogpostsCollection.find('12313121')
|
90
|
+
# p blogpost.comments #=> An Array of Comments
|
91
|
+
def embeds(model_name)
|
92
|
+
@models_to_embed << model_name
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,210 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'active_support/concern'
|
3
|
+
# Cherry Pick not possible
|
4
|
+
require 'active_model'
|
5
|
+
require 'virtus'
|
6
|
+
|
7
|
+
module Guacamole
|
8
|
+
# A domain model of your application
|
9
|
+
#
|
10
|
+
# A Guacamole::Model represents a single entry in your collection or an embedded entry
|
11
|
+
# of another entry.
|
12
|
+
# You use this as a Mixin in your model classes.
|
13
|
+
#
|
14
|
+
# @note This is not a model according to the active record pattern. It does not know anything about the database.
|
15
|
+
#
|
16
|
+
# @!attribute [r] key
|
17
|
+
# Key of the document in the collection
|
18
|
+
#
|
19
|
+
# The key identifies a document distinctly within one collection.
|
20
|
+
#
|
21
|
+
# @return [String] The key of the document
|
22
|
+
# @note Only set when persisted
|
23
|
+
#
|
24
|
+
# @!attribute [r] rev
|
25
|
+
# The revision of the document
|
26
|
+
#
|
27
|
+
# ArangoDB keeps changes the revision of a document automatically, when it has changed.
|
28
|
+
# With this functionality you can quickly find out if you have the most recent version
|
29
|
+
# of the document.
|
30
|
+
#
|
31
|
+
# @return [String] The revision of the document
|
32
|
+
# @note Only set when persisted
|
33
|
+
#
|
34
|
+
# @!attribute [r] created_at
|
35
|
+
# Timestamp of the creation of this document
|
36
|
+
#
|
37
|
+
# This will automatically be set when the document was first saved to the database.
|
38
|
+
#
|
39
|
+
# @return [Time] Timestamp of the creation of the model in the database
|
40
|
+
# @note Only set when persisted
|
41
|
+
#
|
42
|
+
# @!attribute [r] updated_at
|
43
|
+
# Timestamp of the last update of this document
|
44
|
+
#
|
45
|
+
# This will automatically be changed whenever the document is saved.
|
46
|
+
#
|
47
|
+
# @return [Time] Timestamp of the last update in the database
|
48
|
+
# @note Only set when persisted
|
49
|
+
#
|
50
|
+
# @!method self.attribute(attribute_name, type)
|
51
|
+
# Define an attribute for this model (Provided by Virtus)
|
52
|
+
#
|
53
|
+
# @note Setting the value of an attribute leads to automatic coercion
|
54
|
+
# @param attribute_name [Symbol] The name of the attribute to define
|
55
|
+
# @param type [Class] The type of the attribute
|
56
|
+
# @see https://github.com/solnic/virtus
|
57
|
+
# @api public
|
58
|
+
# @example Define an attribute of type String
|
59
|
+
# class BlogPost
|
60
|
+
# include Guacamole::Model
|
61
|
+
#
|
62
|
+
# attribute :title, String
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# @example Define an attribute containing an array of Strings
|
66
|
+
# class Repository
|
67
|
+
# include Guacamole::Model
|
68
|
+
#
|
69
|
+
# attribute :contributor_names, Array[String]
|
70
|
+
# end
|
71
|
+
#
|
72
|
+
# @!method self.validates
|
73
|
+
# This method is a shortcut to all default validators and any custom validator classes ending in 'Validator'
|
74
|
+
#
|
75
|
+
# For further details see the documentation of ActiveModel
|
76
|
+
# or the RailsGuide on Validations
|
77
|
+
#
|
78
|
+
# @see http://guides.rubyonrails.org/active_record_validations.html
|
79
|
+
# @api public
|
80
|
+
# @example Validate the presence of the name
|
81
|
+
# class Person
|
82
|
+
# include Guacamole::Model
|
83
|
+
#
|
84
|
+
# attribute :name, String
|
85
|
+
# validates :name, presence: true
|
86
|
+
# end
|
87
|
+
#
|
88
|
+
# @!method self.validate
|
89
|
+
# Adds a validation method or block to the class
|
90
|
+
#
|
91
|
+
# For further details see the documentation of ActiveModel
|
92
|
+
# or the RailsGuide on Validations
|
93
|
+
#
|
94
|
+
# @see http://guides.rubyonrails.org/active_record_validations.html
|
95
|
+
# @api public
|
96
|
+
# @example Validate that the person is awesome
|
97
|
+
# class Person
|
98
|
+
# include Guacamole::Model
|
99
|
+
# validate :is_awesome
|
100
|
+
#
|
101
|
+
# def is_awesome
|
102
|
+
# # Check if the person is awesome
|
103
|
+
# end
|
104
|
+
# end
|
105
|
+
#
|
106
|
+
# @!method self.validates_with
|
107
|
+
# Passes the record off to the class or classes specified
|
108
|
+
#
|
109
|
+
# This and allows to add errors based on more complex conditions
|
110
|
+
#
|
111
|
+
# For further details see the documentation of ActiveModel
|
112
|
+
# or the RailsGuide on Validations
|
113
|
+
#
|
114
|
+
# @see http://guides.rubyonrails.org/active_record_validations.html
|
115
|
+
# @api public
|
116
|
+
# @example Validate that the person is awesome
|
117
|
+
# class Person
|
118
|
+
# include Guacamole::Model
|
119
|
+
# validates_with PersonValidator
|
120
|
+
# end
|
121
|
+
#
|
122
|
+
# class PersonValidator < ActiveModel::Validator
|
123
|
+
# def validate(record)
|
124
|
+
# # check if the person is valid
|
125
|
+
# end
|
126
|
+
# end
|
127
|
+
#
|
128
|
+
# @!method persisted?
|
129
|
+
# Checks if the object is persisted
|
130
|
+
#
|
131
|
+
# @return [Boolean]
|
132
|
+
# @api public
|
133
|
+
#
|
134
|
+
# @!method valid?
|
135
|
+
# Runs all the specified validations and returns true if no errors were added otherwise false
|
136
|
+
#
|
137
|
+
# For further details see the documentation of ActiveModel
|
138
|
+
# or the RailsGuide on Validations
|
139
|
+
#
|
140
|
+
# @see http://guides.rubyonrails.org/active_record_validations.html
|
141
|
+
# @api public
|
142
|
+
#
|
143
|
+
# @!method invalid?
|
144
|
+
# Performs the opposite of `valid?`. Returns true if errors were added, false otherwise
|
145
|
+
#
|
146
|
+
# For further details see the documentation of ActiveModel
|
147
|
+
# or the RailsGuide on Validations
|
148
|
+
#
|
149
|
+
# @see http://guides.rubyonrails.org/active_record_validations.html
|
150
|
+
# @api public
|
151
|
+
#
|
152
|
+
# @!method errors
|
153
|
+
# Returns the Errors object that holds all information about attribute error messages
|
154
|
+
#
|
155
|
+
# For further details see the documentation of ActiveModel
|
156
|
+
# or the RailsGuide on Validations
|
157
|
+
#
|
158
|
+
# @see http://guides.rubyonrails.org/active_record_validations.html
|
159
|
+
# @api public
|
160
|
+
#
|
161
|
+
# @!method ==(other)
|
162
|
+
# Check if the attributes are from the same model class and have equal attributes
|
163
|
+
#
|
164
|
+
# @param [Model] other the model to compare with
|
165
|
+
# @api public
|
166
|
+
module Model
|
167
|
+
extend ActiveSupport::Concern
|
168
|
+
# @!parse include ActiveModel::Validations
|
169
|
+
# @!parse extend ActiveModel::Naming
|
170
|
+
# @!parse include ActiveModel::Conversion
|
171
|
+
# I know that this is technically not true, but the reality is a parse error:
|
172
|
+
# @!parse include Virtus
|
173
|
+
|
174
|
+
included do
|
175
|
+
include ActiveModel::Validations
|
176
|
+
include ActiveModel::Naming
|
177
|
+
include ActiveModel::Conversion
|
178
|
+
include Virtus.model
|
179
|
+
|
180
|
+
attribute :key, String
|
181
|
+
attribute :rev, String
|
182
|
+
attribute :created_at, Time
|
183
|
+
attribute :updated_at, Time
|
184
|
+
|
185
|
+
def persisted?
|
186
|
+
key.present?
|
187
|
+
end
|
188
|
+
|
189
|
+
# For ActiveModel::Conversion compliance only, please use key
|
190
|
+
def id
|
191
|
+
key
|
192
|
+
end
|
193
|
+
|
194
|
+
def ==(other)
|
195
|
+
other.instance_of?(self.class) &&
|
196
|
+
attributes.all? do |attribute, value|
|
197
|
+
other_value = other.send(attribute)
|
198
|
+
case value
|
199
|
+
when DateTime, Time
|
200
|
+
value.to_s == other_value.to_s # :(
|
201
|
+
else
|
202
|
+
value == other_value
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
alias_method :eql?, :==
|
207
|
+
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
module Guacamole
|
4
|
+
# Build a query for ArangoDB
|
5
|
+
class Query
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
# Connection to the database
|
9
|
+
#
|
10
|
+
# @return [Ashikawa::Core::Collection]
|
11
|
+
attr_reader :connection
|
12
|
+
|
13
|
+
# The mapper class
|
14
|
+
#
|
15
|
+
# @return [Class]
|
16
|
+
attr_reader :mapper
|
17
|
+
|
18
|
+
# The example to search for
|
19
|
+
#
|
20
|
+
# @return [Hash] the example
|
21
|
+
attr_accessor :example
|
22
|
+
|
23
|
+
# Currently set options
|
24
|
+
#
|
25
|
+
# @return [Hash]
|
26
|
+
# @api private
|
27
|
+
attr_accessor :options
|
28
|
+
|
29
|
+
# Create a new Query
|
30
|
+
#
|
31
|
+
# @param [Ashikawa::Core::Collection] connection The collection to use to talk to the database
|
32
|
+
# @param [Class] mapper the class of the mapper to use
|
33
|
+
def initialize(connection, mapper)
|
34
|
+
@connection = connection
|
35
|
+
@mapper = mapper
|
36
|
+
@options = {}
|
37
|
+
end
|
38
|
+
|
39
|
+
# Iterate over the result of the query
|
40
|
+
#
|
41
|
+
# This will execute the query you have build
|
42
|
+
def each
|
43
|
+
return to_enum(__callee__) unless block_given?
|
44
|
+
|
45
|
+
iterator = ->(document) { yield mapper.document_to_model(document) }
|
46
|
+
|
47
|
+
if example
|
48
|
+
connection.by_example(example, options).each(&iterator)
|
49
|
+
else
|
50
|
+
connection.all(options).each(&iterator)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Limit the results of the query
|
55
|
+
#
|
56
|
+
# @param [Fixnum] limit
|
57
|
+
# @return [self]
|
58
|
+
def limit(limit)
|
59
|
+
options[:limit] = limit
|
60
|
+
self
|
61
|
+
end
|
62
|
+
|
63
|
+
# The number of results to skip
|
64
|
+
#
|
65
|
+
# @param [Fixnum] skip
|
66
|
+
# @return [self]
|
67
|
+
def skip(skip)
|
68
|
+
options[:skip] = skip
|
69
|
+
self
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|