<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[钻石墓场]]></title><description><![CDATA[黑夜中会发光]]></description><link>https://loku.it/</link><image><url>https://loku.it/favicon.png</url><title>钻石墓场</title><link>https://loku.it/</link></image><generator>Ghost 3.40</generator><lastBuildDate>Tue, 21 Apr 2026 09:41:30 GMT</lastBuildDate><atom:link href="https://loku.it/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Numpy, pandas, Python3.9 和 Big Sur]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Big Sur 发布了.</p>
<p>最近苹果每一次系统发布, 总伴随着奇奇怪怪的 broken feature. 比如Catalina 引进的 APFS 里有个 &quot;双向虫洞&quot; <a href="https://devstreaming-cdn.apple.com/videos/wwdc/2019/710aunvynji5emrl/710/710_whats_new_in_apple_file_systems.pdf">firmlink</a>. 这个 firmlink 本来是用来解决 APFS 分离只读系统卷和 Data 卷之后的程序路径兼容性问题的. 苹果很自信的说 &quot;没有人&quot; 会感受到差别... 好吧, 升级 Catalina 之后我的 autoenv 就没法 <a href="https://github.com/inishchith/autoenv/issues/188">正常使用</a> 了.</p>
<p>这次升级 Big Sur 之后, 安装三件套 Numpy, Pandas 和 matplotlib 的时候又出问题了</p>
<pre><code>(venv) ➜  data_playground pip install</code></pre>]]></description><link>https://loku.it/numpy-pandas-python3-9-he-big-sur/</link><guid isPermaLink="false">6008e3af0430285aa508b5d6</guid><category><![CDATA[Python]]></category><category><![CDATA[MacOS]]></category><category><![CDATA[Big Sur]]></category><category><![CDATA[Numpy]]></category><category><![CDATA[pandas]]></category><category><![CDATA[Polyfit]]></category><category><![CDATA[BLAS]]></category><category><![CDATA[pip]]></category><dc:creator><![CDATA[Karloku Sang]]></dc:creator><pubDate>Mon, 16 Nov 2020 17:46:47 GMT</pubDate><media:content url="https://loku.it/content/images/2021/01/WechatIMG16.jpeg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://loku.it/content/images/2021/01/WechatIMG16.jpeg" alt="Numpy, pandas, Python3.9 和 Big Sur"><p>Big Sur 发布了.</p>
<p>最近苹果每一次系统发布, 总伴随着奇奇怪怪的 broken feature. 比如Catalina 引进的 APFS 里有个 &quot;双向虫洞&quot; <a href="https://devstreaming-cdn.apple.com/videos/wwdc/2019/710aunvynji5emrl/710/710_whats_new_in_apple_file_systems.pdf">firmlink</a>. 这个 firmlink 本来是用来解决 APFS 分离只读系统卷和 Data 卷之后的程序路径兼容性问题的. 苹果很自信的说 &quot;没有人&quot; 会感受到差别... 好吧, 升级 Catalina 之后我的 autoenv 就没法 <a href="https://github.com/inishchith/autoenv/issues/188">正常使用</a> 了.</p>
<p>这次升级 Big Sur 之后, 安装三件套 Numpy, Pandas 和 matplotlib 的时候又出问题了</p>
<pre><code>(venv) ➜  data_playground pip install --no-binary :all: pandas
Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple
Collecting pandas
  Using cached https://pypi.tuna.tsinghua.edu.cn/packages/09/39/fb93ed98962d032963418cd1ea5927b9e11c4c80cb1e0b45dea769d8f9a5/pandas-1.1.4.tar.gz (5.2 MB)
  Installing build dependencies ... done
  Getting requirements to build wheel ... error
  ERROR: Command errored out with exit status 1:
   command: /Users/karloku/data_playground/venv/bin/python /Users/karloku/data_playground/venv/lib/python3.9/site-packages/pip/_vendor/pep517/_in_process.py get_requires_for_build_wheel /var/folders/hl/g6y3_zb56_39311y6ghp2bdh0000gn/T/tmpxn53kuwr
       cwd: /private/var/folders/hl/g6y3_zb56_39311y6ghp2bdh0000gn/T/pip-install-0as0dvhf/pandas
  Complete output (23 lines):
  init_dgelsd failed init
  Traceback (most recent call last):
    File &quot;/Users/karloku/data_playground/venv/lib/python3.9/site-packages/pip/_vendor/pep517/_in_process.py&quot;, line 280, in &lt;module&gt;
      main()

    ...中略...

    File &quot;/private/var/folders/hl/g6y3_zb56_39311y6ghp2bdh0000gn/T/pip-build-env-myeowxu0/overlay/lib/python3.9/site-packages/numpy/__init__.py&quot;, line 286, in &lt;module&gt;
      raise RuntimeError(msg)
  RuntimeError: Polyfit sanity test emitted a warning, most likely due to using a buggy Accelerate backend. If you compiled yourself, see site.cfg.example for information. Otherwise report this to the vendor that provided NumPy.
  RankWarning: Polyfit may be poorly conditioned

  ----------------------------------------
