Eisen's Blog

© 2024. All rights reserved.

angluar-resource rails integration

2013 July-31

最近本来是在折腾 parse angular rails 的一个项目。一直想把 parse 封装成和 rails 自带的 activerecord 那样的的 api 但是最终由于各种原因还是放弃了。整体来说还是因为 parse 的社区不够大,需求量不够大,导致周边做的不是很给力。要啥啥没有也就罢了,关键是 parse 目前处于高不成低不就的地步:要啥有啥,但是,做的不好,不完善。

浪费了一天在 parse-resource 上无果很是伤心。总想做点东西去弥补下损失,起码找到个可以用于目前项目的,简化项目开发的东西吧?那就从这个的周边入手吧。我一直都在想如果 backend restful 的这个接口搞定了,那前面就可以尝试 angular-resource 了。虽说目前后端没有搞定,但是我们依然可以去尝试它。

angular-resource 就是一个可以简化 angular 写 restful 接口的工具。之前不使用它是因为有这样一个疑问:我并不是仅仅有 restful CRUD 的接口。当我有其他的自定义的动作的时候 angular-resource 能支持吗?今天我终于把这个问题弄清楚了。

首先,要给 angular-resource 提供一个完整的 restful 的 backend。这里我就用 rails 做了一个。

class PostsController < ApplicationController
  respond_to :json

  def index
    @posts = Post.all

    respond_with @posts
  end

  def show
    @post = Post.find_by_id(params[:id])

    respond_with @post, status: if @post.nil? then :not_found else 200 end
  end

  def create
    @post = Post.new(params[:post])
    @post.save

    respond_with @post
  end

  def update
    @post = Post.find_by_id(params[:id])
    @post.update_attributes(params[:post]) if @post

    respond_with @post, status: if @post.nil? then :not_found else 200 end
  end

  def destroy
    @post = Post.find_by_id(params[:id])
    @post.destroy if @post

    respond_with @post, status: if @post.nil? then :not_found else 200 end
  end

  def reset
    @post = Post.find_by_id(params[:id])
    if @post
      @post.body = 'reset as default content'
      @post.save
    end

    respond_with @post, status: if @post.nil? then :not_found else 200 end
  end

  def top2
    @posts = Post.limit(2)
    respond_with @posts
  end
end

这个 controller 在错误的处理以及 http code 的返回上可能还有一些问题,但是我们先忽略这些。关键的提供一个如下的 routes。

resources :posts do
  member do
    post :reset
  end
  collection do
    get :top2
  end
end
$: rake routes

reset_post POST   /posts/:id/reset(.:format) posts#reset
top2_posts GET    /posts/top2(.:format)      posts#top2
     posts GET    /posts(.:format)           posts#index
           POST   /posts(.:format)           posts#create
  new_post GET    /posts/new(.:format)       posts#new
 edit_post GET    /posts/:id/edit(.:format)  posts#edit
      post GET    /posts/:id(.:format)       posts#show
           PUT    /posts/:id(.:format)       posts#update
           DELETE /posts/:id(.:format)       posts#destroy

好,有了这些之后,我们就可以用 angular-resource 构建一个 Post 的 module 来处理 restful 的请求啦。

首先 ngResource 的文档在这里 https://docs.angularjs.org/api/ngResource.$resource 虽然大家都吐槽 angular 的文档很糟糕,但是没有别的办法,这个就是最官方的出处了。而且起码我觉得这篇讲的还算说的过去。

App = angular.module 'App', ['ngResource']

App.factory 'Post', ($resource) ->
  $resource('/posts/:id/:verb', {id: '@id'},
    reset: { method: 'POST', params: {verb: 'reset'} }
    top2: { method: 'GET', params: {verb: 'top2'}, isArray: true }
  )

如上所示,对于有自定义的 restful 接口的情况,我提供了一个额外的参数 verb。对默认的 restful 接口 angular-resource 可以帮我们处理了,我们只需要处理额外的自定义的即可。

reset: { method: 'POST', params: {verb: 'reset'} }

设置 verb: reset 就可以将这个动作和 url 绑定了。

    post = new Post($scope.post)
    post.$reset(
      (data) ->
        alert('success!!')
        $scope.post = data
    )

同理,

top2: { method: 'GET', params: {verb: 'top2'}, isArray: true }

verb: top2 就对应 posts/top2 的 url。不过这里有一个额外的参数 isArray 这个就好像 rails routes 里面声明的 collection 一样,说明不是针对单个元素的。

App.controller 'IndexController', ($scope, Post) ->
  $scope.posts = Post.top2()

虽然这里还没有涉及嵌套的情况,但是整体来说应该是类似的。

