Giới thiệu về rails engine
Bài đăng này đã không được cập nhật trong 7 năm
Giới thiệu về rails engine
1. Giới thiệu
Rails engine giống như một ứng dụng thu nhỏ, bổ trợ, cung cấp các tính năng của nó cho parent app một cách độc lập. Chúng ta làm việc với rails chắc hẳn đều biết đến các gem như devise... Chúng là một engine.
Tất cả engine có thể là gem nhưng không chắc chắn 100% gem có thể là engine
Việc sử dụng engine có tác dụng tách các function thành các component riêng rẽ với parent app, thay vì viết cùng trên app chính. Như vậy việc phát triển app sẽ rất dễ dàng và dễ kiểm soát
2. Cấu trúc rails engine
Một rails engine có cấu trúc tương tự như một rails app thu nhỏ. Khi tạo một rails engine các file được sinh ra sẽ như này
create
create README.rdoc
create Rakefile
create foo_bar.gemspec
create MIT-LICENSE
create .gitignore
create Gemfile
create app
create app/controllers/foo_bar/application_controller.rb
create app/helpers/foo_bar/application_helper.rb
create app/mailers
create app/models
create app/views/layouts/foo_bar/application.html.erb
create app/assets/images/foo_bar
create app/assets/images/foo_bar/.keep
create config/routes.rb
create lib/foo_bar.rb
create lib/tasks/foo_bar_tasks.rake
create lib/foo_bar/version.rb
create lib/foo_bar/engine.rb
create app/assets/stylesheets/foo_bar/application.css
create app/assets/javascripts/foo_bar/application.js
create bin
create bin/rails
create test/test_helper.rb
create test/foo_bar_test.rb
append Rakefile
create test/integration/navigation_test.rb
Các thành phần chính của một engine: Gemspec: là nơi để require các gem được sử dụng trong engine. Các gem này bắt buộc phải dược load từ trên gem server
Rails entities: models, controllers, views, migrations, initializers
lib/foo_bar/engine.rb được gọi trước các file của rails engine giống trong initializer của rails app
config/routes.rb - định nghĩa routes của engine giống như rails app
3. Một số chú ý
Rails engine có 2 loại full engine và mountable engine. Ở đây mình chỉ đề cập đến mountable engine. Để tạo một mountable engine ta dùng lệnh
rails plugin new engine_name --mountable --dummy-path=spec/dummy --skip-no-test
lệnh trên tạo ra 1 mountable engine với thư mục spec/dummy để viết test
- Isolate Namespace
Ví dụ ta có khai báo cho engine Foo như sau
module Foo
class Engine < ::Rails::Engine
isolate_namespace Foo
end
end
Điều này có nghĩa mọi class trong controller hay model của engine đều được wrap trong namespace Foo. Điều đó giúp cho engine hoạt động độc lập tránh việc confict với namespace của parent app cũng như các engine khác.
Để gọi một class trong parent app ta chỉ cần gọi theo namespace ví dụ Foo::User là ta đã gọi đến model User của engine.
- Routes
Routes của engine tương tự như của rails application. Chúng ta có thể khai báo các resources tương tự như rails app. Chúng ta có thể mount engine vào trong routes của parent app bằng lệnh
mount Foo::Engine, at: "foo", as: foo_engine
Khi đó để gọi đến routes của engine ta sẽ gọi thông qua namespace như sau: ví dụ gọi đến danh sách các users:
localost:3000/foo/users
Khi gọi đên đường dẫn path trong engine ta sẽ phải gọi theo cấu trúc ví dụ gọi index của User controller như sau:
foo_engine.users_path
Migrations
Để tạo một model hay migration trong engine ta tạo bình thường như rails app thông thường . Nhưng có 1 điểm chú ý các table được tạo ra sẽ có thêm một prefix có tên là chính engine đó.
Ví dụ để tạo một bảng users trong engine foo ta có thể dùng lệnh
rails g model User
khi đó một file migration định nghĩa việc tạo table có tên là foo_users
như sau:
class CreateFooUsers < ActiveRecord::Migration[5.0]
def change
create_table :foo_user do |t|
.........
t.timestamps
end
end
end
Để tạo migration từ parent app ta sẽ gọi lệnh copy sau để copy sang :
rails g engine_name:install:migrations
với engine name là module name của engine ta cần copy.
- Lib/{engine_name}/engine.rb
Đây là file được load khi engine được gọi. Nó giống config/initialize của rails thông thường . Nó được dùng để config một số thông số của engine hay cấu hình của gem được sử dụng trong engine. Config đơn giản nhất của nó như sau:
module FooBar
class Engine < Rails::Engine
isolate_namespace FooBar
end
end
Ta có thể require các file hay cấu hình các gem trước khi engine được load khi đặt trong hàm initializer :
initializer "now_common" do
Engine::load_config
end
Cấu hình của 1 số gem như carrierwave, config cũng nên đặt trong hàm này để load trước mới có thể sử dụng được Ví dụ config để sử dụng gem config của rails như sau:
def self.load_config
engine_config_dir = Pathname.new(File.expand_path('../../../config', __FILE__))
Settings.reload_from_files(
(engine_config_dir + 'settings.yml').to_s,
(engine_config_dir + "settings/#{Rails.env}.yml").to_s
)
end
initializer "now_common" do
Engine::load_config
end
Gem trong engine Engine sẽ không sử dụng trực tiếp gem file như trong rails app. Để sử dụng gem ta phải add nó trong gemspec bằng câu lệnh
s.add_dependence
Như đã nói ở trên gemspec ko load dc các gem mà ko phải trên server rubygem. Ngoài ra một số gem cần phải được require trước khí engine dc load trong file engine.rb
- Rspec
Để làm việc với rspec với engine bạn có thể viết unitest trong chính engine . Thế nhưng mình khuyên các bạn nên tạo ra một rails app ví dụ rspec/dummy tương tự như parent app và require engine đó giống như một gem . sau đó ta có thể test một cách dễ dàng.
Một chú ý nữa vấn đề autoload khi viết rspec trong engine. Mình đã từng gặp trường hợp này khi đặt engine nested bên trong parent app.
Nếu chạy trên development thì ko vấn đề gì nhưng khi viết test trong engine xảy ra lỗi do bản thân rails nhận folder là một module nên nếu để engine nested sẽ dính lỗi.
5. Tài liệu tham khảo
https://www.amberbit.com/blog/2015/10/15/rails-mountable-engines/ http://api.rubyonrails.org/classes/Rails/Engine.html
All rights reserved