ERROR: Command errored out with exit status 1: /Users/karloku/data_playground/venv/bin/python /Users/karloku/data_playground/venv/lib/python3.9/site-packages/pip/_vendor/pep517/_in_process.py get_requires_for_build_wheel /var/folders/hl/g6y3_zb56_39311y6ghp2bdh0000gn/T/tmpxn53kuwr Check the logs for full command output.
</code></pre>
<p>放狗去搜了一下, <code>Polyfit sanity test emitted a warning</code>, 出来一堆搜索结果. 有说是 <a href="https://stackoverflow.com/questions/64222756/cant-import-numpy-even-though-i-installed-with-pip3">python 3.9 在 macos 上 broke</a> 的, 有说是 <a href="https://stackoverflow.com/questions/64654805/how-do-you-fix-runtimeerror-package-fails-to-pass-a-sanity-check-for-numpy-an">1.19.4 在 macos 上 broke</a> 的. 最后找到 Numpy github 上的 <a href="https://github.com/numpy/numpy/issues/15947">Issue 15947</a>. 看了看.</p>
<p>原来我错怪苹果了, 这不是 Big Sur 的问题, 当然也不是 Numpy 1.19.4 或者 Python 3.9 的问题... 这是苹果提供的 BLAS 库的问题.</p>
<p>Numpy 的安装依赖 <a href="https://zh.wikipedia.org/wiki/BLAS">BLAS</a> 库. 而苹果官方提供的 Accelerate 框架中的 Accelerate-BLAS 被社区认为有<a href="https://github.com/numpy/numpy/issues/15947#issuecomment-716877853">诸多问题</a>. 于是如果 Numpy 的二进制不幸使用了 Accelerate-BLAS 进行编译, 就会报错.</p>
<p>通常在 pypi 中, 会有类似 <code>numpy-1.19.4-cp39-cp39-macosx_10_9_x86_64.whl</code> 这样已经打包好的 wheel 可供选择. numpy 的维护者会使用 OpenBLAS 进行编译后上传 wheel. 但有些时候, 你在 pypi 上就是找不到这样的 wheel, 比如说:</p>
<ul>
<li>新的 python 版本刚发布 (python3.9 刚发布时)</li>
<li>新的 numpy 版本刚发布 (numpy 1.19.4 刚发布时)</li>
<li>新的 OS 版本刚发布 (比如说...你 Big Sur, MacOS 11)</li>
</ul>
<p>维护者都没来得及给环境提供打包好的 wheel, 于是 pip 只能自己下载源码进行编译... 也就出现了惨剧.</p>
<p>知道了原因, 解决方法也就很简单了</p>
<pre><code class="language-sh"># 先安装 OpenBLAS
$ brew install openblas
# 然后指定 OpenBLAS 作为 BLAS 提供方, 安装 Numpy 和 pandas
$ OPENBLAS=&quot;$(brew --prefix openblas)&quot; pip install numpy pandas
</code></pre>
<p>Mac 总是在提供各种坑. 还是 linux 更如实家般安心...</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Python 的世界里没有代码块]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>以前经常写 ruby 和 js, 对于以下的代码非常习惯:</p>
<pre><code class="language-ruby">item = {id: 1, name: 'Donald Clinton'}
attrs = [:id, :name]
attr_getters = attrs.map do |attr|
  lambda do
    &quot;The #{attr} is #{item[attr]}&quot;
  end
end
&gt; attr_getters[0][]
&quot;The id is 1&quot;
&gt; attr_getters[1][]
&quot;The name is Donald</code></pre>]]></description><link>https://loku.it/python-de-shi-jie-li-mei-you-dai-ma-kuai/</link><guid isPermaLink="false">6008e3af0430285aa508b5d5</guid><category><![CDATA[Python]]></category><category><![CDATA[lambda]]></category><dc:creator><![CDATA[Karloku Sang]]></dc:creator><pubDate>Thu, 11 Aug 2016 22:16:12 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>以前经常写 ruby 和 js, 对于以下的代码非常习惯:</p>
<pre><code class="language-ruby">item = {id: 1, name: 'Donald Clinton'}
attrs = [:id, :name]
attr_getters = attrs.map do |attr|
  lambda do
    &quot;The #{attr} is #{item[attr]}&quot;
  end
