Comments

GraphQL与Ruby的使用

GraphQL是Facekbook开源API数据查询语言,随着现在业务场景越来越复杂,一站式开发会产生工作协调低下、项目结构混乱的一些问题,现在比较流行前后端分离应用开发,前端和后台约定数据格式通过Ajax与API进行数据交互这样职责清晰、项目逻辑专注,而GraphQL就是一个数据抽象层,包括数据格式、数据关联、查询方式定义, GraphQL也并不是一个具体的后端编程框架,它只是使后端API能够提供更丰富的数据结构和更多复杂多变的查询方式的一种规范。

ruby graphql简单例子

  • GraphQL::ObjectType.define 对象类型是解析GraphQL语法的一种数据关系映射
  • GraphQL::Relay::Mutation.define 对象数据变更定义
  • GraphQL::Schema.define 数据主入口,包括输入/输出

例子地址

基本类型定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
module Types
  UserType = GraphQL::ObjectType.define do
    name "User"
    description "A User"

    field :id,   !types.ID
    field :name, !types.String
  end
end

module Types
  CommentType = GraphQL::ObjectType.define do
    name "Comment"
    description "A Comment"

    field :id,   !types.ID
    field :body, !types.String
    field :user, UserType
  end
end

module Types
  PostType = GraphQL::ObjectType.define do
    name "Post"
    description "A Post"

    field :id,   !types.ID
    field :body, !types.String
    field :user, UserType
    field :comments, types[CommentType]
  end
end

module Types
  QueryType = GraphQL::ObjectType.define do
    name "Query"
    description "The query root for this schema"

    field :post, PostType do
      argument :id, types.ID
      resolve ->(obj, args, ctx) {
        Post.find(args[:id])
      }
    end

    field :posts, types[PostType] do
      resolve ->(obj, args, ctx) {
        Post.all
      }
    end
  end
end

module Types
  MutationType = GraphQL::ObjectType.define do
    name "Mutation"

    field :addPost, field: Mutations::AddPost.field
  end
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module Mutations

  AddPost = GraphQL::Relay::Mutation.define do
    name "AddPost"

    input_field :body, !types.String
    input_field :userId, !types.ID

    return_field :post, Types::PostType

    resolve ->(object, inputs, ctx) {
      post = Post.create!(body: inputs[:body], user_id: inputs[:userId])
      { post: post }
    }
  end

end
1
2
3
4
StarWarsSchema = GraphQL::Schema.define do
  query Types::QueryType
  mutation Types::MutationType
end

语法执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#查询所有文章
query_string = %|{
  posts {
    body
    user{ id name }
    comments{ id body }
  }
}|

result_hash = StarWarsSchema.execute(query_string)
#{
#  "data": {
#    "posts": [
#      {
#        "body": "article content",
#        "user": { "id": "1", "name": "kaka" },
#        "comments": []
#      }  
#    ]
#  }
#}

#查询单篇文章
query_string = %|{
  post(id: 1) {
    body
    user{ id name }
    comments{ id body }
  }
}|

result_hash = StarWarsSchema.execute(query_string)
#{
#  "data": {
#    "posts": {
#       "body": "article content",
#       "user": { "id": "1", "name": "kaka" },
#       "comments": []
#    }  
#  }
#}


#创建文章
query_string = %|
  mutation addPost {
    addPost(input: {userId: 1, body: "A GraphQL server implementation for Ruby"}){
      post{
        body
      }
    }
  }|

result_hash = StarWarsSchema.execute(query_string)
#{"data"=>{"addPost"=>{"post"=>{"body"=>"A GraphQL server implementation for Ruby"}}}}
Comments

Ruby链式调用安全符号(&.)

Ruby-2.3.0版本添加安全操作符&, 其实我们之前可能是用try处理这种安全调用,但毕竟try是依赖Rails的ActiveSupport Module,&与try之间还是有些区别的;

使用场景

在代码中我们防止nil对象调用报错,我们一般会使用下面这样的逻辑判断来避免错误

1
2
3
if account && account.owner && account.owner.address
...
end

如果我们includes ActiveSupport我们会这样调用

1
2
3
if account.try(:owner).try(:address)
...
end

使用&符号

account&.owner&.address

其实&与try区别在于&前面方法返回nil就会终止链式调用, try则是检测最后的方法是否存在如果不存在就返回nil

1
2
3
4
5
6
7
8
9
10
11
12
account = Account.new(owner: Object.new)
account.owner.address
# => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>

account && account.owner && account.owner.address
# => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>`

account.try(:owner).try(:address)
# => nil

account&.owner&.address
# => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>`
Comments

使用MySQL存储emoji表情符号

一个emoji是4bytes的存储空间, 如果你的MySQL使用的是utf8编码格式那emoji符号存储将会截断字符因为utf8一个字符只是 3bytes的存储空间

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
mysql> SET NAMES utf8;
Query OK, 0 rows affected (0,00 sec)

mysql> INSERT INTO messages (message) VALUES ('What a nice emoji😀!');
Query OK, 1 row affected, 1 warning (0,00 sec)

mysql> SHOW WARNINGS;
+---------+------+---------------------------------------------------------------------------+
| Level   | Code | Message                                                                   |
+---------+------+---------------------------------------------------------------------------+
| Warning | 1366 | Incorrect string value: '\xF0\x9F\x98\x80!' for column 'message' at row 1 |
+---------+------+---------------------------------------------------------------------------+
1 row in set (0,00 sec)

mysql> SELECT message FROM messages;
+-------------------+
| message           |
+-------------------+
| What a nice emoji |
+-------------------+
1 row in set (0,00 sec)

