Tạo Gem của chính bạn
Bài đăng này đã không được cập nhật trong 7 năm
Khi bạn là một Ruby developer thì bạn không lạ lẫm gì với các Gem. Bạn có từng muốn có một Gem thật sự hữu ích để mọi người có thể sử dụng, mà tác giả là chính mình không? Hôm nay, tôi sẽ giới thiệu đến các bạn cách tạo một Gem cho Ruby on Rails.
Một Gem đơn giản mà tôi đã đẩy lên Github simple_banana
YOUR FIRST GEM
Tôi sẽ bắt đầu với một Gem, cái tên khá là "chuối" - simple_banana
. Bạn có thể đặt tên thật độc đáo hoặc mang dấu ấn nào đó của bạn, tốt nhất nếu nó hữu dụng thì hãy đặt một tên gợi nhớ cho Gem. Cách đặt tên được hướng dẫn ở basic recommendations.
Đặt tên thư mục và tên file sẽ có dạng cơ bản như thế này, gồm 1 file .rb đặt trong tư mục lib, và một file .gemspec ngang hàng với thư mục lib
tree
.
├── lib
│ └── simple_banana.rb
└── simple_banana.gemspec
Bây giờ, chúng ta sẽ viết code demo cho Gem của chúng ta. Bên trong file lib/simple_banana
:
class SimpleBanana
def self.hi language = "english"
puts "Hello world!"
end
end
Trong file .gemspec sẽ định nghĩa các thuộc tính của gem, tác giả, cũng như version, description, và các files được load ... Đó cũng là một interface đến RubyGems.org. Tất cả các thông tin bạn thấy ở trang gem đều được định nghĩa từ file .gemspec này.
Gem::Specification.new do |s|
s.name = 'simple_banana'
s.version = '0.0.0'
s.date = '2017-02-01'
s.summary = "banana"
s.description = "A simple hello world gem"
s.authors = ["banana"]
s.email = 'your_mail@something.com'
s.files = ["lib/simple_banana.rb", "lib/simple_banana/translator.rb"]
s.homepage =
'http://rubygems.org/gems/simple_banana'
s.license = 'MIT'
end
*description có thể dài hơn đoạn text mà bạn thấy trong ví dụ của tôi, miễn là nó có thể match với điều kiện
/^== [A-Z]/
sau đó, description sẽ được chạy thông qua RDoc’s markup formatter
để hiển thị trên trang RubyGems web site.
*
Có phải bạn thấy rất quen thuộc? gemspec này cũng như Ruby, bạn có thể thay đổi wrap scripts để tạo các file names, và còn có cả version number. Có rất nhiều fields được include trong file .gemspec này, bạn có thể xem cụ thể tại reference của guides ruby gems.
Sau khi tạo một gem, ta có thể build gem từ đó. Và sau đó là install gem này để test các chức năng mà mình vừa viết (yaoming)
gem build simple_banana.gemspec
Successfully built RubyGem
Name: simple_banana
Version: 0.0.0
File: simple_banana-0.0.0.gem
gem install ./simple_banana-0.0.0.gem
Successfully installed simple_banana-0.0.0
Parsing documentation for simple_banana-0.0.0
Installing ri documentation for simple_banana-0.0.0
Done installing documentation for simple_banana after 0 seconds
1 gem installed
Có thể install gần giống như Gem "xịn" rồi ấy nhỉ!
Đương nhiên, các smoke test vẫn chưa xong, bây giờ bạn có thể require gem và test sản phẩm của mình được rồi. Ở đây mình test trên rails console
require 'simple_banana'
=> true
2.3.0 :002 > SimpleBanana.hi
Hello world!
Hmm, có vẻ ra sản phẩm rồi đấy. Một chú ý nhỏ khi bạn sử dụng bản Ruby trước 1.9.2 thì phải start session với irb -rubygems
hoặc là require ruby gems sau khi launch irb
.
Bây giờ bạn có thể chia sẻ sản phẩm Gem của bạn với cộng đồng Ruby.
curl -u bapboy18 https://rubygems.org/api/v1/api_key.yaml > ~/.gem/credentials; chmod 0600 ~/.gem/credentials
Enter host password for user 'bapboy18':
Push gem lên rubygems:
gem push simple_banana-0.0.0.gem
Pushing gem to https://rubygems.org...
Successfully registered gem: simple_banana (0.0.0)
Ở bước này, có thể bạn cần phải đăng ký/ đăng nhập tài khoản trên Ruby gem
Đây là Gem của tôi
Chỉ trong một khoảng thời gian ngắn sau khi Gem được đẩy lên (dưới 1 phút) thì Gem này có thể được cài đặt từ bất kỳ ai. Bạn có thể xem simple Gem mà tôi vừa tạo ở đây
gem list -r simple_banana
*** REMOTE GEMS ***
simple_banana (0.0.0)
Cài thử dùng luôn (yaoming)
gem install simple_banana
Successfully installed simple_banana-0.0.0
Parsing documentation for simple_banana-0.0.0
Done installing documentation for simple_banana after 0 seconds
1 gem installed
Cài được như Gem "hịn" này! Test luôn:
irb -Ilib -rsimple_banana
2.3.0 :001 > SimpleBanana.hi "english"
=> "hello world"
2.3.0 :002 > SimpleBanana.hi "spanish"
=> "hola mundo"
Chạy có vẻ đúng chức năng rồi đấy. Việc chia sẻ code với Ruby và RubyGems khá dễ dàng nhỉ!
REQUIRING MORE FILES
Khi mở rộng chức năng Gem thì việc dồn tất cả vào 1 files quả là điều rất bất tiện.
Phần tiếp theo đây, tôi sẽ đề cập đến phần mở rộng số lượng files, cũng như chức năng của Gem như thế nào.
Giả sử ta có thêm chức năng muốn thêm trong file simple_banana.rb
class SimpleBanana
def self.hi language = "english"
translator = Translator.new(language)
translator.hi
end
end
class SimpleBanana::Translator
def initialize(language)
@language = language
end
def hi
case @language
when "spanish"
"hola mundo"
else
"hello world"
end
end
end
Có vẻ quá nhiều code trong cùng một file, điều này làm code trở nên thật sự rối khi bạn có nhiều code hơn nữa.
Bây giờ ta sẽ tách class Translator thành một file riêng. Như đã đề cập từ trước, file root có trách nhiệm load code cho gem, các file code khác sẽ được đặt trong thư mục có cùng tên với file root này. Ở đây, tôi đặt là thư mục simple_banana
, dĩ nhiên là bên trong thư mục lib.
tree
.
├── lib
│ ├── simple_banana
│ │ └── translator.rb
│ └── simple_banana.rb
Bây giờ class Translator ở trong thư mục lib/simple_banana, và code trong file translator.rb
này chính là phần mở rộng:
class SimpleBanana::Translator
def initialize(language)
@language = language
end
def hi
case @language
when "spanish"
"hola mundo"
else
"hello world"
end
end
end
Đặt thêm dòng code trong file simple_banana.rb để nó có thể load code trong phần vừa mở rộng:
class SimpleBanana
def self.hi language = "english"
translator = Translator.new(language)
translator.hi
end
end
require "simple_banana/translator"
Và trong file .gemspec không quên khai báo các file mà mình đã tạo:
Gem::Specification.new do |s|
...
s.files = ["lib/simple_banana.rb", "lib/simple_banana/translator.rb"]
...
end
Kết quả khi test trên rails console:
irb -Ilib -rsimple_banana
2.3.0 :001 > SimpleBanana.hi
=> "hello world"
2.3.0 :002 > SimpleBanana.hi "spanish"
=> "hola mundo"
Tôi muốn nhấn mạnh, command có flag -Ilib
. Thông thường, RubyGems includes thư mục lib cho bạn, và người dùng (end users) cũng không cần phải quá quan tâm về flag này. Tuy nhiên, nếu như bạn chạy dòng lệnh bên ngoài thư mục chứa Gem, thì bạn cần phải configure những thứ này. Nó có thể thao tác với $LOAD_PATH từ bên trong bản thân của code. Tuy nhiên đó không phải là patterns trong hầu hết các trường hợp. Có rất nhiều các patterns không tốt (và cả patterns tốt nữa) cho gems, được giải thích ở this guides.
Nếu bạn thêm vào các files vào Gem của bạn, thì hãy nhớ khai báo chúng trong trong files của gemspec trước khi push chúng lên như là một new gem. Hoặc có thể khai báo để nó có thể nhận dạng một cách tự động như just a dynamic gemspec.
Việc thêm nhiều thư mục và thêm nhiều code khi xây dựng Gem cũng theo một quy trình giống nhau. Hãy chia nhỏ các file của bạn khi nó có ý nghĩa. Hãy sắp xếp code của bạn thật khoa học để không bị rối mắt khi nhìn vào đống code đó
ADDING AN EXECUTABLE
mkdir bin
touch bin/simple_banana
chmod a+x bin/simple_banana
ruby -Ilib ./bin/simple_banana
hello world
ruby -Ilib ./bin/simple_banana spanish
hola mundo
head -4 simple_banana.gemspec
Gem::Specification.new do |s|
s.name = 'simple_banana'
s.version = '0.0.1'
s.executables << 'simple_banana'
WRITING TESTS
Testing Gem là một phần vô cùng quan trọng. Không chỉ giúp bạn chắc rằng code của bạn hoạt động, testing còn giúp người khác biết rằng Gem của bạn đang làm công việc gì. Khi đánh giá một Gem, các nhà thẩm định có xu hướng xem bộ solid test có tốt không (hoặc có sai sót gì không), là một trong những nguyên nhân để code của bạn được tin tưởng.
Gem supports việc thêm vào các test files vào trong packages của chính nó để test có thể chạy khi một gem được download.
Tóm lại là: TEST YOUR GEM! Please!
Tạo Rakefile và thư mục test
tree
.
├── bin
│ └── simple_banana
├── lib
│ ├── simple_banana
│ │ └── translator.rb
│ └── simple_banana.rb
├── Rakefile
├── simple_banana-0.0.0.gem
├── simple_banana-0.0.1.gem
├── simple_banana.gemspec
└── test
└── test_simple_banana.rb
Nội dung Rakefile:
require 'rake/testtask'
Rake::TestTask.new do |t|
t.libs << 'test'
end
desc "Run tests"
task :default => :test
Code của file test:
require 'minitest/autorun'
require 'simple_banana'
class SimpleBananaTest < Minitest::Test
def test_english_hello
assert_equal "hello world",
SimpleBanana.hi("english")
end
def test_any_hello
assert_equal "hello world",
SimpleBanana.hi("ruby")
end
def test_spanish_hello
assert_equal "hola mundo",
SimpleBanana.hi("spanish")
end
end
Cuối cùng là run test:
rake test
Run options: --seed 24756
# Running:
...
Finished in 0.001203s, 2493.7283 runs/s, 2493.7283 assertions/s.
3 runs, 3 assertions, 0 failures, 0 errors, 0 skips
Bây giờ, bạn có thể build lại Gem và push lên Ruby Gemspec - nhớ thay đổi version của gem để build nhé. Các bạn có thể tham khảo simple_banana của mình tại Ruby gem simple_banana, Github simple_banana
Chúc các bạn có nhiều Gem hữu ích do chính tay mình làm ra!
Tham khảo Ruby Gem
All rights reserved