end
&gt; attr_getters[0][]
&quot;The id is 1&quot;
&gt; attr_getters[1][]
&quot;The name is Donald Clinton&quot;
</code></pre>
<pre><code class="language-javascript">var item = {id: 1, name: 'Donald Clinton'};
var attrs = ['id', 'name'];
var attr_getters = attrs.map(function (attr) {
  return function() { 
    var value_with_name = 'The ' + attr + ' is ' + item[attr];
    return value_with_name; 
  };
});
&gt; attr_getters[0]()
'The id is 1'
&gt; attr_getters[1]()
'The name is Donald Clinton'
</code></pre>
<p>最近的工作开始要写 python, 于是我想当然的也这么写了....</p>
<pre><code class="language-python">item = {'id': 1, 'name': 'Donald Clinton'}
attrs = ['id', 'name']

attr_getters = []
for attr in attrs:
    getter = lambda : 'The {0} is {1}'.format(attr, item[attr])
    attr_getters.append(getter)
</code></pre>
<p>可是并不 work!</p>
<pre><code>&gt;&gt;&gt; attr_getters[0]()
'The name is Donald Clinton'
&gt;&gt;&gt; attr_getters[1]()
'The name is Donald Clinton'
</code></pre>
<p>仔细观察了一下代码... 这 <code>for ... in ...:</code> 虽然看起来很清真, 可是他<strong>并不是</strong>代码块. 我这傻乎乎的 lambda 的闭包始终绑定着 for 循环所在的环境, 于是当循环完成后, <code>attr</code> 就定格在了 <code>'name'</code> 上.</p>
<p>于是一个蠢呼呼的 fix 是在 lambda 外面再包一个</p>
<pre><code class="language-python">item = {'id': 1, 'name': 'Donald Clinton'}
attrs = ['id', 'name']

attr_getters = []
for attr in attrs:
    getter = (lambda a: lambda : 'The {0} is {1}'.format(a, item[a]))(attr)
    attr_getters.append(getter)
</code></pre>
<p>鉴于这很蠢...于是代码只能再改一下</p>
<pre><code class="language-python">item = {'id': 1, 'name': 'Donald Clinton'}
attrs = ['id', 'name']

def build_getter(attr):
    def getter():
        return 'The {0} is {1}'.format(attr, item[attr])
    return getter

attr_getters = map(build_getter, attrs)
</code></pre>
<p>lambda, 闭包等等, 这些东西在 python 世界里并不是一等公民, 随手定义函数的编程习惯在 python 中基本是完全不适用的. 单行 lambda 更是残废, 基本上想做点事情都必须预先定义一个函数才行, 没法定义匿名 block.</p>
<p>PS: 在 python 的世界里非常注重 callable 的. 小到一个函数, 大到一个类, 甚至一个实例, 只要定义了 <code>__call__</code> 方法, 他就是 callable 的.</p>
<pre><code class="language-python">class A(object)
  def __init__(self, ...):
    ...

map(A, some_list_of_init_params) # =&gt; list of A
</code></pre>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Automatic Eager Loading for Mongoid]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Even in MongoDB, sometimes your models may still have reference relations. Like ActiveRecord, the popular MongoDB ORM, mongoid, provides <a href="http://www.rubydoc.info/github/mongoid/mongoid/Mongoid%2FCriteria%3Aincludes"><code>#includes</code></a> method to perform <a href="http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations">eager loads</a>.</p>
<h2 id="problems">Problems</h2>
<p>However, it is a pain to specify them manually in codes. In most scenes, you just need the eager loading when you what to</p>]]></description><link>https://loku.it/automatic-eager-loading-for-mongoid/</link><guid isPermaLink="false">6008e3af0430285aa508b5d4</guid><category><![CDATA[Ruby]]></category><category><![CDATA[MongoDB]]></category><category><![CDATA[mongoid]]></category><dc:creator><![CDATA[Karloku Sang]]></dc:creator><pubDate>Fri, 29 Apr 2016 10:45:46 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Even in MongoDB, sometimes your models may still have reference relations. Like ActiveRecord, the popular MongoDB ORM, mongoid, provides <a href="http://www.rubydoc.info/github/mongoid/mongoid/Mongoid%2FCriteria%3Aincludes"><code>#includes</code></a> method to perform <a href="http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations">eager loads</a>.</p>
<h2 id="problems">Problems</h2>
<p>However, it is a pain to specify them manually in codes. In most scenes, you just need the eager loading when you what to access the relation.</p>
<p>As described in <a href="http://blog.salsify.com/engineering/automatic-eager-loading-rails">Joel Turkel's post</a> about <a href="https://github.com/salsify/goldiloader">Goldiloader</a>, the eager loading should neither be a duty of controllers (why should they care about how the data is fetched) nor be one of views (they just present your data!).</p>
<p>Eager loading is more likely a job that the model it self should handle.</p>
<h2 id="betterpractice">Better Practice</h2>
<p>Just like Goldiloader for ActiveRecord, there can also be a plugin that makes mongoid do the eager loading automatically.</p>
<p>Say you have the following models for your blog site:</p>
<pre><code class="language-ruby">class User
  include Mongoid::Document
  has_one :device
  has_many :posts
