Skip to content

Commit

Permalink
Merge pull request #95 from Mapotempo/add_yarddoc
Browse files Browse the repository at this point in the history
add yarddoc (N1QL, view and couchbase orm top level project)
  • Loading branch information
giallon committed Jul 1, 2024
2 parents c90712a + c24ee41 commit 3f6247d
Show file tree
Hide file tree
Showing 4 changed files with 213 additions and 52 deletions.
64 changes: 62 additions & 2 deletions lib/couchbase-orm.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
# frozen_string_literal: true, encoding: ASCII-8BIT
# frozen_string_literal: true

require 'logger'
require 'active_support/lazy_load_hooks'

# Add English locale config to load path by default.
ActiveSupport.on_load(:i18n) do
I18n.load_path << File.expand_path('couchbase-orm/locale/en.yml', __dir__)
end

# Top-level module for project.
module CouchbaseOrm
autoload :Encrypt, 'couchbase-orm/encrypt'
autoload :Error, 'couchbase-orm/error'
Expand All @@ -19,16 +20,37 @@ module CouchbaseOrm
autoload :HasMany, 'couchbase-orm/utilities/has_many'
autoload :AttributesDynamic, 'couchbase-orm/attributes/dynamic'

# if COUCHBASE_ORM_DEBUG environement variable exist then logger is set to Logger::DEBUG level
# else logger is set to Logger::INFO level
# @return [ Logger ] current logger setted for CouchbaseOrm
def self.logger
@@logger ||= defined?(Rails) ? Rails.logger : Logger.new(STDOUT).tap { |l|
l.level = Logger::INFO unless ENV['COUCHBASE_ORM_DEBUG']
}
end

# Allows you to set a logger for CouchbaseOrm,
# which can be usueful for logging messages or errors related to CouchbaseOrm
# @param [ Logger ] logger your custom logger
#
# @example Setting the logger in code
# require 'logger'
# my_logger = Logger.new(STDOUT)
# my_logger.level = Logger::DEBUG
# CouchbaseOrm.logger = my_logger
#
# @return [ Logger ] the new logger setted
def self.logger=(logger)
@@logger = logger
end

# Attempts to load a record or records from the Couchbase database.
#
# This method can handle both single IDs and arrays of IDs.
# It adapts its behavior based on the type and quantity of the input.
#
# @param [String, Array<String>] id The ID or array of IDs of the records to load.
# @return [Object, Array<Object>] The loaded model(s). Returns an array of models if the input was an array, or a single model if the input was a single ID.
def self.try_load(id)
result = nil
was_array = id.is_a?(Array)
Expand All @@ -49,6 +71,14 @@ def self.try_load(id)
try_load_create_model(result, id)
end

# Creates a model from the fetched data and ID.
#
# This method checks the type of the fetched document and matches it against the design documents of known models.
# If a match is found, it creates and returns an instance of the corresponding model.
#
# @param [Object] result The fetched record data. Expected to have a `content` method that returns a hash.
# @param [String] id The ID of the record.
# @return [Object, nil] The created model if a matching model is found, or `nil` if no match is found or if the document type is not present.
def self.try_load_create_model(result, id)
ddoc = result&.content&.[]('type')
return nil unless ddoc
Expand All @@ -62,11 +92,41 @@ def self.try_load_create_model(result, id)
end
end

# Provide Boolean conversion function
# Add method to the Kernel module, making it available in all Ruby objects since Kernel is included by Object.
# See: http://www.virtuouscode.com/2012/05/07/a-ruby-conversion-idiom/
module Kernel
private

# Converts a given value to a Boolean.
#
# This method attempts to convert different types of values to their boolean equivalents.
# - Strings and Symbols: 'true' (case-insensitive) is converted to true, 'false' (case-insensitive) is converted to false.
# - Integers: 0 is converted to false, non-zero integers are converted to true.
# - `false` and `nil` are converted to false.
# - `true` is converted to true.
# @see http://www.virtuouscode.com/2012/05/07/a-ruby-conversion-idiom/
# @param [Object] value The value to be converted to a Boolean.
# @return [Boolean] The Boolean representation of the given value.
# @raise [ArgumentError] If the value cannot be converted to a Boolean.
# @example Converting various values to Boolean
# include Kernel
#
# Boolean('true') # => true
# Boolean(' false ') # => false
# Boolean(:true) # => true
# Boolean(:false) # => false
# Boolean(1) # => true
# Boolean(0) # => false
# Boolean(nil) # => false
# Boolean(true) # => true
# Boolean(false) # => false
#
# # Invalid conversion raises ArgumentError
# begin
# Boolean('not a boolean')
# rescue ArgumentError => e
# e.message # => "invalid value for Boolean(): \"not a boolean\""
# end
def Boolean(value) # rubocop:disable Naming/MethodName
case value
when String, Symbol
Expand Down
88 changes: 72 additions & 16 deletions lib/couchbase-orm/n1ql.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,46 @@ def self.sanitize(value)
end

