定义一个 helper method。
背景
在工作中遇到的一个问题:
navbar的item根据是否存在项目来决定是否显示,也就是写一个类似current_user的helper method current_project,要求当current_project存在时,navbar中的dropdown组件【以project为item】显示current_project的name,navbar的item则显示该project下的plans,issues等等,当current_project为nil时,则不显示project的任何信息。
其中,project与plan,issue存在一对多的关系:
#project.rb
class Project < ApplicationRecord
has_many :plans, dependent: :destroy
has_many :issues, dependent: :destroy
end
#plan.rb
class Plan < ApplicationRecord
belongs_to :project
end
#issue.rb
class Issue < ApplicationRecord
belongs_to :project
end
我最初在module ApplicationHelper中定义了一个helper method,算是勉强实现了功能,但是写的极为难看,是通过request.original_fullpath()来判断请求中是否存在project, 如果存在,则找到current_project。而且这个helper method因为定义在ApplicationHelper中,便只能在views中使用。后面老大教了个好方法,真心觉得很赞,记录下大致的解决方法。
解决
devise的current_user的定义,较为复杂,在它的源代码define_helpers中可以看到:
def current_#{mapping}
@current_#{mapping} ||= warden.authenticate(scope: :#{mapping})
end
以我目前的水平,真心看的不是很明白,在stack overflow看到了一个比较简单的定义:
#application_controller.rb
def current_user
@current_user ||= User.find_by_id!(session[:user_id])
end
helper_method :current_user
参考这个,我们可以定义一下current_project:
#application_controller.rb
def current_project
@current_project ||= Project.find(params[:project_id])
end
helper_method :current_project
这样看着,貌似很简单就解决了,但是有一个问题,那就是所有的request中,project_id并不一定存在,这样使用Project.find(params[:project_id])
就有可能报错,显示params中找不到project_id。
老大教的方法是这样的,重新定义了一个controller,然后通过继承,覆写current_project。具体如下:
Step1、在application_controller.rb
定义current_project:
#application_controller.rb
def current_project
nil
end
helper_method :current_project
注意,这里的current_project返回的是nil值,这样所有继承自ApplicationController的Controller,其current_project默认为nil。
Step2、新建一个BaseProjectController, 继承自ApplicationController,重新定义current_project,覆盖掉父类的method:
#base_project_controller.rb
class BaseProjectController < ApplicationController
def current_project
@current_project ||= Project.find(params[:project_id])
end
end
Step3、修改IssuesController和PlansController,让它们继承自BaseProjectController,拥有current_project方法:【以IssuesController为例,PlansController以此类推】
## 修改前:
class IssuesController < BaseProjectController
load_and_authorize_resource :project
.....
end
## 修改后:
class IssuesController < BaseProjectController
before_action -> { @project = current_project }
authorize_resource :project
.....
end
这里删除了cancancan的load_authorize,改用before_action的方式,将current_project赋给了@project。
对于没有继承自BaseProjectController的Controller,其current_project则为nil,不得不说,这样方式真的是漂亮。
OK!