end

class Post
  include Mongoid::Document
  belongs_to :user
  has_and_belongs_to_many :tags
end

class Tag
  include Mongoid::Document
  has_and_belongs_to_many :posts, fully_load: true
end
</code></pre>
<p>When you call</p>
<pre><code class="language-ruby">@user = User.find(id)
@posts = @user.posts.limit(10) # 

# access the tags
@posts.each do |post|
  puts post.title
  puts post.tags.map(&amp;:name).join(', ')
end
</code></pre>
<p>Mongoid will perform 12 queries: 1 on users, 1 on posts and <strong>10</strong> on tags:</p>
<pre><code class="language-javascript">db.users.find('_id' : id);
db.posts.find('user_id' : user_id);
db.tags.find('post_ids' : post0_id);
db.tags.find('post_ids' : post1_id);
...
db.tags.find('post_ids' : post9_id);
</code></pre>
<p>But as an experienced developer, you could easily figure out that the eager loading on <code>:tag</code> relation should be performed. In fact, adding <code>#includes</code> to the criteria is a default action, manually done by the developers.</p>
<p><em>Why not make it a default to mongoid?</em></p>
<p>When you access the tag relation and is about to query it in your db, perform an eager loading on <code>:tag</code> just before the query:</p>
<pre><code class="language-ruby"># before query a relation
# the query loading method of a relation is
# Mongoid::Relations::Targets::Enumerable#unloaded_documents

def before_unloaded_documents
  owner = relation.base # get the relation owner
  metadata = relation.relation_metadata # get the relation metadata

  # DO THE EAGER LOADING
  # 
  #   Requires some trick to access the owners collection through the owner.
  #   Add an reference to the collection into model
end
</code></pre>
<p>Then when you run the previous <code>@posts.each...</code>, it will results to only 3 quires, 1 for each:</p>
<pre><code class="language-javascript">db.users.find('_id' : id);
db.posts.find('user_id' : user_id);
db.tags.find('post_ids' : {'$in' : post_ids});
</code></pre>
<p>Wow, you do not need to specify any eager loads. (The modified) Mongoid will do it automatically for you!</p>
<h2 id="conclusion">Conclusion</h2>
<p>Eager loads are performance boosters, and should be an default behaviour in most situations. The developers should be aware of them, but not repeatly code <code>.includes(:...)</code> just because you need it everywhere.<br>
I've written a gem <a href="https://rubygems.org/gems/mongoload">mongoload</a> to make your mongoid an automatic eager loader. Check it on <a href="https://github.com/karloku/mongoload">GitHub</a>.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Using ES6 style {a, b, c} hash construct in Ruby]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>ES6 ships with a very handy syntax sugar to build Object(aka Hash in Ruby).</p>
<pre><code class="language-Javascript">var year = 2016;
var month = 3;
var day = 30;
var theDate = { year, month, day };
</code></pre>
<p>Can I use the syntax in ruby? calling something simple as <code>HandyHash(:year, :month, :day)</code>?</p>
<p>To get the variable values, the</p>]]></description><link>https://loku.it/using-es6-style-a-b-c-hash-construct-in-ruby/</link><guid isPermaLink="false">6008e3af0430285aa508b5d3</guid><category><![CDATA[Ruby]]></category><dc:creator><![CDATA[Karloku Sang]]></dc:creator><pubDate>Wed, 30 Mar 2016 19:55:41 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>ES6 ships with a very handy syntax sugar to build Object(aka Hash in Ruby).</p>
<pre><code class="language-Javascript">var year = 2016;
var month = 3;
var day = 30;
var theDate = { year, month, day };
</code></pre>
<p>Can I use the syntax in ruby? calling something simple as <code>HandyHash(:year, :month, :day)</code>?</p>
<p>To get the variable values, the method <code>#HandyHash</code> must be aware of the context who calls him. This is not recommend in Ruby.<br>
If you pass bindin to the method, the paramaters become <code>(binding, :year, :month, :day)</code>... Weird. Why I should call binding here.</p>
<p>Another approach to get the caller context binding is to pass a block defined in it. To get the hash keys, I decide to wrap an array in the block.</p>
<pre><code class="language-Ruby">def HandyHash(&amp;list_block)
  variables = yield.map(&amp;:to_s)
  Hash[
    variables.map do |variable|
      [variable.to_sym, list_block.binding.local_variable_get(variable)]
    end
  ]
