瘦身无处不在。
写在前面
MVC 框架中的一个设计原则:fat model, skinny controller。且不说这原则是不是绝对的正确,不过面对臃肿的controller,IDE也会发出提醒表示抗议。
比如在Web API中,一般用get获取资料的时候,返回的JSON中,字段会比较多,相应地,你在controller里面写的index或者show 也相对有些臃肿。那么如何简化?
试试Jbuilder。
下面用个简单的例子来说明下怎么给controller瘦身。
正文
假定你完成了一个products的API接口,对应的app/controllers/api/v1/products_controller.rb
文件内容如下:
class Api::V1::ProductsController < ApiController
before_action :authenticate_user!, only: %i[index show create update destroy]
def index
@products = Product.all
render json: {
data: @products.map do |product|
{
id: product.id,
creator: product.user.user_name,
name: product.name,
description: product.description,
price: product.price
}
end
}
end
def create
@product = Product.new(
name: params[:name],
description: params[:description],
price: params[:price]
)
@product.user = current_user
if @product.save
render json: @product
else
render json: { message: 'failed', errors: @product.errors }, status: 400
end
end
def show
@product = Product.find(params[:id])
render json: {
id: product.id,
creator: product.user.user_name,
name: product.name,
description: product.description,
price: product.price
}
end
def update
@product = Product.find(params[:id])
@product.update(name: params[:name], description: params[:description],price: params[:price])
render json: {
message: 'update product successfully'
}
end
def destroy
@product = Product.find(params[:id])
@product.destroy
render json: {
message: 'product deleted'
}
end
end
看着很冗长啊,怎么瘦?
分三步走:
- 对index, show部分,用Jbuilder,
- 对create,update部分,用 strong params
- 对show, update, destory部分共有的@product,抽出来,用before_action
第一步:简化index,show
这里可以看到show和index中重复代码较多,用上partial
新建文件:
app/views/api/v1/products/_item.json.jbuilder
,app/views/api/v1/products/show.json.jbuilder
,app/views/api/v1/products/index.json.jbuilder
,在
app/views/api/v1/products/_item.json.jbuilder
,添加如下内容:json.id product.id json.creator product.user.user_name json.name product.name json.description product.description json.price product.price
在
app/views/api/v1/products/show.json.jbuilder
,添加如下内容:json.partial! 'item', product: @product
在
app/views/api/v1/products/index.json.jbuilder
, 添加如下内容:json.array! @products, partial: 'item', as: :product
修改
app/controllers/api/v1/products_controller.rb
的index和show部分, 删除render:class Api::V1::ProductsController < ApiController before_action :authenticate_user!, only: %i[index show create update destroy] def index @products = Product.all end ....... def show @product = Product.find(params[:id]) end ....... end
可以用postman测试一下,确保接口仍正常。
第二步:简化create,update, 用params.require
修改
app/controllers/api/v1/products_controller.rb
的create和update部分,如下:class Api::V1::ProductsController < ApiController before_action :authenticate_user!, only: %i[index show create update destroy] ..... def create @product = Product.new(product_params) @product.user = current_user if @product.save render json: @product else render json: { message: 'failed', errors: @product.errors }, status: 400 end end ....... def update @product = Product.find(params[:id]) @product.update(product_params) render json: { message: 'update product successfully' } end ....... private def product_params params.permit(:name, :description, :price) end end
注意,这里**不能使用params.requrie(:product).permit(:name, :description, :price)**,因为你是调用接口来新建或者修改的。
可参见这个:When JSON data posted in rails throws param is missing or the value is empty: stall
第三步:简化show,update, destroy
把show,update, destroy相同的部分:
@product = Product.find(params[:id])
抽出来:class Api::V1::ProductsController < ApiController before_action :authenticate_user!, only: %i[index show create update destroy] before_action :find_product, only: %i[show update destroy] ..... def show; end def update @product.update(product_params) render json: { message: 'update product successfully' } end def destroy @product.destroy render json: { message: 'product deleted' } end private ....... def find_product @product = Product.find(params[:id]) end end
最后瘦身的controller长这样:
class Api::V1::ProductsController < ApiController
before_action :authenticate_user!, only: %i[index show create update destroy]
before_action :find_product, only: %i[show update destroy]
def index
@products = Product.all
end
def create
@product = Product.new(product_params)
@product.user = current_user
if @product.save
render json: @product
else
render json: { message: 'failed', errors: @product.errors }, status: 400
end
end
def show; end
def update
@product.update(product_params)
render json: { message: 'update product successfully'}
end
def destroy
@product.destroy
render json: { message: 'product deleted'}
end
private
def product_params
param.permit(:name, :description, :price)
end
def find_product
@product = Product.find(params[:id])
end
end
是不是清爽了很多?看着舒服多了,大功告成!
这里多一句,如果你的某个method部分代码较多,也可以使用拆分的方式, 比如下面这个show:
def show
@category = Category.find(params[:id])
@categories = Category.all
@search = @category.products.approved.order(updated_at: :desc).ransack(params[:q])
@products = @search.result.page(params[:page]).per(50)
rate
end
可以换成:
before_action :fetch_current_category, only: [:show]
before_action :fetch_categories, only: [:show]
before_action :fetch_search_results, only: [:show]
def show
rate
end
private
def fetch_current_category
@category = Category.find(params[:id])
end
def fetch_categories
@categories = Category.all
end
def fetch_search_results
@search = @category.products.approved.order(updated_at: :desc).ransack(params[:q])
@products = @search.result.page(params[:page]).per(50)
end
参考
What is meant by ‘Assignment Branch Condition Size too high’ and how to fix it?
对于”fat model, skinny controller“这个原则,也有人反对,提出**”skinny everything”**, 戳链接了解: