minidoc 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: bab3513e3711be6f000b2087acd5df2c327ed60f
4
+ data.tar.gz: 7cc05456284d80bbdba422cf58c93a829c45592b
5
+ SHA512:
6
+ metadata.gz: 4d4c5d33c49a995a7d65186a9da8390aac2a0ae87340ad58c38452288bea0c424ac9e7c1d58c77fc171937ae6e1c14a36620df188586b1d5b8ff8dbad5b0638e
7
+ data.tar.gz: 8fbe9a2e11e6c9c37a773f41ec0c832bbe99584170fff167aa423488e01522d8d0ef619798a6185272e763abfb4840dd89f240ed4fc1c5fb44b4b6f0a192a06e
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 1.9.3-p545
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - 2.1.1
6
+ services: mongodb
7
+ before_install:
8
+ - gem update bundler
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Bryan Helmkamp
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,76 @@
1
+ [![Code Climate](https://codeclimate.com/github/brynary/minidoc.svg)](https://codeclimate.com/github/brynary/minidoc)
2
+ [![Build Status](https://travis-ci.org/brynary/minidoc.svg)](https://travis-ci.org/brynary/minidoc)
3
+
4
+ # Minidoc
5
+
6
+ Minidoc is an extremely lightweight layer on top of the MongoDB client to
7
+ make interacting with documents from Ruby more convenient.
8
+
9
+ We rely heavily on the MongoDB client, Virtus and ActiveModel to keep
10
+ things as simple as possible.
11
+
12
+ ## Features
13
+
14
+ * Interact with Ruby objects instead of hashes
15
+ * Full access to the powerful MongoDB client
16
+ * Thread safe. (Hopefully)
17
+ * Simple and easily extensible (Less than 500 lines of code.)
18
+ * ActiveModel-compatible
19
+ * Validations
20
+ * Timestamp tracking (created_at/updated_at)
21
+ * Very basic associations (for reads)
22
+ * Conversion into immutable value objects
23
+ * Read-only records
24
+
25
+ ## Anti-Features
26
+
27
+ * Custom query API (just use Mongo)
28
+ * Callbacks (just define a method like save and call super)
29
+
30
+ ## Usage
31
+
32
+ gem "minidoc", "~> 0.0.1"
33
+
34
+ ### Basics
35
+
36
+ ```ruby
37
+ class User < Minidoc
38
+ attribute :name, String
39
+ attribute :language, String
40
+ timestamps!
41
+ end
42
+
43
+ user = User.create!(name: "Bryan", language: "Cobol")
44
+ User.count # => 1
45
+
46
+ user.language = "Lisp"
47
+ user.save!
48
+
49
+ user.set(language: "Fortran")
50
+
51
+ user.destroy
52
+ User.count # => 0
53
+ ```
54
+
55
+ ### Validations
56
+
57
+ Just uses [`ActiveModel::Validations`](http://api.rubyonrails.org/classes/ActiveModel/Validations.html):
58
+
59
+ ```ruby
60
+ class User < Minidoc
61
+ attribute :name, String
62
+
63
+ validates :name, presence: true
64
+ end
65
+
66
+ user = User.new
67
+ user.valid? # => false
68
+ user.name = "Bryan"
69
+ user.valid? # => true
70
+ ```
71
+
72
+ ### Value Objects
73
+
74
+ ### Associations
75
+
76
+ ### Read-only records
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs.push "lib"
6
+ t.ruby_opts = %w[-W0]
7
+ t.test_files = FileList['test/*_test.rb']
8
+ t.verbose = true
9
+ end
10
+
11
+ task :default => :test
@@ -0,0 +1,60 @@
1
+ require "active_support/concern"
2
+
3
+ module Minidoc::Associations
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ def associations
8
+ @associations ||= {}
9
+ end
10
+
11
+ def belongs_to(association_name, options = {})
12
+ association_name = association_name.to_sym
13
+ associations[association_name] = options
14
+
15
+ attribute "#{association_name}_id", BSON::ObjectId
16
+
17
+ define_method("#{association_name}=") do |value|
18
+ write_association(association_name, value)
19
+ end
20
+
21
+ define_method("#{association_name}_id=") do |value|
22
+ instance_variable_set("@#{association_name}", nil)
23
+ super(value)
24
+ end
25
+
26
+ define_method(association_name) do
27
+ read_association(association_name)
28
+ end
29
+ end
30
+ end
31
+
32
+ def reload
33
+ clear_association_caches
34
+ super
35
+ end
36
+
37
+ private
38
+
39
+ def write_association(name, value)
40
+ send("#{name}_id=", value ? value.id : nil)
41
+ end
42
+
43
+ def read_association(name)
44
+ return instance_variable_get("@#{name}") if instance_variable_get("@#{name}")
45
+
46
+ options = self.class.associations[name]
47
+
48
+ if (foreign_id = self["#{name}_id"])
49
+ record = options[:class_name].constantize.find(foreign_id)
50
+ instance_variable_set("@#{name}", record)
51
+ record
52
+ end
53
+ end
54
+
55
+ def clear_association_caches
56
+ self.class.associations.each do |name, options|
57
+ instance_variable_set("@#{name}", nil)
58
+ end
59
+ end
60
+ end
@@ -0,0 +1 @@
1
+ autoload :Minidoc, "minidoc"
@@ -0,0 +1,28 @@
1
+ require "active_support/concern"
2
+
3
+ module Minidoc::Connection
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ class_attribute :connection
8
+ class_attribute :database_name
9
+ end
10
+
11
+ module ClassMethods
12
+ def collection
13
+ database[collection_name]
14
+ end
15
+
16
+ def database
17
+ connection[database_name]
18
+ end
19
+
20
+ def collection_name=(name)
21
+ @collection_name = name
22
+ end
23
+
24
+ def collection_name
25
+ @collection_name ||= name.demodulize.underscore.pluralize
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,44 @@
1
+ class Minidoc
2
+ module Counters
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+ def counter(field, options = {})
9
+ start = options.fetch(:start, 0)
10
+ step_size = options.fetch(:step_size, 1)
11
+
12
+ attribute field, Integer, default: start
13
+
14
+ class_eval(<<-EOM)
15
+ def increment_#{field}
16
+ Minidoc::Counters::Incrementor.
17
+ new(self, :#{field}).increment(#{step_size})
18
+ end
19
+ EOM
20
+ end
21
+ end
22
+
23
+ class Incrementor
24
+ def initialize(record, field)
25
+ @record = record
26
+ @field = field
27
+ end
28
+
29
+ def increment(step_size = 1)
30
+ result = record.class.collection.find_and_modify(
31
+ query: { _id: record.id },
32
+ update: { "$inc" => { field => step_size } },
33
+ new: true,
34
+ )
35
+
36
+ result[field.to_s]
37
+ end
38
+
39
+ private
40
+
41
+ attr_reader :record, :field
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,12 @@
1
+ class Minidoc::DuplicateKey < Mongo::OperationFailure
2
+ DUPLICATE_KEY_ERROR_CODE = 11000
3
+
4
+ def self.duplicate_key_exception(exception)
5
+ if exception.respond_to?(:error_code) && exception.error_code == DUPLICATE_KEY_ERROR_CODE
6
+ new(exception.message, exception.error_code, exception.result)
7
+ else
8
+ nil
9
+ end
10
+ end
11
+
12
+ end
@@ -0,0 +1,50 @@
1
+ require "active_support/concern"
2
+
3
+ module Minidoc::Finders
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ def first
8
+ find_one({})
9
+ end
10
+
11
+ def count(selector = {})
12
+ collection.count(query: selector)
13
+ end
14
+
15
+ def exists?(selector = {})
16
+ count(selector) > 0
17
+ end
18
+
19
+ def find(id_or_selector, options = {})
20
+ if id_or_selector.is_a?(Hash)
21
+ options.merge!(transformer: method(:wrap))
22
+ collection.find(id_or_selector, options)
23
+ else
24
+ raise ArgumentError unless options.empty?
25
+ id = BSON::ObjectId(id_or_selector.to_s)
26
+ wrap(collection.find_one(_id: id))
27
+ end
28
+ end
29
+
30
+ def find_one(selector, options = {})
31
+ wrap(collection.find_one(selector, options))
32
+ end
33
+
34
+ def from_db(attrs)
35
+ doc = new(attrs)
36
+ doc.instance_variable_set("@new_record", false)
37
+ doc
38
+ end
39
+
40
+ def wrap(doc)
41
+ return nil unless doc
42
+
43
+ if doc.is_a?(Array) || doc.is_a?(Mongo::Cursor)
44
+ doc.map { |d| from_db(d) }
45
+ else
46
+ from_db(doc)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,16 @@
1
+ class Minidoc::Grid < Mongo::Grid
2
+ def get(id)
3
+ id = BSON::ObjectId(id.to_s)
4
+ super(id)
5
+ end
6
+
7
+ def get_json(id)
8
+ raw_data = get(id).read
9
+ JSON.parse(raw_data)
10
+ end
11
+
12
+ def delete(id)
13
+ id = BSON::ObjectId(id.to_s)
14
+ super(id)
15
+ end
16
+ end
@@ -0,0 +1,31 @@
1
+ require "active_support/concern"
2
+
3
+ module Minidoc::Indexes
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ MONGO_DUPLICATE_KEY_ERROR_CODE = 11000
8
+
9
+ def ensure_index(key_or_keys, options = {})
10
+ indexes = Array(key_or_keys).map { |key| { key => 1 } }.reduce(:merge)
11
+
12
+ collection.ensure_index(indexes, options)
13
+ end
14
+
15
+ # Rescue a <tt>Mongo::OperationFailure</tt> exception which originated from
16
+ # duplicate key error (error code 11000) in the given block.
17
+ #
18
+ # Returns the status of the operation in the given block, or +false+ if the
19
+ # exception was raised.
20
+ def rescue_duplicate_key_errors
21
+ yield
22
+ rescue Mongo::OperationFailure => exception
23
+ if exception.respond_to?(:error_code) &&
24
+ exception.error_code == MONGO_DUPLICATE_KEY_ERROR_CODE
25
+ return false
26
+ else
27
+ raise
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,11 @@
1
+ require "minidoc"
2
+ require "minidoc/value"
3
+
4
+ class Minidoc::ReadOnly < Minidoc::Value
5
+ include Minidoc::Connection
6
+ include Minidoc::Finders
7
+
8
+ def self.database
9
+ Minidoc.database
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ class Minidoc::RecordInvalid < StandardError
2
+ attr_reader :record
3
+
4
+ def initialize(record)
5
+ @record = record
6
+ errors = @record.errors.full_messages.join(", ")
7
+ super("Record invalid: #{errors}")
8
+ end
9
+ end
@@ -0,0 +1,24 @@
1
+ class Minidoc
2
+ module TestHelpers
3
+ extend self
4
+
5
+ def clear_database
6
+ clear_collections
7
+ clear_indexes
8
+ end
9
+
10
+ def clear_collections
11
+ each_collection { |c| c.remove({}) }
12
+ end
13
+
14
+ def clear_indexes
15
+ each_collection(&:drop_indexes)
16
+ end
17
+
18
+ def each_collection(&block)
19
+ Minidoc.database.collections.
20
+ reject { |c| c.name.include?("system") }.
21
+ each(&block)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,37 @@
1
+ require "active_support/concern"
2
+
3
+ module Minidoc::Timestamps
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ class_attribute :record_timestamps
8
+ end
9
+
10
+ module ClassMethods
11
+ def timestamps!
12
+ self.record_timestamps = true
13
+ attribute :created_at, Time
14
+ attribute :updated_at, Time
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def create
21
+ if self.class.record_timestamps
22
+ current_time = Time.now.utc
23
+ self.created_at = current_time
24
+ self.updated_at = current_time
25
+ end
26
+
27
+ super
28
+ end
29
+
30
+ def update
31
+ if self.class.record_timestamps
32
+ self.updated_at = Time.now.utc
33
+ end
34
+
35
+ super
36
+ end
37
+ end
@@ -0,0 +1,40 @@
1
+ module Minidoc::Validations
2
+ class UniquenessValidator < ::ActiveModel::EachValidator
3
+ def initialize(options)
4
+ super(options.reverse_merge(case_sensitive: true))
5
+ end
6
+
7
+ def validate_each(record, attribute, value)
8
+ conditions = scope_conditions(record)
9
+
10
+ if options[:case_sensitive]
11
+ conditions[attribute] = value
12
+ else
13
+ conditions[attribute] = /^#{Regexp.escape(value.to_s)}$/i
14
+ end
15
+
16
+ # Make sure we're not including the current document in the query
17
+ if record._id
18
+ conditions[:_id] = { "$ne" => record.id }
19
+ end
20
+
21
+ if record.class.exists?(conditions)
22
+ record.errors.add(
23
+ attribute,
24
+ :taken,
25
+ options.except(:case_sensitive, :scope).merge(value: value)
26
+ )
27
+ end
28
+ end
29
+
30
+ def message(instance)
31
+ super || "has already been taken"
32
+ end
33
+
34
+ def scope_conditions(instance)
35
+ Array(options[:scope]).inject({}) do |conditions, key|
36
+ conditions.merge(key => instance[key])
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,21 @@
1
+ require "minidoc"
2
+
3
+ class Minidoc::Value
4
+ include Virtus.value_object
5
+ extend ActiveModel::Naming
6
+
7
+ attribute :_id, BSON::ObjectId
8
+ alias_method :id, :_id
9
+
10
+ def to_key
11
+ [id.to_s]
12
+ end
13
+
14
+ def to_param
15
+ id.to_s
16
+ end
17
+
18
+ def as_value
19
+ self
20
+ end
21
+ end