end
</code></pre>
<p>Suppose you have <code>year, month, day = 2016, 3, 30</code>. Then you can build the Hash with <code>HandyHash{[:year, :month, :day]}</code>.</p>
<p>I have wrapped the logic in to a gem called <a href="https://rubygems.org/gems/simple_hash">simple_hash</a> (the Github repo is <a href="https://github.com/karloku/simple_hash">here</a>).</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[森的妖精 GraphQL]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>最(好几个)近(月之前), 在公司前端同学的推荐下接触了<a href="http://facebook.github.io/graphql/">GraphQL</a>, 感觉一阵清爽. 如今使用了一段时间, 可以汇报一下使用感受.</p>
<h3 id="graphql">GraphQL?</h3>
<p>物如其名, GraphQL 是一种 Query Language, 由世界上最好语言的领军者 Facebook 公司发明. 它的出现是为了满足服务端向客户端灵活输出数据的需要.</p>
<h3 id="restfulapi">Restful API 不行吗?</h3>
<p>当然行. Restful 是包括我在内, 各位后端同学都非常熟悉的朋友. 从早年凭感觉写的jsp, asp们, 到后来做的 WebService API, 再到有哲学思想指导的 Restful API. 可以很明显的感觉到, 这些前后端的数据交互都是由后端在主导. 后端决定了数据格式, 决定了每个请求所能得到的数据.</p>
<p><em>可是!</em> 时代不再是做个门户, 做个页面这么简单了. 如今更注重的是客户端侧的数据交互. 看看隔壁js们的各种前端框架, SPA, 数据绑定, MVVM 搞得热火朝天. 虽然后端依然承载着业务处理, 数据安全这样核心的功能. 但是客户端才是数据的需求方,</p>]]></description><link>https://loku.it/sen-de-yao-jing-graphql/</link><guid isPermaLink="false">6008e3af0430285aa508b5d2</guid><category><![CDATA[GraphQL]]></category><dc:creator><![CDATA[Karloku Sang]]></dc:creator><pubDate>Mon, 22 Feb 2016 12:32:05 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>最(好几个)近(月之前), 在公司前端同学的推荐下接触了<a href="http://facebook.github.io/graphql/">GraphQL</a>, 感觉一阵清爽. 如今使用了一段时间, 可以汇报一下使用感受.</p>
<h3 id="graphql">GraphQL?</h3>
<p>物如其名, GraphQL 是一种 Query Language, 由世界上最好语言的领军者 Facebook 公司发明. 它的出现是为了满足服务端向客户端灵活输出数据的需要.</p>
<h3 id="restfulapi">Restful API 不行吗?</h3>
<p>当然行. Restful 是包括我在内, 各位后端同学都非常熟悉的朋友. 从早年凭感觉写的jsp, asp们, 到后来做的 WebService API, 再到有哲学思想指导的 Restful API. 可以很明显的感觉到, 这些前后端的数据交互都是由后端在主导. 后端决定了数据格式, 决定了每个请求所能得到的数据.</p>
<p><em>可是!</em> 时代不再是做个门户, 做个页面这么简单了. 如今更注重的是客户端侧的数据交互. 看看隔壁js们的各种前端框架, SPA, 数据绑定, MVVM 搞得热火朝天. 虽然后端依然承载着业务处理, 数据安全这样核心的功能. 但是客户端才是数据的需求方, <strong>前端更清楚, 需要什么样的数据进行呈现, 使用什么样的数据进行交互</strong>.</p>
<p>这样一来写接口的的时候就会有这样的经历, 随着前端页面或是客户端的快速改版, 对于字段的需求是在不断变化, 于是一个 API 不断地 v1, v2, v3...vN. 最后也改烦了: 干脆把每个 model 的字段, 可以展示的都展示出去好了! 牺牲了服务器的带宽和客户的流量, 不过总算是不用再繁琐的更改接口定义了.</p>
<h3 id>何不让客户端主导?</h3>
<p>就好像 ElasticSearch 的接口里一样, 可以告诉服务端我要请求什么字段. 我这里有个叫 Uesr 的 model, 它一对多拥有许多的 Messages. 一个请求 Messages 列表的 Request 看起来就像下面这样:</p>
<pre><code class="language-json">GET /messages
{
  &quot;since&quot;: &quot;20XX-YY-ZZTAA:BB:CC&quot;,
  &quot;fields&quot;: [
    &quot;id&quot;,
    &quot;title&quot;,
    &quot;content&quot;,
    &quot;created_at&quot;,
    {
      &quot;user&quot;: [
        &quot;id&quot;,
        &quot;name&quot;,
        &quot;account&quot;
      ]
    }
  ]
}
</code></pre>
<p>其实也挺好看的对不对...</p>
<p>GraphQL 的出现, 就是定义了一套更为清晰更为灵活的标准, 来满足客户端对于数据灵活的需要.</p>
<h3 id="ingraphql">In GraphQL</h3>
<h4 id>定义结构</h4>
<p>GraphQL 的核心是两个概念. 一个是 Type, 他定义了数据的结构.</p>
<pre><code>type Message {
  id: String
  title: String
  content: String
  user: User
}

