{"id":4797,"date":"2011-02-03T23:44:13","date_gmt":"2011-02-03T15:44:13","guid":{"rendered":"http:\/\/ihower.tw\/blog\/?p=4797"},"modified":"2011-11-16T21:04:28","modified_gmt":"2011-11-16T13:04:28","slug":"designing-ruby-apis-talks","status":"publish","type":"post","link":"https:\/\/ihower.tw\/blog\/4797-designing-ruby-apis-talks","title":{"rendered":"\u5982\u4f55\u8a2d\u8a08\u51fa\u6f02\u4eae\u7684 Ruby APIs [\u6f14\u8b1b\u6458\u8981]"},"content":{"rendered":"<p>\u8a3b\uff1a\u9019\u7bc7\u662f\u597d\u5e7e\u500b\u6708\u524d\u61c9 <a href=\"http:\/\/www.infoq.com\/cn\/\">InfoQ China<\/a> \u7684\u9080\u7a3f\u6240\u5beb\uff0c\u4e0d\u904e\u770b\u8d77\u4f86\u662f\u6c92\u6709\u6d3e\u4e0a\u7528\u5834 (\u5927\u6982\u662f\u7a0b\u5f0f\u78bc\u592a\u591a\u4e86)\u3002\u5c0d\u6211\u4f86\u8aaa\uff0c\u5beb\u597d\u7684\u7a3f\u5b50\u6c92\u516c\u958b\u51fa\u4f86\u592a\u6d6a\u8cbb\u4e86\uff0c\u6240\u4ee5\u5c31\u8cbc\u51fa\u4f86\u5427\u3002<\/p>\n<p>\u672c\u6587\u6458\u9304\u4e86\u6211\u53bb\u5e74\u5728 <a href=\"http:\/\/rubyconfchina.org\/talks\/6-designing-beautiful-ruby-apis\">RubyConf China 2010<\/a> \u4e2d\u7684\u6f14\u8b1b\u5167\u5bb9\uff0c\u5305\u542b\u4e86 Ruby APIs \u7684\u5341\u500b\u8a2d\u8a08\u6280\u5de7\u4ee5\u53ca\u7bc4\u4f8b\u7a0b\u5f0f\uff0c\u540c\u6642\u4e5f\u4ecb\u7d39\u4e86 Ruby \u7684\u7269\u4ef6\u6a21\u7d44\u53ca\u5143\u7de8\u7a0b(Meta-programming)\u3002\u5b8c\u6574\u7684\u7bc4\u4f8b\u7a0b\u5f0f\u8acb\u642d\u914d <a href=\"http:\/\/www.slideshare.net\/ihower\/designing-ruby-apis\">\u6295\u5f71\u7247<\/a> \u670d\u7528\u3002<\/p>\n<p> <!--more--><br \/>\n\u9996\u5148\uff0c\u8b93\u6211\u5011\u7c21\u55ae\u5b9a\u7fa9\u4e00\u4e0b\u4ec0\u9ebc\u662f\u6f02\u4eae\u7684 APIs:<\/p>\n<p>* \u95b1\u8b80\u6027\uff1aAPI \u5bb9\u6613\u7406\u89e3<br \/>\n* \u7de8\u5beb\u6548\u7387\uff1aAPI \u5bb9\u6613\u4f7f\u7528<br \/>\n* \u64f4\u5c55\u6027\uff1aAPI \u5bb9\u6613\u64f4\u5145<\/p>\n<h3>1.Argument Processing<\/h3>\n<p>Ruby \u4f7f\u7528\u4e86 Symbols \u548c Hash \u4f86\u9054\u5230\u865b\u64ec\u95dc\u9375\u5b57\u53c3\u6578(Pseudo-Keyword Arguments)\u3002\u9019\u7a2e\u6280\u5de7\u88ab\u5ee3\u6cdb\u61c9\u7528\u5728 Ruby \u7684\u51fd\u5f0f\u5eab\u548c Rails \u4e2d\uff0c\u589e\u52a0\u4e86\u95b1\u8b80\u6027\uff0c\u4e5f\u5f88\u5bb9\u6613\u4f7f\u7528\u3002<\/p>\n<pre>\r\n<code>\r\n    def blah(options)\r\n      puts options[:foo]\r\n      puts options[:bar]\r\n    end\r\n\r\n    blah(:foo => \"test\", :bar => \"test\")\r\n<\/code>\r\n<\/pre>\n<p>Ruby \u4e5f\u53ef\u4ee5\u5c07\u53c3\u6578\u5217\u7576\u6210\u9663\u5217\u4f7f\u7528\uff1a<\/p>\n<pre>\r\n<code>\r\ndef sum(*args)\r\n    puts args[0]\r\n    puts args[1]\r\n    puts args[2]\r\n    puts args[3]\r\nend\r\n\r\nsum(1,2,3)\r\n<\/code>\r\n<\/pre>\n<p>\u5982\u6b64\u5c31\u53ef\u4ee5\u8a2d\u8a08\u51fa\u4e0d\u56fa\u5b9a\u53c3\u6578\u5217\u3001\u5341\u5206\u5f48\u6027\u7684 API\u3002\u985e\u4f3c\u65bc C++ \u7684 function overloading\u3002\u5728 Rails \u4e2d\u4e5f\u5341\u5206\u5e38\u898b\u9019\u6a23\u7684 API \u8a2d\u8a08\uff0c\u4f8b\u5982 link_to \u5c31\u652f\u63f4\u4e86\u5169\u7a2e\u7528\u6cd5\uff1a<\/p>\n<pre>\r\n<code>\r\n    # USAGE-1 without block\r\n    &lt;% link_to 'Posts list', posts_path, :class => 'posts' %>\r\n\r\n    # USAGE-2 with block\r\n    &lt;% link_to posts_path, :class => 'posts' do %>\r\n      Posts list\r\n    &lt;% end %>\r\n<\/code>\r\n<\/pre>\n<p>\u642d\u914d\u865b\u64ec\u95dc\u9375\u5b57\u53c3\u6578\u4f7f\u7528\u7684\u8a71\uff0c\u53ef\u4ee5\u53c3\u8003 ActiveSupport#extract_options! \u9019\u500b\u5c0f\u6280\u5de7\u53d6\u51fa Hash \u503c\u3002<\/p>\n<h3>2. Code Blocks<\/h3>\n<p>\u7a0b\u5f0f\u5340\u584a(Block)\u662f Ruby \u6700\u91cd\u8981\u7684\u7279\u8272\uff0c\u9664\u4e86\u62ff\u4f86\u505a\u8fed\u4ee3(Iteration)\u4e4b\u5916\uff0c\u4e5f\u53ef\u4ee5\u5305\u88dd\u524d\u5f8c\u7f6e\u8655\u7406(pre- and Post-processing)\uff0c\u4e00\u500b\u6700\u57fa\u672c\u7684\u4f8b\u5b50\u5c31\u662f\u958b\u6a94\u4e86\uff0c\u4e00\u822c\u7a0b\u5e8f\u5f0f\u7684\u5beb\u6cd5\u5982\u4e0b\uff1a<\/p>\n<pre>\r\n<code>\r\n    f = File.open(\"myfile.txt\", 'w')  \r\n    f.write(\"Lorem ipsum dolor sit amet\")\r\n    f.write(\"Lorem ipsum dolor sit amet\")  \r\n    f.close\r\n<\/code>\r\n<\/pre>\n<p>\u4f7f\u7528 Block \u4e4b\u5f8c\uff0c\u6211\u5011\u53ef\u4ee5\u5c07 f.close \u5305\u88dd\u8d77\u4f86\uff0c\u4e0d\u9700\u8981\u660e\u78ba\u547c\u53eb\u3002\u53ea\u8981\u7a0b\u5f0f\u5340\u584a\u7d50\u675f\uff0cRuby \u5c31\u6703\u81ea\u52d5\u95dc\u6a94\u3002\u7a0b\u5f0f\u4e00\u4f86\u56e0\u70ba\u7e2e\u6392\u8b8a\u5f97\u6709\u7d50\u69cb\uff0c\u4e8c\u4f86\u4e5f\u78ba\u5b9a\u6a94\u6848\u4e00\u5b9a\u6703\u95dc\u9589(\u4e0d\u7136\u5c31\u8a9e\u6cd5\u932f\u8aa4\u4e86)<\/p>\n<pre>\r\n<code>\r\n    # using block\r\n    File.open(\"myfile.txt\", 'w') do |f|\r\n      f.write(\"Lorem ipsum dolor sit amet\")\r\n      f.write(\"Lorem ipsum dolor sit amet\")  \r\n    end\r\n<\/code>\r\n<\/pre>\n<p>\u53e6\u4e00\u500b\u7a0b\u5f0f\u5340\u584a\u7684\u6280\u6cd5\uff0c\u662f\u7528\u4f86\u7576\u505a\u56de\u547c(Dynamic Callbacks)\u3002\u5728 Ruby \u4e2d\uff0c\u7a0b\u5f0f\u5340\u584a\u4e5f\u662f\u7269\u4ef6\uff0c\u65bc\u662f\u6211\u5011\u53ef\u4ee5\u5c07\u7a0b\u5f0f\u5340\u584a\u5982\u900f\u904e&#8221;\u8a3b\u518a&#8221;\u7684\u65b9\u5f0f\u5148\u5132\u5b58\u4e0b\u4f86\uff0c\u4e4b\u5f8c\u518d\u4f9d\u7167\u9700\u6c42\u627e\u51fa\u4f86\u57f7\u884c\u3002\u4f8b\u5982\u5728 <a href=\"http:\/\/www.sinatrarb.com\/\">Sinatra<\/a> \u7a0b\u5f0f\u4e2d\uff1a<\/p>\n<pre>\r\n<code>\r\n  get '\/posts' do\r\n    #.. show something ..\r\n  end\r\n\r\n  post '\/posts' do\r\n    #.. create something ..\r\n  end\r\n<\/code>\r\n<\/pre>\n<p>\u6211\u5011&#8221;\u8a3b\u518a&#8221;\u4e86\u5169\u500b\u56de\u547c\uff1a\u4e00\u662f\u7576\u700f\u89bd\u5668\u9001\u51fa GET &#8216;\/posts&#8217; \u6642\uff0c\u6703\u57f7\u884c show something \u7684\u7a0b\u5f0f\u5340\u584a\uff0c\u4e8c\u662f POST &#8216;\/posts&#8217; \u6642\u3002<\/p>\n<h3>3. Module<\/h3>\n<p>\u6a21\u7d44(Module)\u662f Ruby \u7528\u4f86\u89e3\u6c7a\u591a\u91cd\u7e7c\u627f\u554f\u984c\u7684\u8a2d\u8a08\u3002\u5176\u4e2d\u6709\u4e00\u62db Dual interface \u503c\u5f97\u4e00\u63d0\uff1a<\/p>\n<pre>\r\n<code>\r\n    module Logger\r\n        extend self\r\n       \r\n        def log(message)\r\n            $stdout.puts \"#{message} at #{Time.now}\"\r\n        end\r\n    end\r\n    Logger.log(\"test\") # as Logger\u2019s class method\r\n\r\n    class MyClass\r\n        include Logger\r\n    end\r\n    MyClass.new.log(\"test\") # as MyClass\u2019s instance method\r\n<\/code>\r\n<\/pre>\n<p>Ruby \u7684 extend \u4f5c\u7528\u662f\u5c07\u6a21\u7d44\u6df7\u5165(mix-in)\u9032\u55ae\u4ef6\u985e\u5225(singleton class)\uff0c\u65bc\u662f log \u9019\u500b\u65b9\u6cd5\u9664\u4e86\u53ef\u4ee5\u50cf\u4e00\u822c\u7684\u6a21\u7d44\u88ab\u6df7\u5165 MyClass \u4e2d\u4f7f\u7528\uff0c\u4e5f\u53ef\u4ee5\u76f4\u63a5\u7528 Logger.log \u547c\u53eb\u3002<\/p>\n<p>\u8981\u5c07 Ruby \u6a21\u7d44\u7684\u6df7\u5165\u6210\u985e\u5225\u65b9\u6cd5(class method)\uff0c\u4e5f\u6709\u4e00\u4e9b\u5e38\u898b\u7684 pattern \u6a21\u5f0f\uff0c\u53ef\u4ee5\u5c07\u6a21\u7d44\u8a2d\u8a08\u53ef\u4ee5\u540c\u6642\u6df7\u5165\u5be6\u4f8b\u65b9\u6cd5(instance method)\u548c\u985e\u5225\u65b9\u6cd5\uff0c\u8acb\u53c3\u95b1\u6295\u5f71\u7247\u7bc4\u4f8b\u3002\u9019\u5728\u64b0\u5beb Rails plugin \u6642\u975e\u5e38\u5e38\u7528\u3002<\/p>\n<h3>4. method_missing?<\/h3>\n<p>Ruby \u7684 Missing \u65b9\u6cd5\u662f\u7576\u4f60\u547c\u53eb\u4e00\u500b\u4e0d\u5b58\u5728\u7684\u65b9\u6cd5\u6642\uff0cRuby \u4ecd\u7136\u6709\u8fa6\u6cd5\u8655\u7406\u3002\u5b83\u6703\u6539\u547c\u53eb method_missing \u9019\u500b\u65b9\u6cd5\uff0c\u4e26\u628a\u9019\u500b\u4e0d\u5b58\u5728\u7684\u65b9\u6cd5\u540d\u7a31\u50b3\u9032\u53bb\u7576\u505a\u53c3\u6578\u3002\u9019\u500b\u6280\u5de7\u5728 Rails \u7684 ActiveRecord \u4e2d\u62ff\u4f86\u4f7f\u7528\uff1a<\/p>\n<pre>\r\n<code>\r\n    class Person < ActiveRecord::Base\r\n    end\r\n\r\n    p1 = Person.find_by_name(\"ihower\")\r\n    p2 = Person.find_by_name_and_email(\"ihower\", \"ihower@gmail.com\")\r\n<\/code>\r\n<\/pre>\n<p>\u5176\u4e2d find_by_name \u548c find_by_email \u5c31\u662f\u9019\u6a23\u7684\u65b9\u6cd5\u3002\u4e0d\u904e\u9019\u500b\u6280\u5de7\u4e0d\u662f\u842c\u80fd\u4e39\uff0c\u5b83\u7684\u57f7\u884c\u6548\u7387\u4e26\u4e0d\u597d\uff0c\u6240\u4ee5\u53ea\u9069\u5408\u7528\u5728\u4f60\u6c92\u8fa6\u6cd5\u9810\u5148\u77e5\u9053\u65b9\u6cd5\u540d\u7a31\u7684\u60c5\u6cc1\u4e0b\u3002\u4e0d\u904e\u4e5f\u4e0d\u662f\u6c92\u6709\u88dc\u6551\u4e4b\u9053\uff0c\u5982\u679c\u540c\u6a23\u7684\u65b9\u6cd5\u9084\u6703\u7e7c\u7e8c\u547c\u53eb\u5230\uff0c\u4f60\u53ef\u4ee5\u5728 method_missing \u4e4b\u4e2d\u7528 define_method \u6216 class_eval \u52d5\u614b\u5b9a\u7fa9\u6b64\u65b9\u6cd5\uff0c\u90a3\u9ebc\u4e0b\u6b21\u547c\u53eb\u5c31\u4e0d\u6703\u9032\u4f86 method_missing\uff0c\u9032\u800c\u7372\u5f97\u6548\u80fd\u7684\u6539\u5584\u3002\u4e8b\u5be6\u4e0a\uff0cActiveRecord::Base \u7684 method_missing \u5c31\u662f\u9019\u9ebc\u505a\u7684\u3002(\u611f\u8b1d BigCat \u7559\u8a00\u63d0\u9192\u6211\u6709\u6b64\u88dc\u6551\u4e4b\u9053)<\/p>\n<p>\u53e6\u4e00\u500b Missing \u65b9\u6cd5\u7684\u7d55\u5999 API \u8a2d\u8a08\uff0c\u662f\u62ff\u4f86\u69cb\u5efa XML \u6587\u4ef6\uff1a<\/p>\n<pre>\r\n<code>\r\n    builder = Builder::XmlMarkup.new(:target=>STDOUT, :indent=>2)\r\n    builder.person do |b| \r\n      b.name(\"Jim\")\r\n      b.phone(\"555-1234\")\r\n      b.address(\"Taipei, Taiwan\")\r\n    end\r\n\r\n    # &lt;person>\r\n    #   &lt;name>Jim&lt;\/name>\r\n    #   &lt;phone>555-1234&lt;\/phone>\r\n    #   &lt;address>Taipei, Taiwan&lt;\/address>\r\n    # &lt;\/person>\r\n<\/code>\r\n<\/pre>\n<p>\u642d\u914d\u4e86\u5340\u584a\u529f\u80fd\uff0c\u5c31\u80fd\u7528 Ruby \u8a9e\u6cd5\u4f86\u5beb XML\uff0c\u975e\u5e38\u53b2\u5bb3\u3002<\/p>\n<h3>5. const_missing<\/h3>\n<p>\u9664\u4e86 method_missing\uff0cRuby \u4e5f\u6709 const_missing\u3002\u9867\u540d\u601d\u7fa9\u5c31\u662f\u627e\u4e0d\u5230\u6b64\u5e38\u6578\u6642\uff0c\u6703\u547c\u53eb\u4e00\u500b\u53eb\u505a const_missing \u7684\u65b9\u6cd5\u3002\u73fe\u5be6\u4e2d\u7684\u4f8b\u5b50\u6709 Rails \u7684 ActiveSupport::Dependencies\uff0c\u5b83\u5e6b\u52a9\u6211\u5011\u4e0d\u9700\u8981\u5148\u8f09\u5165\u6240\u6709\u985e\u5225\u6a94\u6848\uff0c\u800c\u662f\u7576 Rails \u78b0\u5230\u4e00\u500b\u9084\u4e0d\u8a8d\u8b58\u7684\u5e38\u6578\u6642\uff0c\u5b83\u6703\u81ea\u52d5\u6839\u64da\u6163\u4f8b\uff0c\u627e\u5230\u8a72\u6a94\u6848\u8f09\u5165\u3002<\/p>\n<p>\u6211\u5011\u4e5f\u53ef\u4ee5\u5229\u7528\u9019\u500b\u6280\u5de7\uff0c\u91dd\u5c0d\u7279\u5b9a\u7684\u5e38\u6578\u898f\u5247\u4f86\u8655\u7406\u3002\u4f8b\u5982\u4ee5\u4e0b\u7684\u7a0b\u5f0f\u6703\u81ea\u52d5\u5c07 U \u958b\u982d\u7684\u5e38\u6578\uff0c\u81ea\u52d5\u8f49\u8b6f\u6210 Unicode \u78bc\uff1a<\/p>\n<pre>\r\n<code>\r\n    class Module\r\n        original_c_m = instance_method(:const_missing)\r\n    \r\n        define_method(:const_missing) do |name|\r\n            if name.to_s =~ \/^U([0-9a-fA-F]{4})$\/\r\n                [$1.to_i(16)].pack(\"U*\")\r\n            else\r\n                original_c_m.bind(self).call(name)\r\n            end\r\n        end\r\n    end\r\n\r\n    puts U0123 # \u0123\r\n    puts U9999 # \u9999\r\n<\/code>\r\n<\/pre>\n<h3>6. Methods chaining<\/h3>\n<p>\u65b9\u6cd5\u4e32\u63a5\u662f\u4e00\u500b\u5f88\u5e38\u898b\u7684 API \u8a2d\u8a08\uff0c\u900f\u904e\u5c07\u65b9\u6cd5\u7684\u56de\u50b3\u503c\u8a2d\u6210 self\uff0c\u6211\u5011\u5c31\u53ef\u4ee5\u4e32\u63a5\u8d77\u4f86\u3002\u4f8b\u5982\uff1a<\/p>\n<pre>\r\n<code>\r\n    [1,1,2,3,3,4,5].uniq!.reject!{ |i| i%2 == 0 }.reverse\r\n    # 5,3,1\r\n<\/code>\r\n<\/pre>\n<h3>7. Core extension<\/h3>\n<p>Ruby \u7684\u985e\u5225\u662f\u958b\u653e\u7684\uff0c\u53ef\u4ee5\u96a8\u6642\u6253\u958b\u5b83\u65b0\u589e\u4e00\u9ede\u7a0b\u5f0f\u6216\u662f\u4fee\u6539\u3002\u5373\u4f7f\u662f\u6838\u5fc3\u985e\u5225\u5982 Fixnum \u6216\u662f Object(\u9019\u662f\u6240\u6709\u985e\u5225\u7684\u7236\u985e\u5225) \u90fd\u4e00\u6a23\u3002\u4f8b\u5982 Rails \u5c31\u5b9a\u7fa9\u4e86\u4e00\u4e9b\u6642\u9593\u65b9\u6cd5\u5728 Fixnum \u88e1\uff1a<\/p>\n<pre>\r\n<code>\r\n    class Fixnum\r\n      def hours\r\n        self * 3600 # \u4e00\u5c0f\u6642\u6709\u591a\u5c11\u79d2\r\n      end\r\n      alias hour hours\r\n    end\r\n  \r\n    Time.now + 14.hours \r\n<\/code>\r\n<\/pre>\n<h3>Ruby \u7684\u7269\u4ef6\u6a21\u578b\u8207\u5143\u7de8\u7a0b(Meta-programming)<\/h3>\n<p>\u5728 Ruby \u4e2d\uff0c\u6240\u6709\u6771\u897f\u90fd\u662f\u7269\u4ef6\u3002\u751a\u81f3\u5305\u62ec\u985e\u5225(class)\u672c\u8eab\u4e5f\u662f\u7269\u4ef6\u3002\u9019\u500b\u985e\u5225\u7269\u4ef6(class object)\u662f\u4e00\u500b\u53eb\u505a Class \u7684\u985e\u5225\u6240\u5be6\u4f8b\u51fa\u4f86\u7684\u7269\u4ef6\u3002\u800c\u6240\u6709\u7684\u7269\u4ef6(\u7576\u7136\u4e5f\u5305\u62ec\u985e\u5225\u7269\u4ef6)\uff0c\u90fd\u6709\u4e00\u500b metaclass (\u53c8\u53eb\u505a singleton, eigenclass, ghost class, virtual class \u7b49\u540d\u5b57)\u3002\u5b9a\u7fa9\u5728 metaclass \u88e1\u7684\u65b9\u6cd5\uff0c\u53ea\u6709\u8a72\u7269\u4ef6\u80fd\u5920\u4f7f\u7528\uff0c\u4e5f\u5c31\u662f singleton method (\u55ae\u4ef6\u65b9\u6cd5)\uff0c\u53ea\u6709\u8a72\u7269\u4ef6\u624d\u6709\u7684\u65b9\u6cd5\u3002<\/p>\n<p>\u4e86\u89e3\u4ec0\u9ebc\u662f metaclass \u662f Ruby \u5143\u7de8\u7a0b\u7684\u4e00\u500b\u91cd\u8981\u524d\u63d0\u77e5\u8b58\u3002Ruby \u5143\u7de8\u7a0b\u6700\u5e38\u7528\u7684\u7528\u9014\uff0c\u5c31\u662f\u56e0\u61c9\u9700\u6c42\u53ef\u4ee5\u52d5\u614b\u5730\u5b9a\u7fa9\u65b9\u6cd5\uff0c\u4f8b\u5982\u5728 Rails ActiveRecord \u4e2d\u5e38\u898b\u7684 Class Macro \u61c9\u7528\u3002<\/p>\n<p>\u8981\u80fd\u96a8\u5fc3\u6240\u6b32\u52d5\u614b\u5b9a\u7fa9\u65b9\u6cd5\u7684\u95dc\u9375\u91cd\u9ede\uff0c\u5c31\u662f variable scope (\u8b8a\u6578\u7684\u4f5c\u7528\u57df) \u4e86\u3002\u4f8b\u5982\u4ee5\u4e0b\u6211\u5011\u900f\u904e class_eval \u548c define_method \u5e6b String \u5b9a\u7fa9\u4e86\u4e00\u500b say \u65b9\u6cd5\uff0c\u6ce8\u610f\u5230\u6574\u500b variable scope \u90fd\u662f\u901a\u900f\u7684\uff0c\u6c92\u6709\u5efa\u7acb\u65b0\u7684 scope\uff1a<\/p>\n<pre>\r\n<code>\r\n    name = \"say\"\r\n    var = \"it\u2019s awesome\"\r\n\r\n    String.class_eval do\r\n      define_method(name) do\r\n        puts var\r\n      end  \r\n    end\r\n\r\n    \"ihower\".say # it\u2019s awesome\r\n<\/code>\r\n<\/pre>\n<p>class_eval \u53ef\u4ee5\u8b93\u6211\u5011\u6539\u8b8a method definition \u5340\u57df(\u53c8\u53eb\u505a current class)\u3002\u9664\u4e86\u672c\u6295\u5f71\u7247\uff0c\u5efa\u8b70\u53ef\u4ee5\u95b1\u8b80 <a href=\"http:\/\/yehudakatz.com\/2009\/11\/15\/metaprogramming-in-ruby-its-all-about-the-self\/\">Metaprogramming in Ruby: It\u2019s Allhe Self<\/a> \u548c <a href=\"http:\/\/yugui.jp\/articles\/846\">Three implicit contexts in Ruby<\/a> \u9019\u5169\u7bc7\u6587\u7ae0\u6df1\u5165\u4e86\u89e3 self \u548c current class\u3002<\/p>\n<h3>8. Class Macro (Ruby\u2019s declarative style)<\/h3>\n<p>Class Macro \u662f Ruby Meta-programming \u975e\u5e38\u91cd\u8981\u7684\u4e00\u500b\u61c9\u7528\uff0c\u4f8b\u5982\u5728 Rails ActiveRecord \u4e2d\uff1a<\/p>\n<pre>\r\n<code>\r\n    class User < ActiveRecord::Base\r\n  \r\n      validates_presence_of     :login\r\n      validates_length_of       :login,    :within => 3..40\r\n      validates_presence_of     :email\r\n  \r\n      belongs_to :group\r\n      has_many :posts\r\n  \r\n    end\r\n<\/code>\r\n<\/pre>\n<p>\u90a3\u8981\u5982\u4f55\u5be6\u4f5c\u81ea\u5df1\u7684 Class Macro \u5462? \u4ee5\u5e38\u898b\u7684 Memorize \u70ba\u4f8b\uff1a<\/p>\n<pre>\r\n<code>\r\nclass Account\r\n  def calculate\r\n    @calculate ||= begin\r\n      sleep 10 # expensive calculation\r\n      5\r\n    end\r\n  end\r\nend\r\n\r\na = Account.new\r\na.caculate # need waiting 10s to get 5\r\na.caculate # 5\r\n<\/code>\r\n<\/pre>\n<p>\u6211\u5011\u5e0c\u671b\u6539\u5beb\u6210<\/p>\n<pre>\r\n<code>\r\nclass Account\r\n  def calculate\r\n      sleep 2 # expensive calculation\r\n      5\r\n  end\r\n\r\n  memoize :calculate\r\nend\r\n<\/code>\r\n<\/pre>\n<p>\u5beb\u6cd5\u5982\u4e0b\uff1a<\/p>\n<pre>\r\n<code>\r\nclass Class\r\n  def memoize(name)\r\n    original_method = \"_original_#{name}\"    \r\n    alias_method :\"#{original_method}\", name\r\n    \r\n    define_method name do\r\n      cache = instance_variable_get(\"@#{name}\") \r\n      if cache\r\n        return cache\r\n      else\r\n        result = send(original_method) # Dynamic Dispatches\r\n        instance_variable_set(\"@#{name}\", result)\r\n        return result\r\n      end\r\n    end\r\n    \r\n  end\r\nend\r\n<\/code>\r\n<\/pre>\n<p>\u9019\u88e1\u6709\u5e7e\u500b\u5c0f\u91cd\u9ede\uff1a\u7b2c\u4e00\uff0c\u56e0\u70ba scope \u4e0d\u8b8a\u7684\u95dc\u4fc2\uff0c\u6211\u5011\u9700\u8981 instance_variable_get \u548c instance_variable_set \u4f86\u62ff\u5230\u7269\u4ef6\u8b8a\u6578\u3002\u7b2c\u4e8c\uff0c\u6211\u5011\u9700\u8981\u80fd\u5920\u4fdd\u7559\u820a\u65b9\u6cd5\u5df2\u5f85\u7a0d\u5f8c\u547c\u53eb\uff0c\u9019\u53c8\u6709\u5169\u7a2e\u4f5c\u6cd5\uff1a\u7b2c\u4e00\u7a2e\u6bd4\u8f03\u5e38\u898b\uff0c\u5982\u540c\u9019\u500b\u4f8b\u5b50\u4f7f\u7528 alias_method\u3002\u7b2c\u4e8c\u7a2e\u5247\u662f\u4f7f\u7528 method binding\uff0c\u5982 const_missing \u4e2d\u7684\u7bc4\u4f8b\u3002<\/p>\n<h3>9. instance_eval<\/h3>\n<p>\u7528 DSL \u7684\u8853\u8a9e\u4f86\u8aaa\uff0cinstance_eval \u53ef\u4ee5\u5e6b\u52a9\u6211\u5011\u5728 code block \u4e2d\u5efa\u7acb implicit context (\u96b1\u6027\u7684\u8a9e\u5883)\u3002\u4f8b\u5982\u4ee5\u4e0b\u662f\u4e00\u500b Rack \u7684\u5efa\u69cb\u65b9\u5f0f\uff0c\u6211\u5011\u5728 code block \u4e2d\u76f4\u63a5\u547c\u53eb use \u548c run\uff1a<\/p>\n<pre>\r\n<code>\r\n    Rack::Builder.new do\r\n      use Some::Middleware, param\r\n      use Some::Other::Middleware\r\n      run Application\r\n    end\r\n<\/code>\r\n<\/pre>\n<p>\u90a3\u5c31\u5982\u4f55\u81ea\u5df1\u5beb\u5462? \u7d50\u5408 instance_eval \u548c block variable \u5373\u53ef\uff1a<\/p>\n<pre>\r\n<code>\r\nclass Foo\r\n  attr_accessor :a,:b\r\n \r\n  def initialize(&block)\r\n    instance_eval &block\r\n  end\r\n  \r\n  def use(name)\r\n    # do some setup\r\n  end\r\nend\r\n\r\nbar = Foo.new do\r\n  self.a = 1\r\n  self.b = 2\r\n  use \"blah\"\r\n  use \"blahblah\"\r\nend\r\n<\/code>\r\n<\/pre>\n<h3>10. Class.new<\/h3>\n<p>\u6211\u5011\u5728 Ruby \u7269\u4ef6\u6a21\u578b\u6709\u63d0\u5230\uff0cRuby \u7684\u985e\u4e5f\u662f\u4e00\u500b\u7269\u4ef6\u3002\u6240\u4ee5\u6211\u5011\u4e5f\u53ef\u4ee5\u7528\u7522\u751f\u7269\u4ef6\u7684\u65b9\u5f0f\u4f86\u7522\u751f\u4e00\u500b\u533f\u540d\u7684\u985e\uff0c\u9019\u6a23\u7684\u65b9\u5f0f\u5c31\u4e0d\u6703\u7522\u751f\u65b0\u7684 scope\u3002\u4f8b\u5982\uff1a<\/p>\n<pre>\r\n<code>\r\n    var = \"it\u2019s awesome\"\r\n    klass = Class.new do\r\n        puts var\r\n        define_method :my_method do\r\n            puts var\r\n        end    \r\n    end\r\n\r\n    puts klass.new.my_method\r\n    # it\u2019s awesome\r\n    # it\u2019s awesome\r\n<\/code>\r\n<\/pre>\n<h3>\u7d50\u8ad6<\/h3>\n<p>\u9019\u88e1\u6211\u60f3\u53e6\u5916\u8b1b\u5169\u500b\u6545\u4e8b\uff0c\u7b2c\u4e00\u500b\u662f Jos\u00e9 Valim \u5728 Euruko 2010 \u4e0a\u7684\u6f14\u8b1b <a href=\"http:\/\/blog.plataformatec.com.br\/2010\/06\/dsl-or-nodsl-at-euruko-2010\/\">DSL or NoDSL<\/a>\u3002\u5728\u4ed6\u7684\u6f14\u8b1b\u4e4b\u4e2d\uff0c\u63d0\u5230\u8981\u4e0d\u8981\u628a API \u8a2d\u8a08\u6210 DSL \u5176\u5be6\u4e26\u4e0d\u662f\u91cd\u9ede\uff0c\u91cd\u9ede\u662f\u5982\u4f55\u8a2d\u8a08\u51fa\u4e00\u500b\u7c21\u55ae\u597d\u7528\u7684 API\uff0c\u9019\u624d\u662f\u6211\u5011\u7684\u76ee\u6a19\u3002\u4f8b\u5982\u4ed6\u81ea\u5df1\u66fe\u7d93\u8a2d\u8a08\u4e00\u500b ContactForm\uff0c\u539f\u672c\u7684\u8a2d\u8a08\u5927\u91cf\u4f7f\u7528 Class Macro \u5be6\u4f5c\uff0c\u96d6\u7136\u7b2c\u4e00\u773c\u770b\u8d77\u4f86\u5f88\u6f02\u4eae\uff0c\u4f46\u662f\u4e00\u65e6\u9700\u8981\u64f4\u5145\u6642\uff0c\u53cd\u800c\u8b8a\u5f97\u96e3\u4ee5\u7dad\u8b77\u3002<\/p>\n<p>\u53e6\u4e00\u500b\u6545\u4e8b\u5247\u662f Rails 2 \u5230 Rails 3 \u6709\u4e0d\u5c11\u7684 API \u6539\u8b8a\uff0c\u4f8b\u5982 Routes \u5e7e\u4e4e\u91cd\u65b0\u8a2d\u8a08\u904e\u4e86\uff0c\u5927\u91cf\u63a1\u7528 implicit context \u4f86\u964d\u4f4e\u7a0b\u5f0f\u7684\u53c3\u6578\u5217\u9577\u5ea6\u3002ActiveRecord \u65b0\u7684 API \u4e5f\u5927\u91cf\u63a1\u7528\u4e86 method chaining\u3002ActionMailer \u5bc4\u4fe1\u4e5f\u628a\u52d5\u614b\u65b9\u6cd5\u79fb\u9664\uff0c\u6539\u6210\u56de\u50b3 Mail \u7269\u4ef6\u3002\u9019\u4e9b API \u7684\u6539\u8b8a\u53ef\u4ee5\u8aaa\u662f\u4e00\u500b Ruby \u4e16\u4ee3\u7684\u8b8a\u9769\uff0c\u5c0d\u65bc API \u5982\u4f55\u8a2d\u8a08\u6709\u4e86\u65b0\u7684\u60f3\u6cd5\u3002\u7562\u7adf Rails \u662f Ruby \u958b\u6e90\u793e\u7fa4\u4e2d\u6700\u5927\u7684 codebase\uff0c\u6211\u5011\u53ef\u4ee5\u5f9e\u4ed6\u7684\u8a2d\u8a08\u7d93\u9a57\u4e2d\u5b78\u5230\u5f88\u591a\u3002<\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u8a3b\uff1a\u9019\u7bc7\u662f\u597d\u5e7e\u500b\u6708\u524d\u61c9 InfoQ China \u7684\u9080\u7a3f\u6240\u5beb\uff0c\u4e0d\u904e\u770b\u8d77\u4f86\u662f\u6c92\u6709\u6d3e\u4e0a\u7528\u5834 (\u5927\u6982\u662f\u7a0b\u5f0f\u78bc\u592a\u591a\u4e86)\u3002 &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/ihower.tw\/blog\/4797-designing-ruby-apis-talks\" class=\"more-link\">\u95b1\u8b80\u5168\u6587<span class=\"screen-reader-text\">\u3008\u5982\u4f55\u8a2d\u8a08\u51fa\u6f02\u4eae\u7684 Ruby APIs [\u6f14\u8b1b\u6458\u8981]\u3009<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[31],"tags":[],"class_list":["post-4797","post","type-post","status-publish","format-standard","hentry","category-ruby","entry"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p1q6tG-1fn","jetpack_sharing_enabled":true,"jetpack_likes_enabled":true,"_links":{"self":[{"href":"https:\/\/ihower.tw\/blog\/wp-json\/wp\/v2\/posts\/4797","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ihower.tw\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/ihower.tw\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/ihower.tw\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/ihower.tw\/blog\/wp-json\/wp\/v2\/comments?post=4797"}],"version-history":[{"count":24,"href":"https:\/\/ihower.tw\/blog\/wp-json\/wp\/v2\/posts\/4797\/revisions"}],"predecessor-version":[{"id":6257,"href":"https:\/\/ihower.tw\/blog\/wp-json\/wp\/v2\/posts\/4797\/revisions\/6257"}],"wp:attachment":[{"href":"https:\/\/ihower.tw\/blog\/wp-json\/wp\/v2\/media?parent=4797"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/ihower.tw\/blog\/wp-json\/wp\/v2\/categories?post=4797"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/ihower.tw\/blog\/wp-json\/wp\/v2\/tags?post=4797"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}