From b93f016bfca9a77b1b658bb56d37f54db417d0f6 Mon Sep 17 00:00:00 2001 From: Eduardo Cifuentes Date: Fri, 28 Apr 2023 16:25:31 -0400 Subject: [PATCH 1/7] feat: use monkey patch strategy to handle active resource --- .DS_Store | Bin 0 -> 6148 bytes lib/.DS_Store | Bin 0 -> 6148 bytes lib/active_admin_resource.rb | 9 +-- lib/active_admin_resource/.DS_Store | Bin 0 -> 6148 bytes .../active_admin/namespace_patch.rb | 13 ++++ lib/active_admin_resource/associations.rb | 51 ------------- lib/active_admin_resource/collection_patch.rb | 10 --- lib/active_admin_resource/column_patch.rb | 12 ---- .../formtastic_addons_patch.rb | 18 ----- lib/active_admin_resource/gem_adaptors.rb | 56 --------------- lib/active_admin_resource/railtie.rb | 12 +--- .../railties/resource_api_mock/mock_store.rb | 56 --------------- .../resource_api_mock/resource_api_mock.rb | 42 ----------- lib/active_admin_resource/railties/rspec.rb | 11 --- lib/active_admin_resource/resource.rb | 11 +++ .../resource_chain_adapter.rb | 67 ++++++++++++++++++ .../resource_class_adapter.rb | 64 +++++++++++++++++ lib/active_admin_resource/resource_column.rb | 13 ++++ 18 files changed, 175 insertions(+), 270 deletions(-) create mode 100644 .DS_Store create mode 100644 lib/.DS_Store create mode 100644 lib/active_admin_resource/.DS_Store create mode 100644 lib/active_admin_resource/active_admin/namespace_patch.rb delete mode 100644 lib/active_admin_resource/associations.rb delete mode 100644 lib/active_admin_resource/collection_patch.rb delete mode 100644 lib/active_admin_resource/column_patch.rb delete mode 100644 lib/active_admin_resource/formtastic_addons_patch.rb delete mode 100644 lib/active_admin_resource/gem_adaptors.rb delete mode 100644 lib/active_admin_resource/railties/resource_api_mock/mock_store.rb delete mode 100644 lib/active_admin_resource/railties/resource_api_mock/resource_api_mock.rb delete mode 100644 lib/active_admin_resource/railties/rspec.rb create mode 100644 lib/active_admin_resource/resource.rb create mode 100644 lib/active_admin_resource/resource_chain_adapter.rb create mode 100644 lib/active_admin_resource/resource_class_adapter.rb create mode 100644 lib/active_admin_resource/resource_column.rb diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..bfd9345d3e1678bcd5b43bd6fa00cb72b4c3f194 GIT binary patch literal 6148 zcmeHK-AcnS6i&A3I)=~-1@S82?Zj?QWq4ESd;u$Zp)y-Kv{;+5ZuVjf`T+VuK8Vlb zIY|l*doAM5f#f^CN%KMT!x-bww>0X9eF3cN3?P%sn|zqfFKMI$*V|*2vylEDn07gO2F-o#SOkECxNN zBl<@t%Vo>j**`qH96cv5seIFfa^PFZmcbI*<}_XF+dCu1H`~O zGhj{ytG&)7(AtRsVxW=%+#duqMAu-UQEeU2;q@8g4MY^s@hyQU47vsjjSvChx)e~C za`VLCx*Yt%S5T0$TrWBzE1@W}twPNcp;w9Aj0!H+pQWFz27_-u(_D~9W0DU1J#OHBl zcPp0a!GmID24=t6*_qw!w_$$(K(zZ|3!n}F4k}@(ip3{F?W9vuvK~UA`*?&1y5K_y z_fygA_=^nC+AZUXT|*8rd|ba6Qe4X(u4o1d`eU!Yy<0W#azF29ZNGO;wG*Y1U~K!rX&eo@^`%3V=6;+;J)IDT5r$lx#c8Mp zZPic1Oy_#0!KpgcZhdt)T;JT=Z^+hmb7$0$!~M->LvC;Fjz(2yd2Qq1xOqnZJuxx^%m6bmn+(|9%&E=h=kr%H1I)k=GC=!- zL?v`B76$d!fdyS3sl7r-f;PP+2&F~WVqp+lP=rZEG^xTqF@#A+zqE0##loOT2cbvC ze(cD?zEFf79sN>=gK!OU$qX<9lMIwiwNB^%(bw<)$t12Z1I)m`Vn9?nUZ;&yvS;hm xks)QevdP| zi_suQjhLBa_ARq-y4| z&9OP~7Zsqr+rS;WhYTY4ynhiS!8q*)u?mp$8ua5R&Fb|xQ7D#HR@a<0XWhB=9@W^( z{A`%E{lPW;x=<<(W_>@njJo4aW$Q#GnI9$Hfli3RE>do;q9jz~wi+g3s&hRv;FO(m zr&66vnyp4nHVzM_H90xjtJmayvo)QTo$a07$So_t3Vc<7YCp6J-T!Bc>wit+h8181{-*+>bn2b9ac%Z)om(Z{wG#af qos4+7!H*Q|=&KmHbQM?8wP9RR2hp{d8^j12{}IqKaKj4xssit8Tvv7g literal 0 HcmV?d00001 diff --git a/lib/active_admin_resource/active_admin/namespace_patch.rb b/lib/active_admin_resource/active_admin/namespace_patch.rb new file mode 100644 index 0000000..d9ea875 --- /dev/null +++ b/lib/active_admin_resource/active_admin/namespace_patch.rb @@ -0,0 +1,13 @@ +module ActiveAdminResource + module ActiveAdmin + module NamespacePatch + def find_or_build_resource(resource_class, options) + if resource_class < ActiveResource::Base + resources.add ActiveAdminResource::Resource.new(self, resource_class, options) + else + resources.add ::ActiveAdmin::Resource.new(self, resource_class, options) + end + end + end + end +end \ No newline at end of file diff --git a/lib/active_admin_resource/associations.rb b/lib/active_admin_resource/associations.rb deleted file mode 100644 index 13708a4..0000000 --- a/lib/active_admin_resource/associations.rb +++ /dev/null @@ -1,51 +0,0 @@ -require 'active_resource' - -module ActiveAdminResource - module Associations - def has_many(plural_model_name, options = {}) - klass = Object.const_get plural_model_name.to_s.singularize.classify - # Getter - define_method plural_model_name do - var = instance_variable_get("@#{plural_model_name}") - if !var.nil? - var - else - collection = if options[:as] # polymorphic - foreign_key = "#{options[:as]}_id" - foreign_type = "#{options[:as]}_type" - klass.where(foreign_type => model_name.name, foreign_key => id) - else - foreign_key = "#{model_name.name.downcase}_id" - klass.where(foreign_key => id) - end - instance_variable_set("@#{plural_model_name}", collection) - end - end - # Setter - define_method "#{plural_model_name}=" do |value| - instance_variable_set("@#{plural_model_name}", value) - end - end - - Enumerable.send(:define_method, 'and_preload') do |model_to_load| - model_to_load = model_to_load.to_s.singularize - klass_to_load = Object.const_get model_to_load.classify - foreign_ids = map { |collection_item| collection_item.send("#{model_to_load}_id") }.uniq - preloaded_items = if klass_to_load < ApplicationResource - # Class to load must support where(id: []) - klass_to_load.where(id: foreign_ids, per: foreign_ids.count) - elsif klass_to_load < ApplicationRecord - klass_to_load.where(id: foreign_ids) - else - raise "#{klass_to_load} is not from a supported preload type" - end - each do |collection_item| - corresponding_preloaded = preloaded_items.find do |pit| - pit.id == collection_item.send("#{model_to_load}_id") - end - collection_item.send("#{model_to_load}=", corresponding_preloaded) - end - self - end - end -end diff --git a/lib/active_admin_resource/collection_patch.rb b/lib/active_admin_resource/collection_patch.rb deleted file mode 100644 index 4528153..0000000 --- a/lib/active_admin_resource/collection_patch.rb +++ /dev/null @@ -1,10 +0,0 @@ -module CollectionExtensions - def collection_size(c = collection) - if c.is_a? ActiveRecord::Relation - c = c.except :select, :order - c.group_values.present? ? c.count.count : c.count - else - c.respond_to?(:count) ? c.count : 0 - end - end -end diff --git a/lib/active_admin_resource/column_patch.rb b/lib/active_admin_resource/column_patch.rb deleted file mode 100644 index ee4305a..0000000 --- a/lib/active_admin_resource/column_patch.rb +++ /dev/null @@ -1,12 +0,0 @@ -module ColumnExtensions - def sortable? - if @options.has_key?(:sortable) - !!@options[:sortable] - elsif @resource_class - @resource_class.column_names.map { |c| c.is_a?(String) ? c : c.name } - .include?(sort_column_name) - else - @title.present? - end - end -end diff --git a/lib/active_admin_resource/formtastic_addons_patch.rb b/lib/active_admin_resource/formtastic_addons_patch.rb deleted file mode 100644 index ab36e43..0000000 --- a/lib/active_admin_resource/formtastic_addons_patch.rb +++ /dev/null @@ -1,18 +0,0 @@ -module FormtasticAddonsExtensions - def seems_searchable? - false - end - - def klass - @object.try(:object).try(:klass) - end - - def ransacker? - klass.try(:_ransackers).try(:key?, method.to_s) - end - - def scope? - context = Ransack::Context.for klass rescue nil - context.respond_to?(:ransackable_scope?) && context.ransackable_scope?(method.to_s, klass) - end -end diff --git a/lib/active_admin_resource/gem_adaptors.rb b/lib/active_admin_resource/gem_adaptors.rb deleted file mode 100644 index 91b7595..0000000 --- a/lib/active_admin_resource/gem_adaptors.rb +++ /dev/null @@ -1,56 +0,0 @@ -require 'active_resource' -require 'formtastic' -require 'action_view' -require 'enumerize' - -module ActiveAdminResource - module GemAdaptors - module EnumerizeAdaptor - # Enumerize support - def enumerize(name, options = {}) - # Getter - define_method name do - enumerize_attr = self.class.send(name) - name ||= options[:default] - Enumerize::Value.new(enumerize_attr, attributes[name.to_sym]) - end - # Setter - define_method "#{name}=" do |value| - enumerize_attr = self.class.send(name) - unless value.to_s.in? enumerize_attr.values - raise ArgumentError.new "Invalid value '#{value}' for #{name} enumerized attribute" - end - attributes[name.to_sym] = value - end - super - end - end - - module MoneyAdaptor - def monetize(*fields) - options = fields.extract_options! - fields.each { |field| monetize_field(field, options) } - end - - def monetize_field(field, _options = {}) - # Getter - define_method field do - amount, currency = attributes[field.to_sym] - Money.from_amount(amount.to_f, currency) if amount - end - # Setter - define_method "#{field}=" do |new_amount| - field = field.to_sym - if new_amount.is_a?(Money) - amount = new_amount.amount - currency = new_amount.currency - elsif new_amount.is_a?(Numeric) - amount = new_amount - currency = attributes[field].try(:last) || MoneyRails.default_currency.try(:iso_code) || 'USD' - end - attributes[field] = [amount, currency].map(&:to_s) - end - end - end - end -end diff --git a/lib/active_admin_resource/railtie.rb b/lib/active_admin_resource/railtie.rb index 9f465e1..f8bac74 100644 --- a/lib/active_admin_resource/railtie.rb +++ b/lib/active_admin_resource/railtie.rb @@ -1,15 +1,7 @@ -require 'active_admin_resource/formtastic_addons_patch' -require 'active_admin_resource/collection_patch' -require 'active_admin_resource/connection_patch' -require 'active_admin_resource/column_patch' - module ActiveAdminResource class Railtie < Rails::Railtie - initializer "railtie.configure_rails_initialization" do - ActiveResource::Connection.prepend(ConnectionExtensions) - ActiveAdmin::Helpers::Collection.prepend(CollectionExtensions) - ActiveAdmin::Filters::FormtasticAddons.prepend(FormtasticAddonsExtensions) - ActiveAdmin::Views::TableFor::Column.prepend(ColumnExtensions) + initializer "active_admin_resource.configure_admin_namespace" do + ::ActiveAdmin::Namespace.prepend(ActiveAdmin::NamespacePatch) end end end diff --git a/lib/active_admin_resource/railties/resource_api_mock/mock_store.rb b/lib/active_admin_resource/railties/resource_api_mock/mock_store.rb deleted file mode 100644 index dbfd99f..0000000 --- a/lib/active_admin_resource/railties/resource_api_mock/mock_store.rb +++ /dev/null @@ -1,56 +0,0 @@ -module ActiveAdminResource - class MockStore - @@store_hash = Hash.new([]) - - def self.select(model, query_params) - query_params.stringify_keys! - query_params.dup.each do |k, v| - if query_params["#{k}_id"].nil? && v.respond_to?(:id) - query_params["#{k}_id"] = v.id - query_params.delete(k) - end - end - store_for_model(model).select do |obj| - obj.attributes.slice(*query_params.keys) == query_params - end - end - - def self.insert(object) - model = object_to_model(object) - object.id ||= next_id(model) - @@store_hash[model] += [object] - end - - def self.update(object) - stored_object = store_for_object(object).find { |i| i.id == object.id } - raise "Object Not Found: #{object.class} ##{object.id}" unless stored_object - store_for_object(object).delete(stored_object) - insert(object) - end - - def self.delete(object) - store_for_object(object).delete(object) || - raise("Object Not Found: #{object.class} ##{object.id}") - end - - def self.drop - @@store_hash = Hash.new([]) - end - - def self.object_to_model(object) - object.class.name.downcase.to_sym - end - - def self.store_for_object(object) - store_for_model object_to_model(object) - end - - def self.store_for_model(model) - @@store_hash[model] - end - - def self.next_id(model) - (store_for_model(model).map(&:id).max || 0) + 1 - end - end -end diff --git a/lib/active_admin_resource/railties/resource_api_mock/resource_api_mock.rb b/lib/active_admin_resource/railties/resource_api_mock/resource_api_mock.rb deleted file mode 100644 index 5e918e8..0000000 --- a/lib/active_admin_resource/railties/resource_api_mock/resource_api_mock.rb +++ /dev/null @@ -1,42 +0,0 @@ -require 'active_resource' -require 'uri' - -module ActiveAdminResource - module ResourceApiMock - def self.included(klass) - klass.extend ClassMethods - klass.site = URI.parse('resource.api.mocked') - end - - def site - URI.parse('resource.api.mocked') - end - - def create - @persisted = true - MockStore.insert(self) - end - - def update - MockStore.update(self) - end - - def destroy - MockStore.delete(self) - end - - module ClassMethods - def find_every(options) - options = options[:params] if options.has_key? :params - model = model_name.singular.to_sym - MockStore.select(model, options) - end - - def find_single(id, options) - options = options[:params] if options.has_key? :params - model = model_name.singular.to_sym - MockStore.select(model, options.merge(id: id.to_i)).first - end - end - end -end diff --git a/lib/active_admin_resource/railties/rspec.rb b/lib/active_admin_resource/railties/rspec.rb deleted file mode 100644 index fe15121..0000000 --- a/lib/active_admin_resource/railties/rspec.rb +++ /dev/null @@ -1,11 +0,0 @@ -require 'active_admin_resource/railties/resource_api_mock/resource_api_mock' -require 'active_admin_resource/railties/resource_api_mock/mock_store' -class ActiveAdminResource::Base - include ActiveAdminResource::ResourceApiMock -end - -RSpec.configure do |config| - config.before(:example) do |_example| - ActiveAdminResource::MockStore.drop - end -end diff --git a/lib/active_admin_resource/resource.rb b/lib/active_admin_resource/resource.rb new file mode 100644 index 0000000..c4bef93 --- /dev/null +++ b/lib/active_admin_resource/resource.rb @@ -0,0 +1,11 @@ +module ActiveAdminResource + class Resource < ActiveAdmin::Resource + def resource_class + @resource_class ||= ResourceClassAdapter.new(resource_class_name.constantize) + end + + def resource_quoted_column_name(_column) + _column + end + end +end \ No newline at end of file diff --git a/lib/active_admin_resource/resource_chain_adapter.rb b/lib/active_admin_resource/resource_chain_adapter.rb new file mode 100644 index 0000000..b45fefa --- /dev/null +++ b/lib/active_admin_resource/resource_chain_adapter.rb @@ -0,0 +1,67 @@ +module ActiveAdminResource + class ResourceChainAdapter + def initialize(_resource_class) + @resource_class = _resource_class + end + + def object + ResourceClassAdapter.new(@resource_class) + end + + def reorder(_arel) + self + end + + def page(_page) + @page = _page + self + end + + def ransack(_q) + @q = _q + self + end + + def result + self + end + + def conditions + self + end + + def per(_limit_value) + @limit_value = _limit_value + + resources = @resource_class.find(:all, params: { search: @q.try(:permit!).to_h }) + + pagination_info = @resource_class.format.pagination_info + offset = (pagination_info['current_page'] - 1) * _limit_value + + @ary = Kaminari.paginate_array( + resources, + limit: _limit_value, + offset: offset, + total_count: pagination_info['total_count'] + ) + end + + def map(*_args) + @ary = @ary.map *_args + self + end + + def each(*_args) + @ary.each *_args + self + end + + def to_ary + @ary + end + + def method_missing(method_name, *args, &block) + @q[method_name] + end + end +end \ No newline at end of file diff --git a/lib/active_admin_resource/resource_class_adapter.rb b/lib/active_admin_resource/resource_class_adapter.rb new file mode 100644 index 0000000..8242efc --- /dev/null +++ b/lib/active_admin_resource/resource_class_adapter.rb @@ -0,0 +1,64 @@ +module ActiveAdminResource + class ResourceClassAdapter + extend Forwardable + + def_delegators :@resource_class, :name, :find, :format, :primary_key + def_delegators :chain_adaptor, :reorder, :page, :ransack, :result, :per + + def initialize(_resource_class) + @resource_class = _resource_class + end + + def klass + self + end + + def _ransackers + {} + end + + def display_name + human_model = I18n.t("activerecord.models.#{model_name.i18n_key}.one", default: "") + return "#{human_model} ##{id}" if human_model.present? + + "#{model_name.name} ##{id}" + end + + def column_names + content_columns + end + + def content_columns + if @content_columns.nil? + @content_columns = Array.new + @resource_class.known_attributes.each do |name| + @content_columns << ResourceColumn.new(name) + end + end + + @content_columns + end + + def columns + content_columns + end + + def columns_hash + @columns_hash ||= begin + test = Hash.new { |h,k| puts "O!! #{k}"; nil } + content_columns.each { |c| test[c.name] = c } + test + end + end + + def human_attribute_name(attr, _options = {}) + I18n.t("activerecord.attributes.#{name.downcase}.#{attr}", default: attr.to_s.titleize) + end + + private + + def chain_adaptor + @chain_adaptor ||= ResourceChainAdapter.new(@resource_class) + end + end +end \ No newline at end of file diff --git a/lib/active_admin_resource/resource_column.rb b/lib/active_admin_resource/resource_column.rb new file mode 100644 index 0000000..71c4609 --- /dev/null +++ b/lib/active_admin_resource/resource_column.rb @@ -0,0 +1,13 @@ +module ActiveAdminResource + class ResourceColumn + attr_reader :name + + def initialize(name) + @name = name + end + + def type + :string + end + end +end \ No newline at end of file From 94759b593fa511a25f1a8e0024cd987747e04ed4 Mon Sep 17 00:00:00 2001 From: Ignacio Baixas Date: Fri, 12 May 2023 16:15:44 -0400 Subject: [PATCH 2/7] fix: prevent ResourceChainAdaptor from sending empty query string --- lib/active_admin_resource/resource_chain_adapter.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/active_admin_resource/resource_chain_adapter.rb b/lib/active_admin_resource/resource_chain_adapter.rb index b45fefa..947d445 100644 --- a/lib/active_admin_resource/resource_chain_adapter.rb +++ b/lib/active_admin_resource/resource_chain_adapter.rb @@ -33,7 +33,8 @@ def conditions def per(_limit_value) @limit_value = _limit_value - resources = @resource_class.find(:all, params: { search: @q.try(:permit!).to_h }) + options = @q.present? ? { params: { search: @q.try(:permit!).to_h } } : {} + resources = @resource_class.find(:all, options) pagination_info = @resource_class.format.pagination_info offset = (pagination_info['current_page'] - 1) * _limit_value From 6b5589b38abf38cd76dea7f439d4d16b8fe783f0 Mon Sep 17 00:00:00 2001 From: Ignacio Baixas Date: Fri, 12 May 2023 16:26:42 -0400 Subject: [PATCH 3/7] refactor: removes unused buda specific classes --- lib/active_admin_resource/agent_data_base.rb | 20 --- lib/active_admin_resource/base.rb | 130 ------------------ lib/active_admin_resource/connection_patch.rb | 30 ---- 3 files changed, 180 deletions(-) delete mode 100644 lib/active_admin_resource/agent_data_base.rb delete mode 100644 lib/active_admin_resource/base.rb delete mode 100644 lib/active_admin_resource/connection_patch.rb diff --git a/lib/active_admin_resource/agent_data_base.rb b/lib/active_admin_resource/agent_data_base.rb deleted file mode 100644 index 0f4d388..0000000 --- a/lib/active_admin_resource/agent_data_base.rb +++ /dev/null @@ -1,20 +0,0 @@ -module ActiveAdminResource - class AgentDataBase < Base - self.include_root_in_json = true - - def self.element_path(_id, prefix_options = {}, query_options = nil) - check_prefix_options(prefix_options) - - prefix_options, query_options = split_options(prefix_options) if query_options.nil? - "#{prefix(prefix_options)}#{collection_name}#{format_extension}#{query_string(query_options)}" - end - - def to_json(options = {}) - permitted_attributes = attributes.slice(*schema.keys) - rooted_attributes = {} - rooted_attributes[self.class.element_name] = permitted_attributes - encoded_attributes = include_root_in_json ? rooted_attributes : permitted_attributes - ActiveSupport::JSON.encode(encoded_attributes, options) - end - end -end diff --git a/lib/active_admin_resource/base.rb b/lib/active_admin_resource/base.rb deleted file mode 100644 index ec4efab..0000000 --- a/lib/active_admin_resource/base.rb +++ /dev/null @@ -1,130 +0,0 @@ -require 'active_resource' -require 'active_admin_resource/associations' -require 'active_admin_resource/gem_adaptors' - -module ActiveAdminResource - class Base < ActiveResource::Base - extend ActiveAdminResource::Associations - extend Enumerize if defined? Enumerize - extend ActiveAdminResource::GemAdaptors::EnumerizeAdaptor - extend ActiveAdminResource::GemAdaptors::MoneyAdaptor - - class JsonFormatter - include ActiveResource::Formats::JsonFormat - - attr_reader :collection_name - attr_reader :pagination_info - - def initialize(collection_name) - @collection_name = collection_name.to_s - end - - def decode(json) - pre_process(ActiveSupport::JSON.decode(json)) - end - - private - - def pre_process(data) - @pagination_info = data['meta'] - data.delete('meta') - if data.is_a?(Hash) && data.keys.size == 1 && data.values.first.is_a?(Enumerable) - data.values.first - elsif data.is_a?(Array) && data.size == 1 - data.first - else - data - end - end - end - - self.format = JsonFormatter.new(collection_name) - - cattr_accessor :static_headers - self.static_headers = headers - - def self.inherited(model) - model.site = ENV['RESOURCES_API_URL'] if ENV.has_key?('RESOURCES_API_URL') - super - end - - def self.agent_id - ENV['RESOURCES_API_AGENT_ID'] - end - - def self.secret - ENV['RESOURCES_API_AGENT_SECRET'] - end - - def self.scope(name, body) - singleton_class.send(:define_method, name, &body) - # TODO: fix that a 2nd scope defined with same name in another model will override the 1st one, - # as all model's collections inherit from ActiveResource::Collection - ActiveResource::Collection.send(:define_method, name, &body) - end - - def self.human_attribute_name(attr, _options = {}) - I18n.t("activeresource.attributes.#{name.downcase}.#{attr}", - default: I18n.t("activerecord.attributes.#{name.downcase}.#{attr}", - default: attr.to_s.titleize)) - end - - def self.headers - new_headers = static_headers.clone - new_headers["Content-Type"] = "application/json" - new_headers["Accept"] = "application/json" - new_headers["X-Agent-Id"] = agent_id if !agent_id.nil? - new_headers - end - - def self.column_names - content_columns - end - - def self.content_columns - if @content_columns.nil? - @content_columns = Array.new - known_attributes.each do |name| - @content_columns << ResourceColumn.new(name) - end - end - @content_columns - end - - def self.columns - content_columns - end - - class ResourceColumn - attr_reader :name - - def initialize(name) - @name = name - end - - def type - :string - end - end - - def self.inheritance_column - '' - end - - def self.base_class - self - end - - def self.find_by(arg, *_args) - find(arg[primary_key]) - end - - class << self - def connection(refresh = false) - connection = super(refresh) - _connection.set_secret(secret) if !secret.nil? - connection - end - end - end -end diff --git a/lib/active_admin_resource/connection_patch.rb b/lib/active_admin_resource/connection_patch.rb deleted file mode 100644 index 377e23d..0000000 --- a/lib/active_admin_resource/connection_patch.rb +++ /dev/null @@ -1,30 +0,0 @@ -require 'authograph' - -module ConnectionExtensions - def set_secret(secret) - @secret = secret - end - - def request(method, path, *arguments) - result = ActiveSupport::Notifications.instrument("request.active_resource") do |payload| - payload[:method] = method - payload[:request_uri] = "#{site.scheme}://#{site.host}:#{site.port}#{path}" - Net::HTTP.start(site.host, site.port, use_ssl: defined? @ssl_options) do |http| - configure_http(http) - request = Net::HTTP::const_get(method.capitalize).new path - headers = arguments.last - headers.each do |key, value| - request[key] = value - end - request.body = arguments.first if arguments.length > 1 - Authograph.signer.sign(request, @secret) if !@secret.nil? - payload[:result] = http.request(request) - end - end - handle_response(result) - rescue Timeout::Error => e - raise TimeoutError.new(e.message) - rescue OpenSSL::SSL::SSLError => e - raise SSLError.new(e.message) - end -end From 34130b503891ae3781d84a5908cc02bd17a736a8 Mon Sep 17 00:00:00 2001 From: Ignacio Baixas Date: Fri, 2 Jun 2023 18:01:32 -0400 Subject: [PATCH 4/7] feat: make ResourceClassAdapter require resource to implement specific methods for resource and collection queries Also fix pagination and ordering --- lib/active_admin_resource.rb | 3 +- lib/active_admin_resource/query_result.rb | 11 +++ .../resource_chain_adapter.rb | 68 ------------------ .../resource_chain_helper.rb | 72 +++++++++++++++++++ .../resource_class_adapter.rb | 43 +++++++---- 5 files changed, 115 insertions(+), 82 deletions(-) create mode 100644 lib/active_admin_resource/query_result.rb delete mode 100644 lib/active_admin_resource/resource_chain_adapter.rb create mode 100644 lib/active_admin_resource/resource_chain_helper.rb diff --git a/lib/active_admin_resource.rb b/lib/active_admin_resource.rb index 2ff93e9..a2e4040 100644 --- a/lib/active_admin_resource.rb +++ b/lib/active_admin_resource.rb @@ -1,6 +1,7 @@ +require 'active_admin_resource/query_result' require 'active_admin_resource/resource' require 'active_admin_resource/resource_column' require 'active_admin_resource/resource_class_adapter' -require 'active_admin_resource/resource_chain_adapter' +require 'active_admin_resource/resource_chain_helper' require 'active_admin_resource/active_admin/namespace_patch' require 'active_admin_resource/railtie' diff --git a/lib/active_admin_resource/query_result.rb b/lib/active_admin_resource/query_result.rb new file mode 100644 index 0000000..1febab0 --- /dev/null +++ b/lib/active_admin_resource/query_result.rb @@ -0,0 +1,11 @@ +module ActiveAdminResource + class QueryResult + attr_reader :collection, :total_count, :page + + def initialize(_collection, _total_count, _page) + @collection = _collection + @total_count = _total_count + @page = _page + end + end +end \ No newline at end of file diff --git a/lib/active_admin_resource/resource_chain_adapter.rb b/lib/active_admin_resource/resource_chain_adapter.rb deleted file mode 100644 index 947d445..0000000 --- a/lib/active_admin_resource/resource_chain_adapter.rb +++ /dev/null @@ -1,68 +0,0 @@ -module ActiveAdminResource - class ResourceChainAdapter - def initialize(_resource_class) - @resource_class = _resource_class - end - - def object - ResourceClassAdapter.new(@resource_class) - end - - def reorder(_arel) - self - end - - def page(_page) - @page = _page - self - end - - def ransack(_q) - @q = _q - self - end - - def result - self - end - - def conditions - self - end - - def per(_limit_value) - @limit_value = _limit_value - - options = @q.present? ? { params: { search: @q.try(:permit!).to_h } } : {} - resources = @resource_class.find(:all, options) - - pagination_info = @resource_class.format.pagination_info - offset = (pagination_info['current_page'] - 1) * _limit_value - - @ary = Kaminari.paginate_array( - resources, - limit: _limit_value, - offset: offset, - total_count: pagination_info['total_count'] - ) - end - - def map(*_args) - @ary = @ary.map *_args - self - end - - def each(*_args) - @ary.each *_args - self - end - - def to_ary - @ary - end - - def method_missing(method_name, *args, &block) - @q[method_name] - end - end -end \ No newline at end of file diff --git a/lib/active_admin_resource/resource_chain_helper.rb b/lib/active_admin_resource/resource_chain_helper.rb new file mode 100644 index 0000000..fb55b51 --- /dev/null +++ b/lib/active_admin_resource/resource_chain_helper.rb @@ -0,0 +1,72 @@ +module ActiveAdminResource + class ResourceChainHelper + def initialize(_resource_class_adapter) + @resource_class_adapter = _resource_class_adapter + end + + def object + @resource_class_adapter + end + + def reorder(_query) + @reorder = _query.parameterize(separator: '_') + self + end + + def page(_page) + @page = _page&.to_i + self + end + + def ransack(_ransack) + @ransack = _ransack + self + end + + def result + self + end + + def conditions + self + end + + def per(_limit_value) + actual_page = @page || 1 + + query_result = @resource_class_adapter.query_collection( + ransack: @ransack.try(:permit!)&.to_h, + reorder: @reorder, + page: actual_page, + per: _limit_value + ) + + actual_page = query_result.page if query_result.page.present? + + @ary = Kaminari.paginate_array( + query_result.collection, + limit: _limit_value, + offset: (actual_page - 1) * _limit_value, + total_count: query_result.total_count + ) + end + + def map(*_args) + @ary = @ary.map *_args + self + end + + def each(*_args) + @ary.each *_args + self + end + + def to_ary + @ary + end + + def method_missing(method_name, *args, &block) + @ransack[method_name] + end + end +end \ No newline at end of file diff --git a/lib/active_admin_resource/resource_class_adapter.rb b/lib/active_admin_resource/resource_class_adapter.rb index 8242efc..2ba7c67 100644 --- a/lib/active_admin_resource/resource_class_adapter.rb +++ b/lib/active_admin_resource/resource_class_adapter.rb @@ -2,8 +2,8 @@ module ActiveAdminResource class ResourceClassAdapter extend Forwardable - def_delegators :@resource_class, :name, :find, :format, :primary_key - def_delegators :chain_adaptor, :reorder, :page, :ransack, :result, :per + def_delegators :@resource_class, :name, :format, :primary_key + def_delegators :chain_helper, :reorder, :page, :ransack, :result, :per def initialize(_resource_class) @resource_class = _resource_class @@ -13,6 +13,10 @@ def klass self end + def inheritance_column + nil + end + def _ransackers {} end @@ -24,19 +28,16 @@ def display_name "#{model_name.name} ##{id}" end + def quoted_table_name + `resource_not_table` + end + def column_names - content_columns + content_columns.map &:name end def content_columns - if @content_columns.nil? - @content_columns = Array.new - @resource_class.known_attributes.each do |name| - @content_columns << ResourceColumn.new(name) - end - end - - @content_columns + @content_columns ||= @resource_class.known_attributes.map { |n| ResourceColumn.new(n) } end def columns @@ -51,14 +52,30 @@ def columns_hash end end + def find(_id) + if @resource_class.respond_to? :process_active_admin_resource_query + return @resource_class.process_active_admin_resource_query(_id) + end + + @resource_class.find(_id) + end + + def query_collection(*_params) + unless @resource_class.respond_to? :process_active_admin_collection_query + return ActiveAdminResource::QueryResult.new([], 0) + end + + @resource_class.process_active_admin_collection_query(*_params) + end + def human_attribute_name(attr, _options = {}) I18n.t("activerecord.attributes.#{name.downcase}.#{attr}", default: attr.to_s.titleize) end private - def chain_adaptor - @chain_adaptor ||= ResourceChainAdapter.new(@resource_class) + def chain_helper + @chain_helper ||= ResourceChainHelper.new(self) end end end \ No newline at end of file From a6c0981b1fe587a1c639b5acd7d18bf958d469d6 Mon Sep 17 00:00:00 2001 From: Ignacio Baixas Date: Fri, 2 Jun 2023 18:20:33 -0400 Subject: [PATCH 5/7] chore: updates README --- README.md | 179 ++++++------------------------------------------------ 1 file changed, 17 insertions(+), 162 deletions(-) diff --git a/README.md b/README.md index 3fe9276..933fd31 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,6 @@ Extension of [ActiveResource](https://github.com/rails/activeresource) to allow integration with [ActiveAdmin](https://github.com/activeadmin/activeadmin). -## Disclaimer - -This gem is work in progress. There is only partial support of ActiveAdmin for now, and the are some specific limitation and requirements that need to be considered when using it. - -Pull Requests with improvements are welcomed :) - ## Installation Add this line to your Rails application's Gemfile: @@ -21,167 +15,28 @@ And then execute: $ bundle -## Usage - -This gem provides a base class that extends from ActiveResource and patches some missing functions in both ActiveAdmin and ActiveResource. The patches are executed when the gem is loaded. - -### ActiveAdminResource::Base - -This is the base class that you need to use to create ActiveAdmin enabled ActiveResource models. You need to extend your model with this base class, and define the minimum fields required by ActiveResource as in the following example: - -```ruby -class Market < ActiveAdminResource::Base - self.site = "http://localhost/api/" - - schema do - attribute 'id', :string - attribute 'name', :string - attribute 'base_currency', :string - attribute 'quote_currency', :string - end -end -``` - -This base class allows also to perform signed requests using the [Authograph gem](https://github.com/budacom/authograph/). To enable this, the class must implement the class method *self.secret* and return the private key in that method. Additionaly, an id of the requester can be provided by implementing the *self.agent_id* method as the following example shows: - -```ruby -class Account < ActiveAdminResource::Base - self.site = "http://localhost/signed_api/" - - def self.agent_id - #Return id - end - - def self.secret - #Return secret - end - - schema do - attribute 'id', :integer - attribute 'name', :string - end -end - -``` - -ActiveResource has partial support for relations between models. You can specify `has_one` and `has_many`relations just like its is done in ActiveRecord: - -```ruby -class Account < ActiveAdminResource::Base - self.site = "http://localhost/signed_api/" - - has_one :agent_data - has_many :withdrawals - - def self.agent_id - #Return id - end - - def self.secret - #Return secret - end - - schema do - attribute 'id', :integer - attribute 'name', :string - end -end - -``` - -The difference in how relations are handled is that for the subelement of a model (in this example `AgentData`) the url must include the parent -url with a symbol indicating the id of the parent record (in this example `:account_id`). - -```ruby -class AgentData < ActiveAdminResource::AgentDataBase - self.site = "http://localhost/signed_api/accounts/:account_id" - - def self.agent_id - #Return id - end - - def self.secret - #Return secret - end - - schema do - attribute 'a', :string - end -end -``` - -When accessing the subelement and trying to perform an action, this extra symbol must be included as a parameter to complete the url: - -```ruby -Account.find(4).agent_data.update_attributes(a: "asdf", account_id: 4) -``` - -### ActiveAdminResource::AgentDataBase +Since API quering changes from one ActiveResource integration to another, for this gem to work the proyect's base resource class must implement the following methods: -An additional base class called `AgentDataBase` is provided to model the special case of an `AgentData` object: +### `process_active_admin_collection_query(ransack:, reorder:, page:, per:)` -```ruby -class AgentData < ActiveAdminResource::AgentDataBase - self.site = "http://localhost/signed_api/" - - def self.agent_id - #Return id - end - - def self.secret - #Return secret - end - - schema do - attribute 'a', :string - end -end -``` - -When registering an ActiveAdminResource model with ActiveAdmin some additional considerations must be taken: - -* Batch actions are not supported, so they should be disabled. -* The *find_collection* method of *controller* must be overriden and return a paginated array as a result. -* Pagination is supported by providing pagination info in the server response. The currently supported pagination format expect a *meta* field in the response with a dictionary that has the following fields describing the pagination: *total_pages*, *total_count* and *current_page*. -When a response is requested, the pagination info is stored in *Model.format.pagination_info* and can be used to paginate the info accordingly. +This method will be called with the following arguments: +- `ransack`: ransack query activeadmin is trying to apply to collection +- `reorder`: ordering query activeadmin is trying to apply to collection +- `page`: page number +- `per`: results per page +The method must respond with an instance of `ActiveAdminResource::QueryResult`, that can be built using `ActiveAdminResource::QueryResult.new(collection, total_count, page)`, where: +- `collection`: an array of resource instances +- `total_count`: the total count of resources after applying filters (`nil` if not available) +- `page`: the current page (`nil` if not available) -The following example shows an ActiveAdminResource model being registered for ActiveAdmin and using the pagination schema explained before. - -```ruby - -ActiveAdmin.register Account do - config.batch_actions = false - - filter :names, as: :string, label: "Names" - filter :surnames, as: :string, label: "Surnames" - - controller do - def find_collection - default_per_page = 20 - per_page = params.fetch(:per_page, default_per_page) - query_params = params.fetch(:q, nil) - search_params = query_params.nil? ? {} : query_params.permit!.to_h - @search = OpenStruct.new(search_params.merge(conditions: [])) - result = Account.find(:all, params: { - order: params.fetch(:order, nil), - page: params.fetch(:page, 1), - per: per_page, - search: search_params - }) - pagination_info = Account.format.pagination_info - offset = (pagination_info["current_page"] - 1) * per_page - Kaminari.paginate_array(result, limit: result.count, offset: offset, total_count: pagination_info["total_count"]) - end - end -end - -``` +### `process_active_admin_resource_query(_id)` (optional) +If this method is defined, it will be called instead if `find` -### Limitations and Poorly tested cases +## Limitations -As explained before, there are several limitations and poorly tested cases in the current version: +**WARNING! There are several limitations and no tests, so use with caution** -* Batch actions are not supported -* POST, PUT and DELETE actions are not tested well and may have issues +* There is no way of telling activeadmin when to allow sorting or no +* There is no support for forms From a9d8b5af849477476df2a0ab031401837eb98ddf Mon Sep 17 00:00:00 2001 From: Ignacio Baixas Date: Fri, 29 Sep 2023 12:03:14 -0300 Subject: [PATCH 6/7] fix: rails 6 support --- active_admin_resource.gemspec | 4 ++++ lib/active_admin_resource/resource_chain_helper.rb | 3 ++- lib/active_admin_resource/resource_class_adapter.rb | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/active_admin_resource.gemspec b/active_admin_resource.gemspec index fd81b25..0df50f9 100644 --- a/active_admin_resource.gemspec +++ b/active_admin_resource.gemspec @@ -19,5 +19,9 @@ Gem::Specification.new do |s| s.add_dependency 'money', '~> 6.6' s.add_dependency 'formtastic' s.add_development_dependency 'pry' + + s.add_development_dependency 'rails', '~> 6.1.7.4' + s.add_development_dependency 'rake' s.add_development_dependency 'rspec' + s.add_development_dependency 'rspec-rails' end diff --git a/lib/active_admin_resource/resource_chain_helper.rb b/lib/active_admin_resource/resource_chain_helper.rb index fb55b51..fdea5dd 100644 --- a/lib/active_admin_resource/resource_chain_helper.rb +++ b/lib/active_admin_resource/resource_chain_helper.rb @@ -2,6 +2,7 @@ module ActiveAdminResource class ResourceChainHelper def initialize(_resource_class_adapter) @resource_class_adapter = _resource_class_adapter + @scope_args = [] # we need this variable, activeadmin accesses it directly :painharold: end def object @@ -9,7 +10,7 @@ def object end def reorder(_query) - @reorder = _query.parameterize(separator: '_') + @reorder = _query.split('.').last.parameterize(separator: '_') self end diff --git a/lib/active_admin_resource/resource_class_adapter.rb b/lib/active_admin_resource/resource_class_adapter.rb index 2ba7c67..0b3d717 100644 --- a/lib/active_admin_resource/resource_class_adapter.rb +++ b/lib/active_admin_resource/resource_class_adapter.rb @@ -29,7 +29,7 @@ def display_name end def quoted_table_name - `resource_not_table` + 'resource_not_table' end def column_names From 0d8d4c9375f121e7b6b8af1c85806bda29bad4fe Mon Sep 17 00:00:00 2001 From: Ignacio Baixas Date: Fri, 29 Sep 2023 12:11:31 -0300 Subject: [PATCH 7/7] style: adds missing line breaks --- lib/active_admin_resource/active_admin/namespace_patch.rb | 2 +- lib/active_admin_resource/query_result.rb | 2 +- lib/active_admin_resource/resource.rb | 2 +- lib/active_admin_resource/resource_chain_helper.rb | 2 +- lib/active_admin_resource/resource_class_adapter.rb | 2 +- lib/active_admin_resource/resource_column.rb | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/active_admin_resource/active_admin/namespace_patch.rb b/lib/active_admin_resource/active_admin/namespace_patch.rb index d9ea875..1780a73 100644 --- a/lib/active_admin_resource/active_admin/namespace_patch.rb +++ b/lib/active_admin_resource/active_admin/namespace_patch.rb @@ -10,4 +10,4 @@ def find_or_build_resource(resource_class, options) end end end -end \ No newline at end of file +end diff --git a/lib/active_admin_resource/query_result.rb b/lib/active_admin_resource/query_result.rb index 1febab0..8b1dccb 100644 --- a/lib/active_admin_resource/query_result.rb +++ b/lib/active_admin_resource/query_result.rb @@ -8,4 +8,4 @@ def initialize(_collection, _total_count, _page) @page = _page end end -end \ No newline at end of file +end diff --git a/lib/active_admin_resource/resource.rb b/lib/active_admin_resource/resource.rb index c4bef93..7125233 100644 --- a/lib/active_admin_resource/resource.rb +++ b/lib/active_admin_resource/resource.rb @@ -8,4 +8,4 @@ def resource_quoted_column_name(_column) _column end end -end \ No newline at end of file +end diff --git a/lib/active_admin_resource/resource_chain_helper.rb b/lib/active_admin_resource/resource_chain_helper.rb index fdea5dd..4580c5e 100644 --- a/lib/active_admin_resource/resource_chain_helper.rb +++ b/lib/active_admin_resource/resource_chain_helper.rb @@ -70,4 +70,4 @@ def method_missing(method_name, *args, &block) @ransack[method_name] end end -end \ No newline at end of file +end diff --git a/lib/active_admin_resource/resource_class_adapter.rb b/lib/active_admin_resource/resource_class_adapter.rb index 0b3d717..5b59736 100644 --- a/lib/active_admin_resource/resource_class_adapter.rb +++ b/lib/active_admin_resource/resource_class_adapter.rb @@ -78,4 +78,4 @@ def chain_helper @chain_helper ||= ResourceChainHelper.new(self) end end -end \ No newline at end of file +end diff --git a/lib/active_admin_resource/resource_column.rb b/lib/active_admin_resource/resource_column.rb index 71c4609..7174406 100644 --- a/lib/active_admin_resource/resource_column.rb +++ b/lib/active_admin_resource/resource_column.rb @@ -10,4 +10,4 @@ def type :string end end -end \ No newline at end of file +end