type User {
  id: String
  name: String
  account: String
  messages: [Message]
}
</code></pre>
<p>这是 GraphQL Spec 中定义 Type 用的语言. <small>(虽然实际上我也没见到任何一个 GraphQL 的库用到过这些语言, 包括 Facebook 自己官方提供的例子... 不过这种细节不用在意, 这就是为了方便沟通用的东西.)</small><br>
在这个 Type 定义中, 定义了两个 Type, 在 <code>{}</code> 中间的定义的东西叫做 Field. 也就是 GraphQL 两个核心中的另一个. Type 和 Field 的定义形成了 GraphQL 的 Schema.</p>
<p>当然只有散户的话是没有入口的. GraphQL 的默认 Schema 自带了两个 Type 作为入口. 一个是 QueryRoot, 一个是 MutationRoot. 下文再慢慢讲.</p>
<p>我们可以在 QueryRoot 里把 Message 挂上去.</p>
<pre><code>type QueryRoot {
  messages(since: Date): [Message]
}
</code></pre>
<p>[] 代表列表, () 里的是接受的参数.</p>
<h4 id>执行查询</h4>
<p>有了上面的 Schema 之后, 就能进行 query 了.</p>
<pre><code>query messages(since: &quot;20XX-YY-ZZTAA:BB:CC&quot;) {
  id
  title
  content
  user {
    id
    name
    account
  }
}
</code></pre>
<p>看起来比restful版本的可选字段请求清楚很多了. user 作为关联对象, 也很自然的出现在相同的结构中.</p>
<h4 id="mutation">还有个 Mutation 呢?</h4>
<p>上面提到过, GraphQL 的 Schema 最上级有两个 Root Type, 一个是 QueryRoot, 一个是 MutationRoot. Mutation, 根据 spec 中的定义, 是&quot;会让服务端数据发生变化的 query&quot;, 也就是有副作用的 query .<br>
比如定义一个发消息的 mutation</p>
<pre><code>type MutationRoot {
  createMessage(title: String, content: String): Message
}
</code></pre>
<p>这里 createMessage 是一个 mutation, 它返回的数据 Type 为 Message.</p>
<pre><code>mutation createMessage(title: &quot;森的妖精 GraphQL&quot;, content: &quot;blahblahblahblah&quot;) {
  id
}
</code></pre>
<p>这样就能创建一个 Message, 并且指定服务端返回创建后的 Message ID.</p>
<p>GraphQL 的基本用法就是上面这些了. 详细的还会有 Interface, Union 等东西, 主要是为了解决多态和复用.</p>
<h3 id>总结?</h3>
<p>在 GraphQL 中服务端的职责在于定义数据结构和准备数据, 进行业务处理. 而数据的获取, 展示则由更接近用户的客户端来主导. 对客户端和服务端都是很大的解放. 对于 Restful 在呈现端的不灵活是一个很大的补充.</p>
<h4 id>不足</h4>
<p>要说讨厌的地方, 那就是 Mutation 了. 根据 Facebook 所提供的 spec, 所有的 Mutation 都挂载在 MutationRoot 之下. 用我们 coding 所习惯的说法, 都是同一个类的全局方法... 感觉很难受.</p>
<p>于是我们在开发中, 前后端慢慢形成了一种默契. <strong>在 Type 中定义对象的 Mutation</strong>...<br>
类似这样</p>
<pre><code>type Fish {
  id: String
  name: String
  Price: Float
  inStock: Int
  mutation: FishMutation
}
</code></pre>
<pre><code>type FishMutation {
  update: UpdateFishMutation
  sell: SellFishMutation
}
</code></pre>
<p>于是就能</p>
<pre><code>query {
  fish(id: &quot;1&quot;) {
    id
    name
    price
    mutation {
      sell(weight: 100) {
        inStock
      }
    }
  }
}
</code></pre>
<p>这样看上去稍微舒服点...</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Grape自定义参数类型]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Grape的<code>params</code>函数是非常方便用于定义API参数的工具. 这次做到一个接口要传入GeoJSON格式的参数. 与世界在接口上直接写了:</p>
<pre><code class="language-ruby">params do
  requires :location, type: GeoJSONPoint, desc: "地点"