顺便说一句,项目用到了一个 gem bower-rails 把 bower 整合进来。可以更简单的安装 各种 css js 资源。https://pete-hamilton.co.uk/2013/07/13/angularjs-and-rails-with-bower/ 的讲解推荐一看。

再多说一句,前面有一篇讲述 webapps 的文章,这篇其实和它也有一点关系。目前我比较看好 angularjs 希望用它作为 webapp 的开发框架。


最近工作的总结

2013 July-02

每一段经历都是有意义的。一方面是在这个过程中的感受,另一方面是经历了这个之后的收获。最近又有些疏于码字了。赶紧补一补。记录一些最近工作的一些感想。

用类 dsl 去管理表单的展现

首先当然是讲一讲技术方面的。最近接触了一些相对来说比较复杂的表单:对,就是那种

对于一个下拉选框的每一个选项
  if option == 1 then
    表单 1
  else if option == 2 then
    表单 2
  else if
  ...
  end
end

其中,表单 1 到 表单 n 都不不太一样。然后,还要杂揉着表单验证以及自定义的表单组件。

就具体工作来讲,对每次 select change 事件做出不同的响应是不可避免的。但是,这样的代码往往很难看,并且显得臃肿而不灵活。但是,对于这种业务逻辑与代码要紧密结合的事情,又能有更好的办法么。

首先,我个人感觉,这种工作不可避免。它涉及业务逻辑。而业务逻辑就是人定义的。人的干涉不可避免。那么,就最好去简化这种工作。让人只做最关键的东西,尽量做抽象。对于目前这种工作,人需要提供的就是规则:什么时候应该显示什么表单,什么时候不应该显示什么表单。

然后,我找到了一个比较理想的 lib jquery-interdependencies。这个东西所提供的功能恰好就是我所需要的。它就是一个类似于 dsl 的东西:我告诉它当一个东西的值等于什么的时候,应该做什么。下面是其 github 页面上所提供的一个例子。

// Start creating a new ruleset
var ruleset = $.deps.createRuleset();

// 这里就是指定一条条的规则了。当一个 xx 的值 是 xx 然后就 xxx
var dietRule = ruleset.createRule("#diet", "==", "special");
dietRule.include("#special-diet");

// Make these fields visible when user checks hotel accomodation
var hotelRule = ruleset.createRule("#accomodation", "==", true);
hotelRule.include("#adults");
hotelRule.include("#children");

// Make the ruleset effective on the whole page
ruleset.install({log: true});

写这种简单的规则让我觉得比自己建立事件并配合 switch case 要好很多。我很高兴有人已经提供了这样的 lib。

但是,这里还有一个问题有待处理。如果我们使用 html 里默认的表单提交方式的话。即便是被隐藏的表单,它们的值也会被提交。这并不是我想要的。应该怎么办呢?html 有一个这样的规则,如果 form 中的一个 input 是 disabled 那么它的结果是不会被提交的。那么,我们需要在 interdependencies 帮助我们隐藏表单的同时将要隐藏的字段做 disable 处理,而在显示的时候则将其 disable 去掉即可。谢天谢地,jquery-interdependencies 给我们提供了这种 callback 的机制。

cfg =
  hide: (control) ->
    control.find('input, textarea, select').attr('disabled', true)
    control.hide()
  show: (control) ->
    control.find('input, textarea, select').attr('disabled', false)
    control.show()

这样的话,每次提交表单,其内容就是我们所需要的了。

用前端模版去渲染界面

其实之前的项目就有这么做了。但这次有多学了一招。

之前使用 handlebars 都是和 backbone 做配合。而这次呢,是要简化复杂的界面呈现。这里用到了一个 gem handlebars_assets。它可以帮助预编译前端的 handlebars 模版。而不必每次使用的时候都去使用 Handlebars compile 去重新编译。并且,利用 assets pipeline 可以将每个模版写成独立的文件。

   templates/
      contacts/
        new.hbs
        edit.hbs
        show.hbs

这样的支持让我不用在代码的组织上费什么脑筋了。

coffee 对象的封装

项目中是用 coffee 而不是用 javascript。在考虑将一坨一坨的代码按照 js 的方式

var abc = (function() {
  bla bla…

  return {
    bla bla...
  };
})();

进行的时候发现 coffee 中对应的语法显得非常古怪。我甚至想要放弃 coffee 了。但是,转念一想,coffee 简化了 js 中类的定义和使用。coffee#class 有比较详尽的说明。

用这个做封装其实刚刚好。

