diff --git a/lib/datagrid/columns.rb b/lib/datagrid/columns.rb index 4b182d0..be7fc26 100644 --- a/lib/datagrid/columns.rb +++ b/lib/datagrid/columns.rb @@ -44,7 +44,6 @@ def self.included(base) class_attribute :cached, default: false class_attribute :decorator, instance_writer: false end - base.include InstanceMethods end module ClassMethods @@ -196,326 +195,323 @@ def find_column_by_name(columns,name) end - module InstanceMethods - - # @!visibility private - def assets - append_column_preload( - driver.append_column_queries( - super, columns.select(&:query) - ) + # @!visibility private + def assets + append_column_preload( + driver.append_column_queries( + super, columns.select(&:query) ) - end + ) + end - # @param column_names [Array] list of column names if you want to limit data only to specified columns - # @return [Array] human readable column names. See also "Localization" section - def header(*column_names) - data_columns(*column_names).map(&:header) - end + # @param column_names [Array] list of column names if you want to limit data only to specified columns + # @return [Array] human readable column names. See also "Localization" section + def header(*column_names) + data_columns(*column_names).map(&:header) + end - # @param asset [Object] asset from datagrid scope - # @param column_names [Array] list of column names if you want to limit data only to specified columns - # @return [Array] column values for given asset - def row_for(asset, *column_names) - data_columns(*column_names).map do |column| - data_value(column, asset) - end + # @param asset [Object] asset from datagrid scope + # @param column_names [Array] list of column names if you want to limit data only to specified columns + # @return [Array] column values for given asset + def row_for(asset, *column_names) + data_columns(*column_names).map do |column| + data_value(column, asset) end + end - # @param asset [Object] asset from datagrid scope - # @return [Hash] A mapping where keys are column names and values are column values for the given asset - def hash_for(asset) - result = {} - self.data_columns.each do |column| - result[column.name] = data_value(column, asset) - end - result + # @param asset [Object] asset from datagrid scope + # @return [Hash] A mapping where keys are column names and values are column values for the given asset + def hash_for(asset) + result = {} + self.data_columns.each do |column| + result[column.name] = data_value(column, asset) end + result + end - # @param column_names [Array] list of column names if you want to limit data only to specified columns - # @return [Array>] with data for each row in datagrid assets without header - def rows(*column_names) - map_with_batches do |asset| - self.row_for(asset, *column_names) - end + # @param column_names [Array] list of column names if you want to limit data only to specified columns + # @return [Array>] with data for each row in datagrid assets without header + def rows(*column_names) + map_with_batches do |asset| + self.row_for(asset, *column_names) end + end - # @param column_names [Array] list of column names if you want to limit data only to specified columns - # @return [Array>] data for each row in datagrid assets with header. - def data(*column_names) - self.rows(*column_names).unshift(self.header(*column_names)) - end + # @param column_names [Array] list of column names if you want to limit data only to specified columns + # @return [Array>] data for each row in datagrid assets with header. + def data(*column_names) + self.rows(*column_names).unshift(self.header(*column_names)) + end - # Return Array of Hashes where keys are column names and values are column values - # for each row in filtered datagrid relation. - # - # @example - # class MyGrid - # scope { Model } - # column(:id) - # column(:name) - # end - # - # Model.create!(name: "One") - # Model.create!(name: "Two") - # - # MyGrid.new.data_hash # => [{name: "One"}, {name: "Two"}] - def data_hash - map_with_batches do |asset| - hash_for(asset) - end + # Return Array of Hashes where keys are column names and values are column values + # for each row in filtered datagrid relation. + # + # @example + # class MyGrid + # scope { Model } + # column(:id) + # column(:name) + # end + # + # Model.create!(name: "One") + # Model.create!(name: "Two") + # + # MyGrid.new.data_hash # => [{name: "One"}, {name: "Two"}] + def data_hash + map_with_batches do |asset| + hash_for(asset) end + end - # @param column_names [Array] - # @param options [Hash] CSV generation options - # @return [String] a CSV representation of the data in the grid - # - # @example - # grid.to_csv - # grid.to_csv(:id, :name) - # grid.to_csv(col_sep: ';') - def to_csv(*column_names, **options) - require "csv" - CSV.generate( - headers: self.header(*column_names), - write_headers: true, - **options - ) do |csv| - each_with_batches do |asset| - csv << row_for(asset, *column_names) - end + # @param column_names [Array] + # @param options [Hash] CSV generation options + # @return [String] a CSV representation of the data in the grid + # + # @example + # grid.to_csv + # grid.to_csv(:id, :name) + # grid.to_csv(col_sep: ';') + def to_csv(*column_names, **options) + require "csv" + CSV.generate( + headers: self.header(*column_names), + write_headers: true, + **options + ) do |csv| + each_with_batches do |asset| + csv << row_for(asset, *column_names) end end + end - # @param column_names [Array] - # @return [Array] all columns selected in grid instance - # @example - # MyGrid.new.columns # => all defined columns - # grid = MyGrid.new(column_names: [:id, :name]) - # grid.columns # => id and name columns - # grid.columns(:id, :category) # => id and category column - def columns(*column_names, data: false, html: false) - self.class.filter_columns( - columns_array, *column_names, data: data, html: html - ).select do |column| - column.enabled?(self) - end + # @param column_names [Array] + # @return [Array] all columns selected in grid instance + # @example + # MyGrid.new.columns # => all defined columns + # grid = MyGrid.new(column_names: [:id, :name]) + # grid.columns # => id and name columns + # grid.columns(:id, :category) # => id and category column + def columns(*column_names, data: false, html: false) + self.class.filter_columns( + columns_array, *column_names, data: data, html: html + ).select do |column| + column.enabled?(self) end + end - # @param column_names [Array] list of column names if you want to limit data only to specified columns - # @return columns that can be represented in plain data(non-html) way - def data_columns(*column_names, **options) - self.columns(*column_names, **options, data: true) - end + # @param column_names [Array] list of column names if you want to limit data only to specified columns + # @return columns that can be represented in plain data(non-html) way + def data_columns(*column_names, **options) + self.columns(*column_names, **options, data: true) + end - # @param column_names [Array] list of column names if you want to limit data only to specified columns - # @return all columns that can be represented in HTML table - def html_columns(*column_names, **options) - self.columns(*column_names, **options, html: true) - end + # @param column_names [Array] list of column names if you want to limit data only to specified columns + # @return all columns that can be represented in HTML table + def html_columns(*column_names, **options) + self.columns(*column_names, **options, html: true) + end - # Finds a column definition by name - # @param name [String, Symbol] column name to be found - # @return [Datagrid::Columns::Column, nil] - def column_by_name(name) - self.class.find_column_by_name(columns_array, name) - end + # Finds a column definition by name + # @param name [String, Symbol] column name to be found + # @return [Datagrid::Columns::Column, nil] + def column_by_name(name) + self.class.find_column_by_name(columns_array, name) + end - # Gives ability to have a different formatting for CSV and HTML column value. - # - # @example - # column(:name) do |model| - # format(model.name) do |value| - # content_tag(:strong, value) - # end - # end - # - # column(:company) do |model| - # format(model.company.name) do - # render partial: "company_with_logo", locals: {company: model.company } - # end - # end - # @return [Datagrid::Columns::Column::ResponseFormat] Format object - def format(value, &block) - if block_given? - self.class.format(value, &block) - else - # don't override Object#format method - super - end + # Gives ability to have a different formatting for CSV and HTML column value. + # + # @example + # column(:name) do |model| + # format(model.name) do |value| + # content_tag(:strong, value) + # end + # end + # + # column(:company) do |model| + # format(model.company.name) do + # render partial: "company_with_logo", locals: {company: model.company } + # end + # end + # @return [Datagrid::Columns::Column::ResponseFormat] Format object + def format(value, &block) + if block_given? + self.class.format(value, &block) + else + # don't override Object#format method + super end + end - # @return [Datagrid::Columns::DataRow] an object representing a grid row. - # @example - # class MyGrid - # scope { User } - # column(:id) - # column(:name) - # column(:number_of_purchases) do |user| - # user.purchases.count - # end - # end - # - # row = MyGrid.new.data_row(User.last) - # row.id # => user.id - # row.number_of_purchases # => user.purchases.count - def data_row(asset) - ::Datagrid::Columns::DataRow.new(self, asset) - end + # @return [Datagrid::Columns::DataRow] an object representing a grid row. + # @example + # class MyGrid + # scope { User } + # column(:id) + # column(:name) + # column(:number_of_purchases) do |user| + # user.purchases.count + # end + # end + # + # row = MyGrid.new.data_row(User.last) + # row.id # => user.id + # row.number_of_purchases # => user.purchases.count + def data_row(asset) + ::Datagrid::Columns::DataRow.new(self, asset) + end - # Defines a column at instance level - # - # @see Datagrid::Columns::ClassMethods#column - def column(name, query = nil, **options, &block) - self.class.define_column(columns_array, name, query, **options, &block) - end + # Defines a column at instance level + # + # @see Datagrid::Columns::ClassMethods#column + def column(name, query = nil, **options, &block) + self.class.define_column(columns_array, name, query, **options, &block) + end - # @!visibility private - def initialize(*) - self.columns_array = self.class.columns_array.clone - super + # @!visibility private + def initialize(*) + self.columns_array = self.class.columns_array.clone + super + end + + # @return [Array] all columns that are possible to be displayed for the current grid object + # + # @example + # class MyGrid + # filter(:search) {|scope, value| scope.full_text_search(value)} + # column(:id) + # column(:name, mandatory: true) + # column(:search_match, if: proc {|grid| grid.search.present? }) do |model, grid| + # search_match_line(model.searchable_content, grid.search) + # end + # end + # + # grid = MyGrid.new + # grid.columns # => [ # ] + # grid.available_columns # => [ #, # ] + # grid.search = "keyword" + # grid.available_columns # => [ #, #, # ] + def available_columns + columns_array.select do |column| + column.enabled?(self) end + end - # @return [Array] all columns that are possible to be displayed for the current grid object - # - # @example - # class MyGrid - # filter(:search) {|scope, value| scope.full_text_search(value)} - # column(:id) - # column(:name, mandatory: true) - # column(:search_match, if: proc {|grid| grid.search.present? }) do |model, grid| - # search_match_line(model.searchable_content, grid.search) - # end - # end - # - # grid = MyGrid.new - # grid.columns # => [ # ] - # grid.available_columns # => [ #, # ] - # grid.search = "keyword" - # grid.available_columns # => [ #, #, # ] - def available_columns - columns_array.select do |column| - column.enabled?(self) - end + # @return [Object] a cell data value for given column name and asset + def data_value(column_name, asset) + column = column_by_name(column_name) + cache(column, asset, :data_value) do + raise "no data value for #{column.name} column" unless column.data? + result = generic_value(column, asset) + result.is_a?(Datagrid::Columns::Column::ResponseFormat) ? result.call_data : result end + end - # @return [Object] a cell data value for given column name and asset - def data_value(column_name, asset) - column = column_by_name(column_name) - cache(column, asset, :data_value) do - raise "no data value for #{column.name} column" unless column.data? + # @return [Object] a cell HTML value for given column name and asset and view context + def html_value(column_name, context, asset) + column = column_by_name(column_name) + cache(column, asset, :html_value) do + if column.html? && column.html_block + value_from_html_block(context, asset, column) + else result = generic_value(column, asset) - result.is_a?(Datagrid::Columns::Column::ResponseFormat) ? result.call_data : result + result.is_a?(Datagrid::Columns::Column::ResponseFormat) ? result.call_html(context) : result end end + end - # @return [Object] a cell HTML value for given column name and asset and view context - def html_value(column_name, context, asset) - column = column_by_name(column_name) - cache(column, asset, :html_value) do - if column.html? && column.html_block - value_from_html_block(context, asset, column) - else - result = generic_value(column, asset) - result.is_a?(Datagrid::Columns::Column::ResponseFormat) ? result.call_html(context) : result - end + # @return [Object] a decorated version of given model if decorator is specified or the model otherwise. + def decorate(model) + self.class.decorate(model) + end + + # @!visibility private + def generic_value(column, model) + cache(column, model, :generic_value) do + presenter = decorate(model) + unless column.enabled?(self) + raise Datagrid::ColumnUnavailableError, "Column #{column.name} disabled for #{inspect}" end - end - # @return [Object] a decorated version of given model if decorator is specified or the model otherwise. - def decorate(model) - self.class.decorate(model) + if column.data_block.arity >= 1 + Datagrid::Utils.apply_args(presenter, self, data_row(model), &column.data_block) + else + presenter.instance_eval(&column.data_block) + end end - # @!visibility private - def generic_value(column, model) - cache(column, model, :generic_value) do - presenter = decorate(model) - unless column.enabled?(self) - raise Datagrid::ColumnUnavailableError, "Column #{column.name} disabled for #{inspect}" - end + end - if column.data_block.arity >= 1 - Datagrid::Utils.apply_args(presenter, self, data_row(model), &column.data_block) - else - presenter.instance_eval(&column.data_block) - end - end + protected + def append_column_preload(relation) + columns.inject(relation) do |current, column| + column.append_preload(current) end + end - protected - - def append_column_preload(relation) - columns.inject(relation) do |current, column| - column.append_preload(current) - end + def cache(column, asset, type) + @cache ||= {} + unless cached? + @cache.clear + return yield end - - def cache(column, asset, type) - @cache ||= {} - unless cached? - @cache.clear - return yield - end - key = cache_key(asset) - unless key - raise(Datagrid::CacheKeyError, "Datagrid Cache key is #{key.inspect} for #{asset.inspect}.") - end - @cache[column.name] ||= {} - @cache[column.name][key] ||= {} - @cache[column.name][key][type] ||= yield + key = cache_key(asset) + unless key + raise(Datagrid::CacheKeyError, "Datagrid Cache key is #{key.inspect} for #{asset.inspect}.") end + @cache[column.name] ||= {} + @cache[column.name][key] ||= {} + @cache[column.name][key][type] ||= yield + end - def cache_key(asset) - if cached.respond_to?(:call) - cached.call(asset) - else - driver.default_cache_key(asset) - end - rescue NotImplementedError - raise Datagrid::ConfigurationError, "#{self} is setup to use cache. But there was appropriate cache key found for #{asset.inspect}. Please set cached option to block with asset as argument and cache key as returning value to resolve the issue." + def cache_key(asset) + if cached.respond_to?(:call) + cached.call(asset) + else + driver.default_cache_key(asset) end + rescue NotImplementedError + raise Datagrid::ConfigurationError, "#{self} is setup to use cache. But there was appropriate cache key found for #{asset.inspect}. Please set cached option to block with asset as argument and cache key as returning value to resolve the issue." + end - def map_with_batches(&block) - result = [] - each_with_batches do |asset| - result << block.call(asset) - end - result + def map_with_batches(&block) + result = [] + each_with_batches do |asset| + result << block.call(asset) end + result + end - def each_with_batches(&block) - if batch_size && batch_size > 0 - driver.batch_each(assets, batch_size, &block) - else - assets.each(&block) - end + def each_with_batches(&block) + if batch_size && batch_size > 0 + driver.batch_each(assets, batch_size, &block) + else + assets.each(&block) end + end - def value_from_html_block(context, asset, column) - args = [] - remaining_arity = column.html_block.arity - remaining_arity = 1 if remaining_arity < 0 + def value_from_html_block(context, asset, column) + args = [] + remaining_arity = column.html_block.arity + remaining_arity = 1 if remaining_arity < 0 - asset = decorate(asset) + asset = decorate(asset) - if column.data? - args << data_value(column, asset) - remaining_arity -= 1 - end + if column.data? + args << data_value(column, asset) + remaining_arity -= 1 + end - args << asset if remaining_arity > 0 - args << self if remaining_arity > 1 + args << asset if remaining_arity > 0 + args << self if remaining_arity > 1 - context.instance_exec(*args, &column.html_block) - end + context.instance_exec(*args, &column.html_block) end # Object representing a single row of data when building a datagrid table - # @see Datagrid::Columns::InstanceMethods#data_row + # @see Datagrid::Columns#data_row class DataRow < BasicObject def initialize(grid, model) @grid = grid diff --git a/lib/datagrid/core.rb b/lib/datagrid/core.rb index 661a4c6..2074b85 100644 --- a/lib/datagrid/core.rb +++ b/lib/datagrid/core.rb @@ -4,6 +4,7 @@ module Datagrid module Core + include ::ActiveModel::AttributeAssignment # @!visibility private def self.included(base) @@ -13,9 +14,7 @@ def self.included(base) class_attribute :datagrid_attributes, instance_writer: false, default: [] class_attribute :dynamic_block, instance_writer: false class_attribute :forbidden_attributes_protection, instance_writer: false, default: false - include ::ActiveModel::AttributeAssignment end - base.include InstanceMethods end module ClassMethods @@ -124,152 +123,149 @@ def inherited(child_class) end - module InstanceMethods + def initialize(attributes = nil, &block) + super() - def initialize(attributes = nil, &block) - super() - - if attributes - self.attributes = attributes - end + if attributes + self.attributes = attributes + end - instance_eval(&dynamic_block) if dynamic_block - if block_given? - self.scope(&block) - end + instance_eval(&dynamic_block) if dynamic_block + if block_given? + self.scope(&block) end + end - # @return [Hash] grid attributes including filter values and ordering values - def attributes - result = {} - self.datagrid_attributes.each do |name| - result[name] = self[name] - end - result + # @return [Hash] grid attributes including filter values and ordering values + def attributes + result = {} + self.datagrid_attributes.each do |name| + result[name] = self[name] end + result + end - # Updates datagrid attributes with a passed hash argument - # @param attributes [Hash] - # @example - # grid = MyGrid.new - # grid.attributes = {first_name: 'John', last_name: 'Smith'} - # grid.first_name # => 'John' - # grid.last_name # => 'Smith' - def attributes=(attributes) - super(attributes) - end + # Updates datagrid attributes with a passed hash argument + # @param attributes [Hash] + # @example + # grid = MyGrid.new + # grid.attributes = {first_name: 'John', last_name: 'Smith'} + # grid.first_name # => 'John' + # grid.last_name # => 'Smith' + def attributes=(attributes) + super(attributes) + end - # @return [Object] Any datagrid attribute value - def [](attribute) - self.public_send(attribute) - end + # @return [Object] Any datagrid attribute value + def [](attribute) + self.public_send(attribute) + end - # Assigns any datagrid attribute - # @param attribute [Symbol, String] Datagrid attribute name - # @param value [Object] Datagrid attribute value - # @return [void] - def []=(attribute, value) - self.public_send(:"#{attribute}=", value) - end + # Assigns any datagrid attribute + # @param attribute [Symbol, String] Datagrid attribute name + # @param value [Object] Datagrid attribute value + # @return [void] + def []=(attribute, value) + self.public_send(:"#{attribute}=", value) + end - # @return [Object] a scope relation (e.g ActiveRecord::Relation) with all applied filters - def assets - scope - end + # @return [Object] a scope relation (e.g ActiveRecord::Relation) with all applied filters + def assets + scope + end - # Returns serializable query arguments skipping all nil values - # @example - # grid = ProductsGrid.new(category: 'dresses', available: true) - # grid.as_query # => {category: 'dresses', available: true} - def as_query - attributes = self.attributes.clone - attributes.each do |key, value| - attributes.delete(key) if value.nil? - end - attributes + # Returns serializable query arguments skipping all nil values + # @example + # grid = ProductsGrid.new(category: 'dresses', available: true) + # grid.as_query # => {category: 'dresses', available: true} + def as_query + attributes = self.attributes.clone + attributes.each do |key, value| + attributes.delete(key) if value.nil? end + attributes + end - # @return [Hash>] query parameters to link this grid from a page - # @example - # grid = ProductsGrid.new(category: 'dresses', available: true) - # Rails.application.routes.url_helpers.products_path(grid.query_params) - # # => "/products?products_grid[category]=dresses&products_grid[available]=true" - def query_params(attributes = {}) - { param_name.to_sym => as_query.merge(attributes) } - end + # @return [Hash>] query parameters to link this grid from a page + # @example + # grid = ProductsGrid.new(category: 'dresses', available: true) + # Rails.application.routes.url_helpers.products_path(grid.query_params) + # # => "/products?products_grid[category]=dresses&products_grid[available]=true" + def query_params(attributes = {}) + { param_name.to_sym => as_query.merge(attributes) } + end - # Redefines scope at instance level - # @example - # class MyGrid - # scope { Article.order('created_at desc') } - # end - # - # grid = MyGrid.new - # grid.scope do |scope| - # scope.where(author_id: current_user.id) - # end - # grid.assets - # # => SELECT * FROM articles WHERE author_id = ? - # # ORDER BY created_at desc - def scope(&block) - if block_given? - current_scope = scope - self.scope_value = proc { - Datagrid::Utils.apply_args(current_scope, &block) - } - self - else - scope = original_scope - driver.to_scope(scope) - end + # Redefines scope at instance level + # @example + # class MyGrid + # scope { Article.order('created_at desc') } + # end + # + # grid = MyGrid.new + # grid.scope do |scope| + # scope.where(author_id: current_user.id) + # end + # grid.assets + # # => SELECT * FROM articles WHERE author_id = ? + # # ORDER BY created_at desc + def scope(&block) + if block_given? + current_scope = scope + self.scope_value = proc { + Datagrid::Utils.apply_args(current_scope, &block) + } + self + else + scope = original_scope + driver.to_scope(scope) end + end - # @!visibility private - def original_scope - check_scope_defined! - scope_value.call - end + # @!visibility private + def original_scope + check_scope_defined! + scope_value.call + end - # Resets current instance scope to default scope defined in a class - # @return [void] - def reset_scope - self.scope_value = self.class.scope_value - end + # Resets current instance scope to default scope defined in a class + # @return [void] + def reset_scope + self.scope_value = self.class.scope_value + end - # @return [Boolean] true if the scope was redefined for this instance of grid object - def redefined_scope? - self.class.scope_value != scope_value - end + # @return [Boolean] true if the scope was redefined for this instance of grid object + def redefined_scope? + self.class.scope_value != scope_value + end - # @!visibility private - def driver - self.class.driver - end + # @!visibility private + def driver + self.class.driver + end - # @!visibility private - def check_scope_defined!(message = nil) - self.class.send :check_scope_defined!, message - end + # @!visibility private + def check_scope_defined!(message = nil) + self.class.send :check_scope_defined!, message + end - # @return [String] a datagrid attributes and their values in inspection form - def inspect - attrs = attributes.map do |key, value| - "#{key}: #{value.inspect}" - end.join(", ") - "#<#{self.class} #{attrs}>" - end + # @return [String] a datagrid attributes and their values in inspection form + def inspect + attrs = attributes.map do |key, value| + "#{key}: #{value.inspect}" + end.join(", ") + "#<#{self.class} #{attrs}>" + end - def ==(other) - self.class == other.class && - attributes == other.attributes && - scope == other.scope - end + def ==(other) + self.class == other.class && + attributes == other.attributes && + scope == other.scope + end - protected - def sanitize_for_mass_assignment(attributes) - forbidden_attributes_protection ? super(attributes) : attributes - end + protected + def sanitize_for_mass_assignment(attributes) + forbidden_attributes_protection ? super(attributes) : attributes end end end diff --git a/lib/datagrid/filters.rb b/lib/datagrid/filters.rb index ac85558..7b3e9b9 100644 --- a/lib/datagrid/filters.rb +++ b/lib/datagrid/filters.rb @@ -41,7 +41,6 @@ def self.included(base) class_attribute :filters_array, default: [] end - base.include InstanceMethods end module ClassMethods @@ -137,92 +136,90 @@ def filters_inspection end end - module InstanceMethods - # @!visibility private - def initialize(*args, &block) - self.filters_array = self.class.filters_array.clone - self.filters_array.each do |filter| - self[filter.name] = filter.default(self) - end - super(*args, &block) + # @!visibility private + def initialize(*args, &block) + self.filters_array = self.class.filters_array.clone + self.filters_array.each do |filter| + self[filter.name] = filter.default(self) end + super(*args, &block) + end - # @!visibility private - def assets - apply_filters(super, filters) - end + # @!visibility private + def assets + apply_filters(super, filters) + end - # Returns filter value for given filter definition - def filter_value(filter) - self[filter.name] - end + # Returns filter value for given filter definition + def filter_value(filter) + self[filter.name] + end - # Returns string representation of filter value - def filter_value_as_string(name) - filter = filter_by_name(name) - value = filter_value(filter) - if value.is_a?(Array) - value.map {|v| filter.format(v) }.join(filter.separator) - else - filter.format(value) - end + # Returns string representation of filter value + def filter_value_as_string(name) + filter = filter_by_name(name) + value = filter_value(filter) + if value.is_a?(Array) + value.map {|v| filter.format(v) }.join(filter.separator) + else + filter.format(value) end + end - # Returns filter object with the given name - def filter_by_name(name) - self.class.filter_by_name(name) - end + # Returns filter object with the given name + def filter_by_name(name) + self.class.filter_by_name(name) + end - # Returns assets filtered only by specified filters - # Allows partial filtering - def filter_by(*filters) - apply_filters(scope, filters.map{|f| filter_by_name(f)}) - end + # Returns assets filtered only by specified filters + # Allows partial filtering + def filter_by(*filters) + apply_filters(scope, filters.map{|f| filter_by_name(f)}) + end - # Returns select options for specific filter or filter name - # If given filter doesn't support select options raises `ArgumentError` - def select_options(filter) - find_select_filter(filter).select(self) - end + # Returns select options for specific filter or filter name + # If given filter doesn't support select options raises `ArgumentError` + def select_options(filter) + find_select_filter(filter).select(self) + end - # Sets all options as selected for a filter that has options - def select_all(filter) - filter = find_select_filter(filter) - self[filter.name] = select_values(filter) - end + # Sets all options as selected for a filter that has options + def select_all(filter) + filter = find_select_filter(filter) + self[filter.name] = select_values(filter) + end - # Returns all values that can be set to a filter with select options - def select_values(filter) - find_select_filter(filter).select_values(self) - end + # Returns all values that can be set to a filter with select options + def select_values(filter) + find_select_filter(filter).select_values(self) + end - def default_filter - self.class.default_filter - end + def default_filter + self.class.default_filter + end - # Returns all currently enabled filters - def filters - self.class.filters.select do |filter| - filter.enabled?(self) - end + # Returns all currently enabled filters + def filters + self.class.filters.select do |filter| + filter.enabled?(self) end + end - protected + protected - def find_select_filter(filter) - filter = filter_by_name(filter) - unless filter.class.included_modules.include?(::Datagrid::Filters::SelectOptions) - raise ::Datagrid::ArgumentError, - "#{self.class.name}##{filter.name} with type #{FILTER_TYPES.invert[filter.class].inspect} can not have select options" - end - filter + def find_select_filter(filter) + filter = filter_by_name(filter) + unless filter.class.included_modules.include?(::Datagrid::Filters::SelectOptions) + raise ::Datagrid::ArgumentError, + "#{self.class.name}##{filter.name} with type #{FILTER_TYPES.invert[filter.class].inspect} can not have select options" end + filter + end - def apply_filters(current_scope, filters) - filters.inject(current_scope) do |result, filter| - filter.apply(self, result, filter_value(filter)) - end + def apply_filters(current_scope, filters) + filters.inject(current_scope) do |result, filter| + filter.apply(self, result, filter_value(filter)) end end end diff --git a/lib/datagrid/ordering.rb b/lib/datagrid/ordering.rb index 965a0aa..0f6cbee 100644 --- a/lib/datagrid/ordering.rb +++ b/lib/datagrid/ordering.rb @@ -27,7 +27,6 @@ def self.included(base) alias descending? descending end - base.include InstanceMethods end # @!visibility private @@ -37,96 +36,94 @@ def order_unsupported(name, reason) end end - module InstanceMethods - # @!visibility private - def assets - check_order_valid! - apply_order(super) - end + # @!visibility private + def assets + check_order_valid! + apply_order(super) + end - # @return [Datagrid::Columns::Column, nil] a column definition that is currently used to order assets - # @example - # class MyGrid - # scope { Model } - # column(:id) - # column(:name) - # end - # MyGrid.new(order: "name").order_column # => # - def order_column - order ? column_by_name(order) : nil - end + # @return [Datagrid::Columns::Column, nil] a column definition that is currently used to order assets + # @example + # class MyGrid + # scope { Model } + # column(:id) + # column(:name) + # end + # MyGrid.new(order: "name").order_column # => # + def order_column + order ? column_by_name(order) : nil + end - # @param column [String, Datagrid::Columns::Column] - # @return [Boolean] true if given grid is ordered by given column. - def ordered_by?(column) - order_column == column_by_name(column) - end + # @param column [String, Datagrid::Columns::Column] + # @return [Boolean] true if given grid is ordered by given column. + def ordered_by?(column) + order_column == column_by_name(column) + end - private + private - def apply_order(assets) - return assets unless order - if order_column.order_by_value? - assets = assets.sort_by do |asset| - order_column.order_by_value(asset, self) - end - descending? ? assets.reverse : assets - else - if descending? - if order_column.order_desc - apply_asc_order(assets, order_column.order_desc) - else - apply_desc_order(assets, order_column.order) - end + def apply_order(assets) + return assets unless order + if order_column.order_by_value? + assets = assets.sort_by do |asset| + order_column.order_by_value(asset, self) + end + descending? ? assets.reverse : assets + else + if descending? + if order_column.order_desc + apply_asc_order(assets, order_column.order_desc) else - apply_asc_order(assets, order_column.order) + apply_desc_order(assets, order_column.order) end + else + apply_asc_order(assets, order_column.order) end end + end - def check_order_valid! - return unless order - column = column_by_name(order) - unless column - self.class.order_unsupported(order, "no column #{order} in #{self.class}") - end - unless column.supports_order? - self.class.order_unsupported(column.name, "column don't support order" ) - end + def check_order_valid! + return unless order + column = column_by_name(order) + unless column + self.class.order_unsupported(order, "no column #{order} in #{self.class}") end - - def apply_asc_order(assets, order) - if order.respond_to?(:call) - apply_block_order(assets, order) - else - driver.asc(assets, order) - end + unless column.supports_order? + self.class.order_unsupported(column.name, "column don't support order" ) end + end - def apply_desc_order(assets, order) - if order.respond_to?(:call) - reverse_order(apply_asc_order(assets, order)) - else - driver.desc(assets, order) - end + def apply_asc_order(assets, order) + if order.respond_to?(:call) + apply_block_order(assets, order) + else + driver.asc(assets, order) end + end - def reverse_order(assets) - driver.reverse_order(assets) - rescue NotImplementedError - self.class.order_unsupported(order_column.name, "Your ORM do not support reverse order: please specify :order_desc option manually") + def apply_desc_order(assets, order) + if order.respond_to?(:call) + reverse_order(apply_asc_order(assets, order)) + else + driver.desc(assets, order) end + end - def apply_block_order(assets, order) - case order.arity - when -1, 0 - assets.instance_eval(&order) - when 1 - order.call(assets) - else - self.class.order_unsupported(order_column.name, "Order option proc can not handle more than one argument") - end + def reverse_order(assets) + driver.reverse_order(assets) + rescue NotImplementedError + self.class.order_unsupported(order_column.name, "Your ORM do not support reverse order: please specify :order_desc option manually") + end + + def apply_block_order(assets, order) + case order.arity + when -1, 0 + assets.instance_eval(&order) + when 1 + order.call(assets) + else + self.class.order_unsupported(order_column.name, "Order option proc can not handle more than one argument") end end end