module ClassMethods
# Defines a query N1QL for the model
#
# @param [Symbol, String, Array] names names of the views
# @param [Hash] options options passed to the {Couchbase::N1QL}
#
# @example Define some N1QL queries for a model
# class Post < CouchbaseOrm::Base
# n1ql :by_rating, emit_key: :rating
# end
#
# Post.by_rating do |response|
# # ...
# end
# TODO: add range keys [:startkey, :endkey]
# Defines a N1QL query method with dynamic method creation.
#
# N1QL (Non-first Normal Form Query Language) is a powerful query language for Couchbase
# that allows you to perform complex queries on your data.
# For more details, see the [Couchbase N1QL Documentation](https://docs.couchbase.com/server/current/n1ql/n1ql-intro/index.html).
#
# @param name [Symbol, String] The name of the N1QL query method.
# @param query_fn [Proc, nil] An optional function to customize the query.
# @param emit_key [Symbol, Array<Symbol>] The key(s) to emit from the query (optional, defaults to an empty array).
# @param custom_order [String, nil] An optional parameter to define custom ordering for the query.
# @param options [Hash] Additional options for the N1QL query.
# @return [void]
# @raise [ArgumentError] if the class already responds to the given name.
#
# @example Define a N1QL query to find documents by `email`
# class User
# include CouchbaseOrm::Model
# n1ql :find_by_email, emit_key: :email
# end
#
# # This creates a query method `find_by_email`
# users = User.find_by_email(key: '[email protected]')
#
# @example Define a N1QL query with custom ordering
# class User
# include CouchbaseOrm::Model
# n1ql :ordered_by_creation, emit_key: :created_at, custom_order: 'ORDER BY created_at DESC'
# end
#
# # This creates a query method `ordered_by_creation`
# users = User.ordered_by_creation
#
# @example Define a N1QL query with a custom query function
# class User
# include CouchbaseOrm::Model
# n1ql :custom_query, query_fn: ->(keys) { "SELECT * FROM `bucket` WHERE #{keys.map { |k| "`#{k}` = ?" }.join(' AND ')}" }, emit_key: [:email, :username]
# end
#
# # This creates a query method `custom_query`
# users = User.custom_query(key: { email: '[email protected]', username: 'johndoe' })
def n1ql(name, query_fn: nil, emit_key: [], custom_order: nil, **options)
raise ArgumentError.new("#{self} already respond_to? #{name}") if self.respond_to?(name)

Expand Down Expand Up @@ -65,8 +91,38 @@ def n1ql(name, query_fn: nil, emit_key: [], custom_order: nil, **options)
end
N1QL_DEFAULTS = { include_docs: true }.freeze

# add a n1ql query and lookup method to the model for finding all records
# using a value in the supplied attr.
# Sets up a Couchbase N1QL query and a corresponding finder method for the given attribute.
#
# N1QL (Non-first Normal Form Query Language) is a powerful query language for Couchbase
# that allows you to perform complex queries on your data.
# For more details, see the [Couchbase N1QL Documentation](https://docs.couchbase.com/server/current/n1ql/n1ql-intro/index.html).
#
# @param attr [Symbol] The attribute to create the N1QL query and finder method for.
# @param validate [Boolean] Whether to validate the presence of the attribute (default: true).
# @param find_method [Symbol, String, nil] The name of the finder method to be created (optional).
# @param n1ql_method [Symbol, String, nil] The name of the N1QL query method to be created (optional).
# @return [void]
# @raise [ArgumentError] if the class already responds to the given name.
#
# @example Define an index N1QL query for the `email` attribute
# class User
# include CouchbaseOrm::Model
# index_n1ql :email
# end
#
# # This creates a N1QL query method `by_email` and a finder method `find_by_email`
# users = User.by_email(key: ['[email protected]'])
# user = User.find_by_email('[email protected]')
#
# @example Define an index N1QL query for the `username` attribute with custom method names
# class User
# include CouchbaseOrm::Model
# index_n1ql :username, find_method: :find_user_by_username, n1ql_method: :by_username_n1ql
# end
#
# # This creates a N1QL query method `by_username_n1ql` and a finder method `find_user_by_username`
# users = User.by_username_n1ql(key: ['john_doe'])
# user = User.find_user_by_username('john_doe')
def index_n1ql(attr, validate: true, find_method: nil, n1ql_method: nil)
n1ql_method ||= "by_#{attr}"
find_method ||= "find_#{n1ql_method}"
Expand Down
95 changes: 79 additions & 16 deletions lib/couchbase-orm/views.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,52 @@ module Views
extend ActiveSupport::Concern

