guacamole 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|