• @jasl 嗯,是的。一个明确的结构更容易维护。分别建表完全可行,我这样就是偷懒 😄

  • @jasl 嗯,我是为额外属性建立 Ecto schema 嵌套在主 schema 下面,方便 app struct 和 database row 的互相转换。大概这样:

    defmodule Question do
      schema "questions" do
        field :type,   TypeEnum
        # 存放额外属性,Elixir 里为 map ,对应 Ruby 的 hash ,PostgreSQL 里面为 jsonb
        field :config, :map
      end
    
      # 每题定义一个子类型,用来格式化 config 的数据
      defmodule Select do
        embedded_schema do
          field :multi_sel, :boolean
          field :min_sel,   :integer
          field :max_sel,   :integer
        end
      end
    
      defmodule ValueMark do
        # ...
      end
    
      # 根据 type 选择题型相关的 schema 来转换数据
      def cast_config(ques, params) do
        mod = get_config_module(params[:type]) # get Select module
        config = params["config"] |> transform_to_schema(mod) |> transform_back_to_map_if_valid
        put_change(ques, :config, config)
      end
    end
    

    用 jsonb 有两个原因:

    • 每个题型子类型的属性太多了,如果少的话也可以用类似 STI 那样的做法,建立一堆额外的数据库字段。
    • 受制于 Ecto ,没办法动态扩展 field ,只能用嵌套的形式了。不过我最近碰到了不同的子类型有公用属性的情况(比如所有选择类的题目都有多选和随机),打算对子类型用动态的 schema 重构一下。

    这种方法的主要缺陷在于没法保证引用完整性,比如额外属性里有图片资源的 id ,我也在考虑要不要特事特办,额外开字段存储部分数据。

    问题间跳转就一言难尽了,前端流程处理可以问问 @nightire ,后端这边我们是存储了每个题对应的下一题的路径数据,因为也有额外属性不定的问题,所以也是 jsonb 存的。

  • @jasl 我现在在巧思负责新后端的开发。但我的场景不算是 form 的场景,只是同一个大类型的不同 object 都会有一些额外的属性,而且这些额外属性是可预测的(除了需求)而不是用户自定义的。目前是在应用层面用 hash/map 表示并存进数据库的 jsonb 字段里。

    我们的问卷系统倒是跟自定义 form 有少数重叠部分,不过因为题目的渲染和验证等逻辑都是在前端实现的,后端更多的是持久化 “元数据” ,所以也不存在用 Elixir 动态渲染页面的情况。

  • @jasl 👍 ,期待你开源相关的存储方案

  • @jasl 请问你们这种动态表单提交的数据在知人里是怎么持久化的呢?也是类似 form_core_fields 这种 EAV 的方式么?

  • 说句题外话, 如果使用 PostgreSQL 的话,查询 tree 结构的 children/ancestors 都可以用 WITH (CTE) 做递归查询,稍微封装一下就可以不依赖任何库。详细信息可以看 文档

    优点:

    1. 单条 SQL 查多级父子关系,而且不用缓存都很高效。
    2. 不需要额外的字段,只需要 parent_id

    缺点:

    1. SQL 查出来的结果始终是平铺的记录,如果需要树形结构的数据,得在应用层面做转换。不过平铺的数据在某些特定场景下(比如 JSON API 规范的 compound documents ,或者其他展平关联数据的 API 格式)反而是优点。
    2. 上一点提到在应用层面做转换,但对 ActiveRecord 而言不太好做,因为 model.relationship = another_model 会引发一些副作用,如果是转成纯 Ruby 对象就不存在这种问题。
  • 北京面试所感 at 2017年04月14日

    面试是很难看出一个人的真实能力的,所以很多公司不得不采取一些能够量化的标准:工作年限,过往履历,毕业院校…… 包括算法水平和笔试题其实也是量化的一部分,目的是最大程度的刷掉一批人,让有限的人力能投入到公司认为合格的人身上去。这跟吐槽了很多年的高考有点相似之处 -- 不是一个好的筛选方式,但至少是个简单易行也勉强可用的方式。

    LZ 不妨思考一下如何量化自己的优势和公司的需求。前者了解自己,后者了解对方。多了解一下面试公司的要求,甚至是多个公司对你想要的职位的平均要求,然后针对性的弥补。即使面试失败也可以多问对方一些问题,比如理论知识不足说的是哪些理论知识?需要如何补充?能够量化的东西就有优化的空间,不然努力不到点子上,就浪费时间精力了。

    最后,12 个星期拿来练手,真不如找个公司做事。别以学习的名义逃避求职,这才第三次呢。

  • 偶尔关注下 Crystal ,不过还是更看好 Elixir 多一些。个人感受是单纯的函数式编程并没有跟传统的 OO 有非常大的差别,相反很多理念是可以通用的。造成 Elixir 最大思维差异的是 process 的设计,以及衍生出来的 OTP 的那些东西,这都是 Erlang 特有的思路,函数式编程语言中貌似也就此一家。

    喜欢 Elixir 的一个主要原因是,它是一个考虑到应用层级的解决方案,而不仅仅是提供一个语言。Mix 提供完整的项目构建和管理,ExUnit 提供完备的测试框架,Application 提供的组件化单元,Supervisor 提供的监控和重启机制(后两者都是 OTP 的范畴了),release 提供的打包和热更新…… 虽说 Elixir 很新,但得益于 Erlang 三十年的发展,各种基础组件都还比较稳固。不像其他语言要从地基开始重新做起。我觉得大多数基础设施还是需要时间积累的。

  • 我好半天才想到 “返工与遥遥” 是 Rework 和 Remote 😄

  • @nightire 应该没问题的。这跟你说的 npm link 其他 module 是一个意思。不过不重启 server 就更新代码我记得只有 Rails engine 才行。