【问题标题】:Rails: Create Dynamic routes, controllersRails:创建动态路由、控制器
【发布时间】:2021-09-08 08:47:38
【问题描述】:

我很少有数据库视图作为 REST API 端点公开。当前的实现是添加视图后,我们添加 Rails 代码,如

  1. 添加新路线
  2. 添加Controller等代码查询返回json响应的视图

这种方法的缺点是我们每次添加数据库视图时都需要向应用程序添加代码,有时这也不是即时的,这是另一个问题。

有没有办法,可能通过使用Meta Programming 或其他我们能够查询数据库以获取视图列表并生成必要的路由和代码以返回有效响应的方法。

下面是相关部分的代码

namespace :api do
  namespace :v1 do
    [
      'leases',
      # a new will go in here
    ]
  end
end

class Api::V1::LeasesController < Api::V1::ApplicationController
  api :GET, '/leases', "Retrieve paginated well month data"
  param :authentication_token, String, "Token for authentication"

  def index
    result = Api::Lease.ransack(params[:q]).result.page(params[:page]).per(10000)
    render json: result.to_json
  end
end


class Api::Lease < ExternalRecord
  self.table_name = 'View_Leases' #View in External Postgres server
end

谢谢。

【问题讨论】:

  • 每次向数据库添加内容时,不应该有理由添加新的路线和视图。您能否添加一些您的路线、模型和控制器的代码?这可能使我们更容易理解您的问题并为您提供帮助。
  • 已用相关代码更新问题

标签: ruby-on-rails ruby ruby-on-rails-5 metaprogramming


【解决方案1】:

有没有办法,可能通过使用元编程或其他方式,我们能够查询数据库以获取视图列表并生成必要的路由和代码以返回有效响应。

是的,但实际实现取决于使用的数据库。在 Postgres 上,您可以通过查询 pg_catalog.pg_views 来获取视图列表:

pg_views = Arel::Table.new('pg_catalog.pg_views')
query = pg_views.project(:schemaname, :viewname)
                .where(
                  pg_views[:schemaname].in('pg_catalog', 'information_schema').not
                )

result = ActiveRecord::Base.connection.execute(query)
# ... 

但是这里需要改变框架。视图是否需要与它自己的路由相对应,或者您能否创建更好的 RESTful 设计?

例如,如果您按年/月列出,您可以轻松设置一条涵盖它的路线:

namespace :api do
  namespace :v1 do
    resources :leases do
      get '/leases/by_month/:year/:month', as: :by_month, action: :by_month
    end
  end
end

你能用元编程建立一个模型吗?

当然。 Ruby 中的类是一等对象,您可以使用Class.new 创建它们:

# Setup a base class for shared behavior 
class ApplicationView < ActiveRecord::Base
  self.abstract_class = true
end

str = 'Foo'
model = Class.new(ApplicationView) do |klass|
  # do your model specific thing here...
end

# Classes get their name by being assigned to a constant
ApplicationView.const_set(str, model)
ApplicationView::Foo.all # SELECT * FROM foos;  

ActiveRecord 和 ActiveModel 不太喜欢匿名类(未分配给常量的类),因为它们会根据类名进行大量假设。在这里,我们将常量嵌套在 ApplicationView 中只是为了避免命名空间崩溃。

有时在库代码中使用的另一种方法是创建一个包含代码的字符串以定义类并对其进行评估。 ​

您还可以设置查询不同表/视图的单个模型。

您能否使用元编程设置控制器和视图(如在 MVC 中)?

是的。但你不应该需要它。您可以简单地创建可以处理各种模型的通用控制器。请记住,控制器对应于单个模型的想法只是适用于琐碎情况的约定。

【讨论】:

  • 非常感谢 max,这非常有帮助。我正在使用postgres,但我不确定的一件事是我将如何处理添加的新视图。例如,目前我有一个租约视图,如果我们添加另一个名为lease_owners 我将如何在我的routes.rb 中处理这个问题
  • 如果我没记错的话 routes.rb 是按请求评估的。因此,如果您在 routes.rb 中查询 'pg_catalog.pg_views' 表,您应该获得有关数据库架构的最新信息。人们为多租户做了非常相似的设计。将功能拆分为单独的模块/类,而不是将 routes.rb 变成怪物。
  • 我应该警告您,这可能是一项相当艰巨的任务,并将测试您对所有 Rails 基础知识的了解。
  • 非常感谢 Max,这非常有帮助。多亏了你,我现在有了一个好主意。
【解决方案2】:

您可以通过给定的连接进行任意 SQL 查询

namespace :v1 do
  resources :views, only: :show
end


class ViewsController
  def show
    table_name = "View_#{params[:id].titleize}"
    offset = (params[:page].to_i || 1) * 10_000 - 10_000
    limit = 10_000
    sql = "SELECT #{table_name}.* FROM #{table_name} OFFSET #{offset} LIMIT #{limit};"
    data = ActiveRecord::Base.connection.execute(sql)
    render json: data.to_json
  end
end

然后忘记使用ransack。 Ransack 需要一个模型,它依赖于推断的列类型将查询转换为模型本身的一系列 where、join 等。

当然,如果你自己创建 SQL 字符串,你应该保护自己免受 SQL 注入。

如果您想保留查询的全部功能,最好在您的应用程序和视图之间放置Hasura 之类的内容,并将传入的查询转换为对 hasura 的 GraphQL 请求(其余部分会为您处理) .

【讨论】:

  • 更好的方法是使用 Arel 以编程方式构造查询,而不是构造字符串,这是一个非常冒险的提议。 tbl = Arel.Table.new(str); tbl.project(tbl[Arel.star]).offset(offset).limit(limit).
  • 很好@max !!!很好的主意。如果您将其作为单独的答案发布,我会支持它
  • 啊,你已经做到了:facepalm:
猜你喜欢
  • 2011-12-11
  • 2011-09-25
  • 2011-02-05
  • 2017-10-17
  • 1970-01-01
  • 2018-04-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多