end
</code></pre>
<pre><code class="language-ruby">class GeoJSONPoint
  attr_accessor :type, :coordinates
  
  def initialize(coordinates=[])
    @coordinates = coordinates
  end
  
  # always a point
  def type
    "Point"
  end
end
</code></pre>
<p>不过这样显然是不行的, 一定会出400 Bad Request. Grape毕竟不知道GeoJSONPoint这个类是怎么转换的. 读了读Grape的代码, 发现Grape是用 <a href="https://github.com/solnic/virtus/" title="solnic/Virtus">Virtus</a> 这个gem来转换参数=&gt;对象的.<br>
于是这个GeoJSONPoint的类就需要用Virtus的方式来定义. 于是代码变成了</p>
<pre><code class="language-ruby">class GeoJSONPoint
  include</code></pre>]]></description><link>https://loku.it/grapezi-ding-yi-can-shu-lei-xing/</link><guid isPermaLink="false">6008e3af0430285aa508b5d1</guid><category><![CDATA[Ruby]]></category><category><![CDATA[Grape]]></category><category><![CDATA[Virtus]]></category><category><![CDATA[Model]]></category><dc:creator><![CDATA[Karloku Sang]]></dc:creator><pubDate>Thu, 19 Mar 2015 14:48:46 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Grape的<code>params</code>函数是非常方便用于定义API参数的工具. 这次做到一个接口要传入GeoJSON格式的参数. 与世界在接口上直接写了:</p>
<pre><code class="language-ruby">params do
  requires :location, type: GeoJSONPoint, desc: "地点"
end
</code></pre>
<pre><code class="language-ruby">class GeoJSONPoint
  attr_accessor :type, :coordinates
  
  def initialize(coordinates=[])
    @coordinates = coordinates
  end
  
  # always a point
  def type
    "Point"
  end
end
</code></pre>
<p>不过这样显然是不行的, 一定会出400 Bad Request. Grape毕竟不知道GeoJSONPoint这个类是怎么转换的. 读了读Grape的代码, 发现Grape是用 <a href="https://github.com/solnic/virtus/" title="solnic/Virtus">Virtus</a> 这个gem来转换参数=&gt;对象的.<br>
于是这个GeoJSONPoint的类就需要用Virtus的方式来定义. 于是代码变成了</p>
<pre><code class="language-ruby">class GeoJSONPoint
  include Virtus.model
  
  attribute :type, String
  attribute :coordinates, Array
  
  def initialize(coordinates=[])
    @coordinates = coordinates
  end
  
  # always a point
  def type
    "Point"
  end