class DateRangeGenerator
  day = 1000 * 86400
  week = day * 7
  year = day * 365

  date_format = (date) ->
    date = new Date(+date)
    "#{date.getFullYear()}-#{date.getMonth() + 1}-#{date.getDate()}"

  generate_range = (time, num) ->
    (date_format(new Date() - time * period) for period in [0...num]).reverse()

  day_range: (num) ->
    generate_range(day, num)

  week_range: (num) ->
    generate_range(week, num)

  year_range: (num) ->
    generate_range(year, num)

上面是我建立的一个类的示例。把代码这里整理之后其实比 jquery 那种胡乱点点的函数用来用去要强很多。


下面就是吐槽了。

其实很多时候,当我们接受一个半途的项目的时候,都会有强烈吐槽的欲望。可能代码的风格不和你的意,可能项目的理念和你不相投。可能代码就是一片混乱,难以下手。但是,静下来看看,总还是可以着手去工作的。与其以一个极端对立的态度给自己打退堂鼓,不如说静下心来想办法如何应对。

当然,上一段说的有点扯淡。关键是应该要怎么想,要怎么做吧。

别人的代码搓是别人的代码。不能说别人的不好,我的也无所谓了。最起码的,别人的东西我不帮忙收拾就罢了,但是从我的开始,我会让它尽量好起来。起码要对自己的代码负责任,然后再能者多劳吧。

给别人做项目的时候要有一定的主动权。其实很多客户给你提要求的时候并不一定是深思熟虑了。他们可能也是一时随口一说。但是如果自己不好好权衡和争取的话,可能会拖慢了进度又做不出客户想要的结果。而且,有的时候真正的客户也不是给你钱的人,那么就更要多沟通,灵活点。


webapps

2013 June-15

其实我一直都很看好 web。但是,我自我感觉自己并没有盲目的看好。当然,在大家纷纷打压 webapps 的时候,我也没有很果断的说还是 native 靠谱。同时画过界面并做过 web 开发的人知道 html css javascript 三个层次分离的重大优势。界面的元素与样式分离,那么修改样式这种 +1 -1 的动作也不会那么的令人讨厌了。js 的事件驱动看起来也是相当的优雅。面对多屏幕适配的问题,responsive design 天生就是来处理这个问题的。总体来说,我很满意 html css js 的这种协作方式。不过,我还是觉得应该尽量避免拿了锤子就把什么都当钉子的2b观点。我一直在观望 web 到底是不是我需要的功能齐全的瑞士军刀。

语义标签是浮云

首先 html5 包含了很多的东西。最基本的,也是最可用的,就是各种新兴的语义标签:article header footer。这些东西没什么可说的。浏览器其实对于不认识的标签也早就有考虑。

canvas 也是浮云

然后呢,就是 canvas 这个可以独立讲的东西。其实吧,它就是一个 javascript 可以控制的像素级别的画图板。对于一些应用场景,比如图表,图片的处理,图像的拼接这些是一个大大的增强。web 可以直接在前端做那些 flash 甚至是后端才可以做的事情了。这里有一个 canvas 做滤镜的 demo https://xiebiji.com/works/QST/examples_new/imgCutMod/。对于 canvas 做游戏这方面我确实是没有什么了解。我就不瞎说了。

css3 很给力

还有就是新兴的 css3 里面的一些效果。css transition css animation css tranlation 这些虽说老的桌面浏览器并不支持。但对于移动端来说,这些功能大部分都是可用的了。尤其是像 iphone 其内置的 safari 在这方面的支持非常的好。在没有这些之前,页面的动画效果是靠 javascript 做出来的。靠 cpu 跑的话,似乎还是差一些。这就是很多人声称的

Native apps perform better than Web apps.

然而 transition 与 tranlation 在有些浏览器已经支持硬件加速,很多 native 的动画效果就差不多可以实现了。更详细的东西可以移步这里 https://mobile.smashingmagazine.com/2012/06/21/play-with-hardware-accelerated-css/

最后,很多 web 固有的,不如 native 的体验并不是不可避免的。比如 html 刷新的闪屏,我们是可以将内容通过 ajax 拿到之后通过一个动画效果展现出的。这些东西可能比较细致,听起来很麻烦,但是随着这方面的基础设施的越发齐全。我想,慢慢是可以解决的。

哦,当然,还有一个不可避免的问题。webapps 拿不到系统的一些资源。比如 地理位置,拍照,推送。这些问题可以通过 phonegap 这种的工具去解决。毕竟,目前来看,很多移动端的浏览器对于全屏浏览这样的效果支持的并不好。如果说这方面有进一步提升的话,webapps 完全可以不走 app store 而只是一个链接的快捷方式。

接下来我会花一段时间自己去尝试做一个 webapp 试水。看看是否可以做的让我满意。当然,这个 app 一开始应该不会用到那些不好获取的系统资源。先从动画以及体验着手。