guacamole 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +3 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +18 -0
  6. data/CONTRIBUTING.md +26 -0
  7. data/Gemfile +16 -0
  8. data/Gemfile.devtools +55 -0
  9. data/Guardfile +13 -0
  10. data/LICENSE +202 -0
  11. data/README.md +130 -0
  12. data/Rakefile +7 -0
  13. data/config/devtools.yml +4 -0
  14. data/config/flay.yml +3 -0
  15. data/config/flog.yml +2 -0
  16. data/config/mutant.yml +3 -0
  17. data/config/reek.yml +104 -0
  18. data/config/rubocop.yml +68 -0
  19. data/config/yardstick.yml +2 -0
  20. data/guacamole.gemspec +28 -0
  21. data/lib/guacamole.rb +19 -0
  22. data/lib/guacamole/collection.rb +246 -0
  23. data/lib/guacamole/configuration.rb +123 -0
  24. data/lib/guacamole/document_model_mapper.rb +95 -0
  25. data/lib/guacamole/model.rb +210 -0
  26. data/lib/guacamole/query.rb +72 -0
  27. data/lib/guacamole/railtie.rb +27 -0
  28. data/lib/guacamole/railtie/database.rake +5 -0
  29. data/lib/guacamole/version.rb +5 -0
  30. data/log/.gitkeep +0 -0
  31. data/spec/acceptance/.gitkeep +0 -0
  32. data/spec/acceptance/basic_spec.rb +112 -0
  33. data/spec/acceptance/config/guacamole.yml +11 -0
  34. data/spec/acceptance/spec_helper.rb +61 -0
  35. data/spec/fabricators/article_fabricator.rb +10 -0
  36. data/spec/fabricators/comment_fabricator.rb +5 -0
  37. data/spec/setup/arangodb.sh +65 -0
  38. data/spec/spec_helper.rb +19 -0
  39. data/spec/unit/.gitkeep +0 -0
  40. data/spec/unit/collection_spec.rb +380 -0
  41. data/spec/unit/configuration_spec.rb +119 -0
  42. data/spec/unit/document_model_mapper_spec.rb +120 -0
  43. data/spec/unit/example_spec.rb +8 -0
  44. data/spec/unit/model_spec.rb +116 -0
  45. data/spec/unit/query_spec.rb +140 -0
  46. data/tasks/adjustments.rake +35 -0
  47. 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