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.
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