class Syncano module ActiveRecord # ScopeBuilder class allows for creating and chaining more complex queries class ScopeBuilder # Constructor for ScopeBuilder # @param [Class] model def initialize(model) raise 'Model should be a class extending module Syncano::ActiveRecord::Base' unless model <= Syncano::ActiveRecord::Base self.model = model self.parameters = {} end # Returns collection of objects # @return [Array] def all folder.data_objects.all(parameters).collect do |data_object| model.new(data_object) end end # Returns count of objects # @return [Integer] def count folder.data_objects.all(parameters).count end # Returns one object found by id # @param [Integer] id # @return [Object] def find(id) parameters[:data_ids] = [id] all.first end # Returns first object or collection of first x objects # @param [Integer] amount # @return [Object, Array] def first(amount = nil) objects = all.first(amount || 1) amount.nil? ? objects.first : objects end # Returns last object or last x objects # @param [Integer] amount # @return [Object, Array] def last(amount) objects = all.last(amount || 1) amount.nil? ? objects.first : objects end # Adds to the current scope builder condition to the scope builder # @param [String] condition # @param [Array] params # @return [Syncano::ActiveRecord::ScopeBuilder] def where(condition, *params) raise 'Invalid params count in where clause!' unless condition.count('?') == params.count params.each do |param| condition.sub!('?', param.to_s) end conditions = condition.gsub(/\s+/, ' ').split(/and/i) conditions.each do |condition| attribute, operator, value = condition.split(' ') raise 'Invalid attribute in where clause!' unless model.attributes.keys.include?(attribute) raise 'Invalid operator in where clause!' unless self.class.where_mapping.keys.include?(operator) raise 'Parameter in where clause is not an integer!' if !(value =~ /\A[-+]?[0-9]+\z/) method_name = "#{model.filterable_attributes[attribute]}__#{self.class.where_mapping[operator]}" parameters[method_name] = value end self end # Adds to the current scope builder condition for filtering by parent_id # @param [Integer] parent_id # @return [Syncano::ActiveRecord::ScopeBuilder] def by_parent_id(parent_id) parameters[:parent_ids] = parent_id self end # Adds to the current scope builder order clause # @param [String] order # @return [Syncano::ActiveRecord::ScopeBuilder] def order(order) attribute, order_type = order.gsub(/\s+/, ' ').split(' ') raise 'Invalid attribute in order clause' unless (model.attributes.keys + ['id', 'created_at']).include?(attribute) attribute = model.map_to_syncano_attribute(attribute) order_type = order_type.to_s.downcase == 'desc' ? 'DESC' : 'ASC' self.parameters.merge!({ order_by: attribute, order: order_type }) self end # Adds to the current scope builder condition for filtering by ids newer than provided # @param [Integer, String] id - id or datetime # @return [Syncano::ActiveRecord::ScopeBuilder] def since(id) if !(id =~ /\A[-+]?[0-9]+\z/) self.parameters[:since] = id else self.parameters[:since_time] = id.to_time end self end # Adds to the current scope builder condition for filtering by ids older than provided # @param [Integer] id # @return [Syncano::ActiveRecord::ScopeBuilder] def before(id) self.parameters[:max_id] = id self end # Adds to the current scope builder limit clause # @param [Integer] amount # @return [Syncano::ActiveRecord::ScopeBuilder] def limit(amount) self.parameters[:limit] = amount self end private attr_accessor :parameters, :model, :scopes # Returns folder for current model # @return [Syncano::Resources::Folder] def folder model.folder end # Returns scopes for current model # @return [HashWithIndifferentAccess] def scopes model.scopes end # Returns mapping for operators # @return [Hash] def self.where_mapping { '=' => 'eq', '!=' => 'neq', '<>' => 'neq', '>=' => 'gte', '>' => 'gt', '<=' => 'lte', '<' => 'lt' } end # Applies scope to the current scope builder # @param [Symbol] name # @param [Array] args # @return [Syncano::ActiveRecord::ScopeBuilder] def execute_scope(name, *args) procedure = scopes[name] instance_exec(*args, &procedure) self end # Overwritten method_missing for handling calling defined scopes # @param [String] name # @param [Array] args def method_missing(name, *args) if scopes[name].nil? super else execute_scope(name, *args) end end end end end