module ClassMethods
# Defines a view for the model
#
# @param [Symbol, String, Array] names names of the views
# @param [Hash] options options passed to the {Couchbase::View}
#
# @example Define some views for a model
# class Post < CouchbaseOrm::Base
# view :all
# view :by_rating, emit_key: :rating
# end
#
# Post.by_rating do |response|
# # ...
# end
# Defines a Couchbase view with dynamic method creation.
#
# Couchbase views allow you to create custom queries on your data using map and reduce functions.
# They are defined in design documents and can be queried to retrieve documents or aggregated data.
# For more details, see the [Couchbase Views Basics](https://docs.couchbase.com/server/current/learn/views/views-basics.html).
#
# @param name [Symbol, String] The name of the view.
# @param map [String, nil] The map function for the view (optional).
# @param emit_key [Symbol, Array<Symbol>, nil] The key(s) to emit from the map function (optional).
# @param reduce [String, nil] The reduce function for the view (optional).
# @param options [Hash] Additional options for the view query.
# @return [void]
# @raise [ArgumentError] if the class already responds to the given name.
#
# @example Define a simple view to emit documents by `created_at`
# class MyModel
# include CouchbaseOrm::Model
# view :by_created_at, emit_key: :created_at
# end
#
# # You can now use the dynamically defined method:
# results = MyModel.by_created_at
#
# @example Define a view with multiple emit keys
# class MyModel
# include CouchbaseOrm::Model
# view :by_user_and_date, emit_key: [:user_id, :created_at]
# end
#
# # Use the defined view method
# results = MyModel.by_user_and_date
#
# @example Define a view with a custom map function
# class MyModel
# include CouchbaseOrm::Model
# view :by_custom_map, map: <<-MAP
# function (doc) {
# if (doc.type === "my_model") {
# emit(doc.custom_field, null);
# }
# }
# MAP
# end
#
# # Use the defined view method
# results = MyModel.by_custom_map
def view(name, map: nil, emit_key: nil, reduce: nil, **options)
raise ArgumentError.new("#{self} already respond_to? #{name}") if self.respond_to?(name)

Expand Down Expand Up @@ -81,8 +113,37 @@ def view(name, map: nil, emit_key: nil, reduce: nil, **options)
end
ViewDefaults = {include_docs: true}.freeze

# add a view and lookup method to the model for finding all records
# using a value in the supplied attr.
# Sets up a Couchbase view and a corresponding finder method for the given attribute.
#
# Couchbase views allow you to create custom queries on your data using map and reduce functions.
# They are defined in design documents and can be queried to retrieve documents or aggregated data.
# For more details, see the [Couchbase Views Basics](https://docs.couchbase.com/server/current/learn/views/views-basics.html).
#
# @param attr [Symbol] The attribute to create the view and finder method for.
# @param validate [Boolean] Whether to validate the presence of the attribute (default: true).
# @param find_method [Symbol, String, nil] The name of the finder method to be created (optional).
# @param view_method [Symbol, String, nil] The name of the view method to be created (optional).
# @return [void]
#
# @example Define an index view for the `email` attribute
# class User
# include CouchbaseOrm::Model
# index_view :email
# end
#
# # This creates a view method `by_email` and a finder method `find_by_email`
# users_by_email = User.by_email(key: '[email protected]')
# user = User.find_by_email('[email protected]')
#
# @example Define an index view for the `username` attribute with custom method names
# class User
# include CouchbaseOrm::Model
# index_view :username, find_method: :find_user_by_username, view_method: :by_username_view
# end
#
# # This creates a view method `by_username_view` and a finder method `find_user_by_username`
# users_by_username = User.by_username_view(key: 'john_doe')
# user = User.find_user_by_username('john_doe')
def index_view(attr, validate: true, find_method: nil, view_method: nil)
view_method ||= "by_#{attr}"
find_method ||= "find_#{view_method}"
Expand Down Expand Up @@ -150,6 +211,8 @@ def ensure_design_document!
end
end

private

def include_docs(view_result)
if view_result.rows.length > 1
self.find(view_result.rows.map(&:id))
Expand Down
18 changes: 0 additions & 18 deletions lib/rails/generators/couchbase_orm_generator.rb
Original file line number Diff line number Diff line change
@@ -1,23 +1,5 @@
# frozen_string_literal: true

#
# Author:: Couchbase <[email protected]>
# Copyright:: 2012 Couchbase, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

require 'rails/generators/named_base'
require 'rails/generators/active_model'

Expand Down

0 comments on commit 3f6247d

Please sign in to comment.