使用utf8mb4格式可以存储emoji字符,但是MySQL 5.5.3版本才引进utf8mb4编码格式,所以数据库要更新到这个版本或者更高的, 用utf8mb4之后我们经常会遇到Mysql2::Error: Specified key was too long; max key length is 767 bytes错误,这是因为Innodb的索引长度限制767bytes, (varchar(255) * 4bytes)这个是会超过767bytes

如果你的字段类型长度不能减少,那只能是指定行的格式来解决,在创建表时指定ROW_FORMAT=DYNAMIC参数可以从767bytes限制提升到3072bytes, 这样就是会牺牲一点空间

例如:

1
2
3
4
5
6
7
8
9
10
CREATE TABLE `bookmarks` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `url` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
  `title` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
  `user_id` int(11) NOT NULL,
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  `site_info_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=25 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC

如果使用的是Rails的migration的话就如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class CreateArticles < ActiveRecord::Migration[5.0]
  def change
    create_table :articles, options: 'ROW_FORMAT=DYNAMIC' do |t|
      t.string :title, null: false, limit: 300
      t.datetime :published
      t.string :author
      t.text :description
      t.text :content

      t.timestamps
    end

    add_index :articles, :title, :unique => true
  end
end
Comments

使用Ruby MonitorMixin注意的坑

初次使用MonitorMixin控制同步执行的时候一不小心就踩坑了,当你include的时候如果你的class定义了initialize方法 请调用super否则会报undefined method `lock' for nil:NilClass错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Number
  attr_accessor :n

  include MonitorMixin

  def initialize(*args)
    @n = 0
    super() # 调用MonitorMixin#initialize
  end

  def increase(x)
    synchronize do
      @n = @n + x
      puts("#{@n}, #{x}")
    end
  end
end
Comments

ember使用express做后台API服务

使用Ember CLI的ember generate server命令创建server,模块会生成server/index.js文件,该服务是基于Express框架

ember generate server

server/index.js默认内容

1
2
3
4
5
6
7
8
9
10
11
12
13
module.exports = function(app) {
  var globSync   = require('glob').sync;
  var mocks      = globSync('./mocks/**/*.js', { cwd: __dirname }).map(require);
  var proxies    = globSync('./proxies/**/*.js', { cwd: __dirname }).map(require);

  // Log proxy requests
  var morgan  = require('morgan');
  app.use(morgan('dev'));

  mocks.forEach(function(route) { route(app); });
  proxies.forEach(function(route) { route(app); });

};

从内容分析可以看出,其实这个服务可以让前端更好的mock API的数据做测试,这样在工作配合上前端不需要等后端API调式 可以使用server模拟数据;

也可以自定义API数据接口

1
2
3
4
5
6
7
8
9
10
11
12
// server/index.js
var bodyParser = require('body-parser');

module.exports = function(app) {

  app.use(bodyParser.json({ type: "application/json" }));

  app.get('/api/items/:item', function(req, res) {
    const item = localdb.find('item', req.params.item);
    res.send({ item: item });
  });
}

运行服务

ember server

我们就可以通过http://localhost:4200/api/items/1API获取数据

Comments

Ember容器注入对象

Ember注入是用来将一个对象注入到一个容器里面去,让容器可以访问对象,容器是指routecontrollerhelper

使用用例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// app/services/session.js
export default Ember.Service.extend({
  isAuthenticated: false
});

// app/initializes/inject-session.js
export function initialize(application) {
  application.inject('route', 'service:session');
}

export default {
  name: 'session',
  initialize: initialize
}


// app/routes/products.js
export default Ember.Route.extend({

  model(){
    this.session
  },
  setupController(controller, model){
    this.session
    ...
  }
});
Comments

Intersection-of-two-arrays

Given two arrays, write a function to compute their intersection.

Example: Given nums1 = [1, 2, 2, 1], nums2 = [2, 2], return [2].

Note: Each element in the result must be unique. The result can be in any order.

# @param {Integer[]} nums1
# @param {Integer[]} nums2
# @return {Integer[]}
def intersection(nums1, nums2)
  nums1 & nums2
end
Comments

Reverse Vowels of a String

Write a function that takes a string as input and reverse only the vowels of a string.

Example 1: Given s = “hello”, return “holle”.

Example 2: Given s = “leetcode”, return “leotcede”.

1
2
3
4
5
6
7
8
9
10
11
12
# @param {String} s
# @return {String}
def reverse_vowels(a)
  tmp = a.length.times.inject([]) do |s, i|
    /(a|e|i|o|u)/i =~ a[i] ? s << i : s
  end
  tmp.length.fdiv(2).ceil.times.each do |i|
    v, b = tmp[i], tmp[tmp.length-1-i]
    a[v], a[b] = a[b], a[v] if v && b
  end
  a
end

Comments

Devise Warden报错401

今天遇到一个特别的问题在项目里面使用devise应用到User与Admin, 因为User是可以用手机号码和Email登录的, 而Admin只能邮件登录,结果User可以正常登录,但是Admin应该也可以正常登录,但是就是报401密码错误,经过翻看源码发现一个很好的工具,找到出现问题的原因

(byebug) Devise::Models.check_fields!(Admin)
*** Devise::Models::MissingAttribute Exception: The following attribute(s) is (are) missing on your model: phone

因为我在devise.rb配置了

config.authentication_keys = [:email, :phone]

所以我要在Admin模型里面指定authentication_keys

devise :database_authenticatable, :registerable,
     :recoverable, :rememberable, :trackable, :validatable,
     :authentication_keys => [:email]
Copyright © 2017 kaka