+1

Giới thiệu về rails engine

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

Viblo
Let's register a Viblo Account to get more interesting posts.