rails: 配合 react-admin API adapter

这套API,用 ruby on rails 应该如何实现

const dataProvider = {
async getList(resource, { sort, filter, pagination }) => ({ data: Record[], total: number }),
async getOne(resource, { id }) => ({ data: Record }),
async getMany(resource, { ids }) => ({ data: Record[] }),
async getManyReference(resource, { target, id, sort, filter, pagination }) => ({ data: Record[], total: number }),
async create(resource, { data }) => ({ data: Record }),
async update(resource, { id, data }) => ({ data: Record }),
async updateMany(resource, { ids, data }) => ({ data: Identifier[] }),
async delete(resource, { id } ) => ({ data: Record }),
async deleteMany(resource, { ids }) => ({ data: Identifier[] }),
}

你提供的 dataProvider 接口是典型的 React Admin(或类似 Admin UI 框架)所使用的数据提供者契约,它要求后端提供一套符合 RESTful 风格 的 API。在 Ruby on Rails 中,可以通过标准的 Rails RESTful 路由 + 控制器 来实现这套接口。

下面是如何在 Rails 中实现这套 API 的详细说明(假设你使用的是 Rails 7+,并启用 JSON API 响应)。


✅ 1. 路由设计(RESTful)

假设你的 resourceposts,那么 Rails 默认的 RESTful 路由已经覆盖了大部分操作:

# config/routes.rb
Rails.application.routes.draw do
  resources :posts, only: [:index, :show, :create, :update, :destroy]
end

但注意:React Admin 的某些操作(如 getMany, updateMany, deleteMany, getManyReference不是标准 REST,需要额外自定义路由。

补充非标准路由:

Rails.application.routes.draw do
  resources :posts do
    collection do
      post :get_many        # POST /posts/get_many
      patch :update_many    # PATCH /posts/update_many
      delete :delete_many   # DELETE /posts/delete_many
    end
  end

  # getManyReference: 例如 /posts?target=author_id&id=5
  # 这个其实可以通过 index + filter 实现,无需额外路由
end

✅ 2. 控制器实现(PostsController 示例)

# app/controllers/posts_controller.rb
class PostsController < ApplicationController
  before_action :set_post, only: [:show, :update, :destroy]

  # getList
  def index
    @posts = Post.all

    # 分页(假设使用 pagy 或 kaminari)
    @posts = @posts.page(params[:page]).per(params[:perPage]) if params[:page]

    # 排序
    if (sort_field = params.dig(:sort, :field)) && (sort_order = params.dig(:sort, :order))
      @posts = @posts.order(sort_field => sort_order)
    end

    # 过滤(简单示例)
    if params[:filter].is_a?(Hash)
      params[:filter].each do |key, value|
        @posts = @posts.where(key => value) unless value.blank?
      end
    end

    render json: {
      data: @posts.map(&:as_json_for_admin),
      total: Post.count # 或 @posts.total_count(分页时)
    }
  end

  # getOne
  def show
    render json: { data: @post.as_json_for_admin }
  end

  # getMany
  def get_many
    ids = Array(params[:ids])
    posts = Post.where(id: ids)
    render json: { data: posts.map(&:as_json_for_admin) }
  end

  # getManyReference
  # 本质是带 filter 的 index,例如:/posts?target=author_id&id=5
  # 可在 index 中处理:
  # if params[:target] && params[:id]
  #   @posts = @posts.where(params[:target] => params[:id])
  # end

  # create
  def create
    @post = Post.new(post_params)
    if @post.save
      render json: { data: @post.as_json_for_admin }, status: :created
    else
      render json: { error: @post.errors }, status: :unprocessable_entity
    end
  end

  # update
  def update
    if @post.update(post_params)
      render json: { data: @post.as_json_for_admin }
    else
      render json: { error: @post.errors }, status: :unprocessable_entity
    end
  end

  # updateMany
  def update_many
    ids = Array(params[:ids])
    updated_ids = []
    Post.where(id: ids).find_each do |post|
      if post.update(post_params)
        updated_ids << post.id
      end
    end
    render json: { data: updated_ids }
  end

  # delete
  def destroy
    data = @post.as_json_for_admin
    @post.destroy
    render json: { data: data }
  end

  # deleteMany
  def delete_many
    ids = Array(params[:ids])
    deleted_ids = Post.where(id: ids).map(&:id)
    Post.where(id: ids).destroy_all
    render json: { data: deleted_ids }
  end

  private

  def set_post
    @post = Post.find(params[:id])
  end

  def post_params
    params.require(:data).permit(:title, :body, :author_id) # 根据你的模型调整
  end
end

✅ 3. 数据序列化建议

为保持一致性,建议在模型中定义 as_json_for_admin 方法:

# app/models/post.rb
class Post < ApplicationRecord
  def as_json_for_admin
    {
      id: id,
      title: title,
      body: body,
      author_id: author_id,
      created_at: created_at,
      updated_at: updated_at
    }
  end
end

或者使用 ActiveModel::Serializers / Jbuilder / Blueprinter 等序列化工具。


✅ 4. 请求参数格式说明

React Admin 默认发送的请求体结构如下:

  • create: { data: { title: "xxx", body: "yyy" } }
  • update: { id: 123, data: { title: "new" } }
  • updateMany: { ids: [1,2,3], data: { published: true } }

所以控制器中使用 params[:data] 提取数据是正确的。


✅ 5. 分页与排序参数(兼容 React Admin)

React Admin 默认发送:

pagination: { page: 1, perPage: 10 }
sort: { field: 'title', order: 'ASC' }

对应 Rails 接收为:

params[:pagination][:page]     # 或直接 params[:page](可做映射)
params[:sort][:field]
params[:sort][:order]

你可以在 ApplicationController 中做参数标准化:

# app/controllers/application_controller.rb
class ApplicationController < ActionController::API
  before_action :normalize_params

  private

  def normalize_params
    if params[:pagination]
      params[:page] = params[:pagination][:page]
      params[:perPage] = params[:pagination][:perPage]
    end
  end
end

✅ 6. 错误处理(可选)

统一错误格式,便于前端处理:

rescue_from ActiveRecord::RecordNotFound, with: :record_not_found

def record_not_found(exception)
  render json: { error: 'Record not found' }, status: :not_found
end

✅ 总结

| React Admin 方法 | Rails 对应实现 |
|------------------------|----------------------------------|
| getList | index(带分页/排序/过滤) |
| getOne | show |
| getMany | 自定义 get_many action |
| getManyReference | index + ?target=xxx&id=yyy |
| create | create |
| update | update |
| updateMany | 自定义 update_many |
| delete | destroy |
| deleteMany | 自定义 delete_many |


如果你正在开发的是 mockapi-rails 项目,建议:

  • 使用 resources :<resource> 自动生成基础路由;
  • getMany / updateMany / deleteMany 添加 collection 路由;
  • 统一返回 { data: ..., total?: ... } 结构;
  • 使用强参数(require(:data).permit(...))确保安全。

需要我为你生成一个通用的 BaseController 或脚手架代码吗?