2020. 7. 3. 17:57ㆍ위키
ActiveAdmin 은 루비 온 레일즈를 위한 관리 프레임워크입니다. 이 글은 ActiveAdmin 에 대한 가이드입니다. ActiveAdmin.info 에 대한 내용을 다루고 있습니다. 개발환경과 코드는 아래를 참고하시기 바랍니다.
macOS Catalina
ruby 2.6.6
rails 5.2.3
hwangwoojin/rails-blog
This is my code from getting started code at RailsGuides v5.2 : https://guides.rubyonrails.org/v5.2/getting_started.html - hwangwoojin/rails-blog
github.com
설치하기
기존 루비 온 레일즈에 ActiveAdmin 을 적용하는 방법은 매우 간단합니다. 먼저 루비 온 레일즈 프로젝트에 있는 Gemfile 에 다음 Gem 들을 추가해줍니다.
# ActiveAdmin
gem 'activeadmin'
# Plus integrations with:
gem 'devise'
gem 'cancancan'
gem 'draper'
gem 'pundit'
activeadmin Gem 아래에 있는 4개의 Gem 들은 사용하면 유용한 Gem 들입니다. 자세히는 다루지 않지만 하나만 짚고 가자면 devise 는 ActiveAdmin 을 위한 훌륭한 보안 옵션을 제공해줍니다. 어쨋든 Gem 을 설치한 후에 다음 명령어를 통해 ActiveAdmin 을 설치할 수 있습니다. 여기서 g 는 generate 명령어의 줄임말입니다.
$ rails g active_admin:install
# 만약 Devise 를 사용하지 않는다면
# rails g active_admin:install --skip-users
# 만약 이미 존재하는 user 클래스를 사용하고 싶다면
# rails g active_admin:install User
만약 위 명령어를 눌렀는데 무한 로딩이 걸린다면 아래 명령어를 입력한 후 다시 시도해보세요.
$ spring stop
성공했다면 ActiveAdmin 관련 파일이 이것저것 많이 설치가 됩니다. 시작전에 먼저 마이그레이션도 해주어야 합니다.
rails db:migrate
rails db:seed
다 끝났으면 웹서버를 켜보도록 하겠습니다.
rails server
그러면 이제 http://localhost:3000/ 의 모습이 조금 변경되면서 http://localhost:3000/admin/ 에 ActiveAdmin 페이지가 생성됩니다. Hello, World 의 모양이 바뀌는 이유는 ActiveAdmin 의 stylesheet 를 같이 사용하기 때문입니다.
그럼 이제 ActiveAdmin 에 로그인을 해보도록 하겠습니다. 로그인 정보는 Email: admin@example.com, Password: password 입니다. 가끔 로그인이 안될때가 있는데 그럴때는 Ruby on Rails 콘솔로 접속한 후 새로 어드민 계정을 생성해주면 됩니다.
$ rails console
> AdminUser.create!(:email => 'admin@example.com', :password => 'password', :password_confirmation => 'password')
그러면 이제 ActiveAdmin 을 위한 모든 준비는 끝났습니다.
기본 설정하기
ActiveAdmin 의 기본적인 설정은 config/initializers/active_admin.rb 에서 할 수 있습니다. 이 글에서는 기본적인 것들만 만져보도록 하겠습니다. 첫번째는 Authentication 입니다. 로그인을 할 때 권한을 증명하는 부분이죠. 대표적으로 다음과 같은 옵션을 설정할 수 있습니다.
# 보안 증명을 강제로 하도록 하는 메서드
config.authentication_method = :authenticate_admin_user!
# 현재 유저가 누구인지 알기 위한 메서드
config.current_user_method = :current_admin_user
위 설정들을 사용할 생각이 없다면 false 값을 주면 됩니다.
config.authentication_method = false
config.current_user_method = false
두번째는 사이트 설정입니다. 사이트의 기본적인 제목, 주소, 이미지 등을 설정할 수 있습니다. 참고로 이미지의 위치는 app/assets/images/logo.png 입니다.
# 사이트의 제목
config.site_title = "My Admin Site"
# 사이트 주소
config.site_title_link = "/"
# 사이트 이미지
config.site_title_image = "logo.png"
세번째는 국가 설정입니다. internationalization 이라고도 하며 i18n 이라고도 하는 부분입니다. 처음에 ActiveAdmin 을 설치하면 영어밖에는 없지만, config/locales 에 ko.yml 을 추가하는 식으로 한국을 위한 설정을 할 수 있습니다. ko.yml 파일에 대해서는 ActiveAdmin 깃허브 페이지 를 참고하시기 바랍니다. 단 여기에는 Devise 를 위한 번역 옵션은 없으니 Devise-i18n 은 따로 설정해주어야 합니다.
네번째는 네임스페이스입니다. ActiveAdmin 의 모든 resource 들은 다 네임스페이스를 가집니다. 기본적인 네임스페이스는 admin 입니다. 만약에 admin 페이지와 super_admin 페이지가 있고, 둘 다 각각 다른 타이틀을 가지는 것을 원한다고 생각해 보자구요. 그러면 다음과 같이 다른 네임스페이스를 주어 구별해 줄 수 있습니다.
ActiveAdmin.setup do |config|
config.site_title = "My Default Site Title"
config.namespace :admin do |admin|
admin.site_title = "Admin Site"
end
config.namespace :super_admin do |super_admin|
super_admin.site_title = "Super Admin Site"
end
end
만약 멀티-테넌트 모델의 어플리케이션인 경우 route_option 을 통해 다음과 같이도 설정 가능합니다.
config.namespace :site_1 do |admin|
admin.route_options = { path: :admin, constraints: ->(request){ request.domain == "site1.com" } }
end
만약에 path 를 사용하지 않고 subdomain 으로 네임스페이스를 주려면 다음과 같이 하면 됩니다.
config.namespace :admin do |admin|
admin.route_options = { path: '', subdomain: 'admin' }
end
다섯번째는 comment 입니다. comments 는 ActiveAdmin 에서 기본적으로 제공하는 것들 중 하나입니다. dashboard, user 그리고 그 옆에 comment 가 있죠.
뭔가 유용한 기능 같지만, 아직은 무슨 기능인지 잘 모르겠으므로 그냥 끄도록 하겠습니다. 다음 코드를 통해 Comment 옵션을 끌 수 있습니다. 적용이 안될경우, 웹서버를 한번 껏다가 켜면 적용이 됩니다.
# 모든 어플리케이션에서 comment 끄기
config.comments = false
# 해당 네임스페이스에서 comment 끄기
config.namespace :admin do |admin|
admin.comments = false
end
여섯번째는 Utility Navigation 입니다. 이게 뭐냐면 어드민 페이지의 오른쪽 제일 위에 보이는 유저 정보와 로그아웃을 말합니다.
이를 수정하는 코드는 다음과 같습니다. 여기서는 admin@example.com 을 My Great Website 로 바꾸어보도록 하겠습니다.
config.namespace :admin do |admin|
admin.build_menu :utility_navigation do |menu|
menu.add label: "My Great Website", url: "http://www.mygreatwebsite.com", html_options: { target: :blank }
admin.add_logout_button_to_menu menu
end
end
마지막은 Footer 입니다. 보통 페이지의 가장 아래를 Footer 라고 하죠. 초기 설정은 “Powered by ActiveAdmin” 입니다. 이를 My Great Footer 로 바꾸어보도록 하겠습니다. 코드는 다음과 같습니다.
config.footer = 'My Great Footer'
Resource 다루기
ActiveAdmin 에서 resource 는 Ruby on Rails 에서 모델을 의미합니다. 하지만 resource 를 생성하기 전에 반드시 해당 모델을 먼저 생성해야 합니다. 모델을 생성하는 Rails 코드는 다음과 같습니다. 여기서는 Post 모델을 생성하겠습니다.
$ bin/rails g model Post
$ bin/rails db:migrate
모델을 생성했으면 이제 resource 를 생성할 차례입니다.
$ rails g active_admin:resource Post
여기까지 문제없이 수행했다면 웹서버를 켰을 때 다음과 같이 Post 라는 이름의 resource 가 생성되는 것을 확인할 수 있습니다.
그리고 app/admin/posts.rb 라는 파일이 생성됩니다. 이 파일에서 resource 에 대한 자세한 것들을 설정할 수 있습니다. permit_params 를 사용하면 Post 가 어떤 속성을 바꿀 수 있는지를 명시할 수 있습니다.
# app/admin/posts.rb
permit_params :title, :content, :publisher_id
여러 속성값을 전달하는 경우가 있을수 있습니다. 예를 들어 array 속성이나 HABTM(has_and_belongs_to_many) 으로 선언된 속성이 있습니다. 이 경우 다음과 같이 선언해주어야 합니다. HABTM 이 뭐냐구요?
# app/admin/posts.rb
# 만약 HABTM 이 roles 인 경우
permit_params :title, :content, :publisher_id, role_ids: []
만약 여러 속성들을 가지고 있는 params 인 경우도 이와 비슷합니다. 단 이 경우 추가적인 조작이 필요합니다. 새로 클래스를 생성해서 accepts_nested_attributes_for 를 설정해주어야 합니다.
# app/admin/posts.rb
ActiveAdmin.register Post do
permit_params :title, :content, :publisher_id,
tags_attributes: [:id, :name, :description, :_destroy]
end
# 중첩된 속성을 위한 코드
class Post < ActiveRecord::Base
accepts_nested_attributes_for :tags, allow_destroy: true
end
반면에 resource 자체가 중첩된(nested) 경우는 belongs_to 를 먼저 선언하고, permit_params 를 선언해야 합니다. 순서에 주의하시기 바랍니다.
# app/admin/posts.rb
belongs_to :user
permit_params :title, :content, :publisher_id
동적으로 permit_params 를 설정하고 싶은 경우는 아래와 같습니다. 이 코드는 만약 현재 사용자가 어드민일 경우에 author_id 라는 속성을 추가로 수정할 수 있도록 합니다.
# app/admin/posts.rb
permit_params do
params = [:title, :content, :publisher_id]
params.push :author_id if current_user.admin?
params
end
이렇게 permit_params 메서드를 통해 허용한 속성들은 permitted_params 를 통해 가져올 수 있습니다. params 를 통해서도 parameter 들을 가져올 수 있지만 이보다 permitted_params 를 통해 strong parameter 들만 가져오는 것이 훨씬 더 좋은 방법입니다. 보통 create 메서드를 사용할 때 다음과 같이 사용하는 것이 정석적입니다.
# app/admin/posts.rb
controller do
def create
# 좋은 방법
@post = Post.new(permitted_params[:post])
# 안좋은 방법
@post = Post.new(params[:post])
end
end
resource 에서는 기본적으로 모든 CRUD 가 가능합니다. 이를 제한하지 않으면 문제가 생길 수 있겠죠? 이는 actions 에서 설정할 수 있습니다. 아래 코드는 resource 에서 update, destroy 를 제외한 모든 action 들을 허용한다는 의미입니다.
# app/admin/posts.rb
actions :all, except: [:update, :destroy]
액션의 이름은 config/locales 디렉토리에 정의되어 있으며, 원하는대로 수정 가능합니다.
resource 의 이름을 바꾸고 싶다면 resource 를 선언한 클래스에서 as 로 원하는 이름을 적어주면 됩니다. 만약 Post resource 를 Article 이라는 이름으로 바꾸고 싶다면 아래 코드처럼 구현하면 되죠.
# app/admin/posts.rb
ActiveAdmin.register Post, as: "Article"
이제 새로고침하고 http://localhost:3000/admin/articles 에 접속하면 정상적으로 동작하는 것을 확인할 수 있습니다. Post 가 아닌 Article 이 되어 말이죠.
resource 뿐 아니라 네임스페이스의 이름도 변경할 수 있습니다. 기본은 default 이지만 다음 코드처럼 today 나 false 로 설정할 수 있습니다.
# app/admin/posts.rb
# /today/posts 로 설정하고 싶은 경우
ActiveAdmin.register Post, as: "Article", namespace: :today
# /posts 로 설정하고 싶은 경우
ActiveAdmin.register Post, as: "Article", namespace: false
만약 namespace 를 today 로 설정하게되면 기본 어드민 페이지에서 Article 이 사라지고, http://localhost:3000/today/articles 에서 짜잔하고 등장합니다.
이번에는 resource 의 menu 에 대해 다루어보도록 하겠습니다. menu 란 resource 를 만들었을 때 어드민 왼쪽 위에 생성되는 것들을 말합니다. Dashboard, Admin Users 그리고 위에서 생성한 Posts 등입니다. 만약 Posts 를 그냥 안보이게 해버리고 싶다면 menu 에 false 를 주면 됩니다. 이는 if 문을 통해 동적으로 설정할 수도 있습니다.
# app/admin/posts.rb
ActiveAdmin.register Post do
menu false
# 만약 현재 유저가 posts 를 edit 할 수 있을때만 보여주고 싶을 때
# menu if: proc{ current_user.can_edit_posts? }
end
메뉴는 라벨을 주어 이름을 설정할 수도 있습니다.
# app/admin/posts.rb
menu label: "Article"
라벨을 해도 이름이 바뀌고, as: 를 해도 이름이 바뀌죠. 이 둘의 차이는 실제 주소가 바뀌는지의 차이입니다. as 를 통해 선언하게 되면 주소도 바뀌는(/admin/article) 반면에 라벨을 하게 되면 주소는 그대로인채(admin/posts) 메뉴의 이름만 변경되는 식입니다.
메뉴의 위치는 priority 를 주어 설정할 수 있습니다. priority 가 1에 가까우면 왼쪽, 크면 클수록 오른쪽입니다. 기본값은 10 이기 때문에 Posts 의 priority 를 1로 주게되면 My Posts 가 메뉴에서 왼쪽으로 이동합니다.
# app/admin/posts.rb
menu priority: 1
메뉴를 드랍다운으로 구현하고 싶으면 parent 를 사용합니다. 직접 보면 이해가 더 쉬울 것이라 생각합니다.
# app/admin/posts.rb
menu parent: "Blog"
다중으로도 가능합니다. 예를 들어 Admin 하위에 Blog 하위에 Posts 를 두고 싶다면 다음과 같이 하면 됩니다.
# app/admin/posts.rb
menu parent: ["Admin", "Blog"]
app/admin/posts.rb 에서만 메뉴를 변경할 수 있는 것은 아닙니다. config/initializer/active_admin.rb 파일에서도 config 를 수정해서 메뉴를 수정할 수 있습니다. 단 이 경우 resource 를 새로 불러와야 하므로 웹서버를 껏다가 켜야 적용이 됩니다. 꽤 간단한 부분이므로 설명은 config 를 어떻게 구현했는지에 대한 코드와 결과 화면으로 대체하겠습니다.
# config/initializer/active_admin.rb
config.namespace :admin do |admin|
admin.build_menu do |menu|
menu.add label: "The Application", url: "/", priority: 0
menu.add label: "Sites" do |sites|
sites.add label: "Google",
url: "http://google.com",
html_options: { target: :blank }
sites.add label: "Facebook",
url: "http://facebook.com"
sites.add label: "Github",
url: "http://github.com"
end
end
end
네임스페이스 사용에만 주의하면 크게 어려운 부분은 없습니다. 정상적으로 입력했다면 다음과 같은 화면이 나오게 됩니다.
scope 를 사용한다면 어드민마다 페이지에 접근하는 범위를 다르게 할 수도 있습니다. 이부분은 아직 저도 이해중...
# app/admin/posts.rb
# posts 를 current_user.posts 에만 제한하고 싶을 때
scope_to :current_user
# 위 경우에서 default 값이 없을 때
scope_to :current_user, association_method: :blog_posts
# 블록 단위로 범위를 정할 때
scope_to do
User.most_popular_posts
end
# 동적으로 정할 때
scope_to :current_user, if: proc{ current_user.limited_access? }
scope_to :current_user, unless: proc{ current_user.admin? }
1:N 관계에 있는 데이터베이스에서 쿼리문을 실행시킬 때, 관련 테이블의 정보를 모두 가져오므로 성능상 문제가 발생할 수 있습니다. 이를 N+1 문제라고도 하는데 이 경우 참조할 정보를 미리 명시하는 eager loading 을 통해 성능을 향상시킬 수 있습니다. 이는 include 키워드를 사용해서 구현할 수 있습니다.
# app/admin/posts.rb
includes :author, :categories
위에서는 resource 를 하나만 선언했지만 만약에 resource 가 여러개있다면 서로 포함관계가 있을수도 있겠죠. 이를 해주는 것이 belongs_to 입니다. 예를 들어 project 하나가 여러개의 ticket 을 가질 수 있다고 가정해보죠. 그러면 이를 다음과 같이 구현할 수 있습니다. 아 물론 그 전에 Model 과 Resource 선언하는거 잊지 마시구요.
# app/admin/projects.rb
ActiveAdmin.register Project
# app/admin/tickest.rb
ActiveAdmin.register Ticket do
belongs_to :project
end
그러면 ticket resource 는 projects/1/tickets, projects/2/tickets 와 같이 projects 하위에 id 값을 가진채로 설정이 됩니다.
'위키' 카테고리의 다른 글
장고(Django) RESTful Framework 시작하기 (1) | 2020.07.19 |
---|---|
간단한 Command-line HTTP 클라이언트, Httpie (0) | 2020.07.19 |
리액트(ReactJS) 빠르게 시작하기 (0) | 2020.07.18 |
루비 온 레일즈 v5.2 시작하기 (0) | 2020.07.01 |
루비 튜토리얼 (0) | 2020.06.29 |