end
</code></pre>
<p>先用Virtus的方法试一下, 结果OK:</p>
<pre><code class="language-ruby">coercer = Virtus::Attribute.build(GeoJSONPoint)
point = coercer.coerce({type: "Point", coordinates: [190, 50]})
=> #&lt;GeoJSONPoint:0x007ffdf3a61618 @coordinates={:type=&gt;"Point", :coordinates=>[190, 50]}&gt;
</code></pre>
<p>再到RestClient里发个请求看看:</p>
<pre><code class="language-javascript">{"location" : { 
  "type" : "Point", 
  "coordinates" : [190, 50] 
}}</code></pre>
<p>结果:</p>
<pre><code class="language-javascript">{
  "application":{"name":"TestServer","status":"ok"},
  "request":{
    "headers":{
     "Version":"HTTP/1.1",
     "Host":"localhost:3000",
     "Connection":"Keep-Alive",
     "User-Agent":"Apache-HttpClient/4.3.5 (java 1.5)",
     "Accept-Encoding":"gzip,deflate"
   },
   "params":{
     "location":{
       "type":"Point",
       "coordinates":[190,50]}}},
 "env":"127.0.0.1"
}</code></pre>
<p>Grape能够成功把GeoJSONPoint转换出来.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Sorcery + Grape on Rails]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>最近手头有一个需要快速开发的新Web项目, 于是想了想还是不用sinatra, 直接用RoR来写. 因为需要为手机提供API, 感觉在Rails上面架设sinatra有点自欺欺人的感觉, 于是第一次尝试在Rails上面加一层Grape... 于是问题就来了.</p>
<p>Sorcery在Session/Cookies方面的操作完全依赖Rails的API, 以前用sinatra的时候, 基本上没有感觉到什么不适, 这次在Grape上使用Sorcery就爆炸了OTL. <code>Grape::Endpoint.send(:include, Sorcery::Controller)</code>之后就开始各种undefined. 看了看大概都集中在ActionDispatch和ActionController里. 下午大概的做了个hotfix, 可惜因为Grape自用的Cookies和ActionDispatch::Cookies区别实在太大, Grape的remember_me功能在grape上暂时用不了.</p>
<pre><code class="language-ruby">module API
  module SorceryAdapter

    AUTHENTICITY_TOKEN_LENGTH = 32

    def self.included(mod)
      mod.instance_eval {
        helpers do
          ### Adapt for Sorcery (some directly taken from rails</code></pre>]]></description><link>https://loku.it/sorcery-grape-on-rails/</link><guid isPermaLink="false">6008e3af0430285aa508b5d0</guid><category><![CDATA[Ruby]]></category><category><![CDATA[Sorcery]]></category><category><![CDATA[Ruby on Rails]]></category><category><![CDATA[Web Application]]></category><category><![CDATA[Grape]]></category><dc:creator><![CDATA[Karloku Sang]]></dc:creator><pubDate>Tue, 17 Mar 2015 16:09:39 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>最近手头有一个需要快速开发的新Web项目, 于是想了想还是不用sinatra, 直接用RoR来写. 因为需要为手机提供API, 感觉在Rails上面架设sinatra有点自欺欺人的感觉, 于是第一次尝试在Rails上面加一层Grape... 于是问题就来了.</p>
<p>Sorcery在Session/Cookies方面的操作完全依赖Rails的API, 以前用sinatra的时候, 基本上没有感觉到什么不适, 这次在Grape上使用Sorcery就爆炸了OTL. <code>Grape::Endpoint.send(:include, Sorcery::Controller)</code>之后就开始各种undefined. 看了看大概都集中在ActionDispatch和ActionController里. 下午大概的做了个hotfix, 可惜因为Grape自用的Cookies和ActionDispatch::Cookies区别实在太大, Grape的remember_me功能在grape上暂时用不了.</p>
<pre><code class="language-ruby">module API
  module SorceryAdapter

    AUTHENTICITY_TOKEN_LENGTH = 32

    def self.included(mod)
      mod.instance_eval {
        helpers do
          ### Adapt for Sorcery (some directly taken from rails codes)

          # Get session
          def session
            env[Rack::Session::Abstract::ENV_SESSION_KEY]
          end

          # Disable remember_me because of the cookies type conflict
          def current_user
            unless defined?(@current_user)
              @current_user = login_from_session || nil
            end
            @current_user
          end

          ## ActionDispatch::Request
          def reset_session
            if session && session.respond_to?(:destroy)
              session.destroy
            else
              self.session = {}
            end
            @env['action_dispatch.request.flash_hash'] = nil
          end

          ## ActionController::RequestForgeryProtection::ProtectionMethods::NullSession
          # Sets the token value for the current session.
          def form_authenticity_token
            masked_authenticity_token(session)
          end

          def real_csrf_token(session)
            session[:_csrf_token] ||= SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH)
            Base64.strict_decode64(session[:_csrf_token])
          end

          # Creates a masked version of the authenticity token that varies
          # on each request. The masking is used to mitigate SSL attacks
          # like BREACH.
          def masked_authenticity_token(session)
            one_time_pad = SecureRandom.random_bytes(AUTHENTICITY_TOKEN_LENGTH)
            encrypted_csrf_token = xor_byte_strings(one_time_pad, real_csrf_token(session))
            masked_token = one_time_pad + encrypted_csrf_token
            Base64.strict_encode64(masked_token)
          end

          def xor_byte_strings(s1, s2)
            s1.bytes.zip(s2.bytes).map { |(c1,c2)| c1 ^ c2 }.pack('c*')
          end
        end
      }
    end
  end
end</code></pre><!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[不小心是常态....]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>想着把Blog的backend db从sqlite换到mariadb, 结果不小心把.db文件删掉了... 明明一直在跟服务器打交道, 结果大意地把数据库删了&gt;&lt; 好糟糕otl... 总之钻石墓场就这样了OTL...</p>
<!--kg-card-end: markdown-->]]></description><link>https://loku.it/bu-xiao-xin-shi-chang-tai/</link><guid isPermaLink="false">6008e3af0430285aa508b5cf</guid><dc:creator><![CDATA[Karloku Sang]]></dc:creator><pubDate>Tue, 17 Mar 2015 14:11:48 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>想着把Blog的backend db从sqlite换到mariadb, 结果不小心把.db文件删掉了... 明明一直在跟服务器打交道, 结果大意地把数据库删了&gt;&lt; 好糟糕otl... 总之钻石墓场就这样了OTL...</p>
<!--kg-card-end: markdown-->]]></content:encoded></item></channel></rss>