【问题标题】:How can we use rails routes with angularjs in a DRY way?我们如何以 DRY 的方式使用带有 angularjs 的 rails 路由?
【发布时间】:2013-04-26 01:23:27
【问题描述】:

首先,我很抱歉我的英文和代码被破坏了......(这里的很多词来自谷歌翻译......所以,我怕我不能说清楚......所以,我粘贴所有的代码...)

在 Rails 中设置路线非常简单。但是当我们想把它变形为 angurjs 时,它变得有点冗长......这种工作是否有任何“最佳实践”:

给定一些 Rails 路线资源:

resources :users
resources :photos
...
resources :topics

斜边怎么做?

这就是我的做法(在咖啡脚本中,使用 angular 1.1.4):

以 Rails 方式使用 RESTful 服务:

# angular/services/restful.js.coffee
# RESTful resource following Rails route's convention
# index:  GET  '/resource.json'
# save:   POST '/resource.json'
# get:    GET  '/resource/:id.json'
# update: PUT  '/resource/:id.json'
# edit:   GET  '/resource/:id/edit.json'
# new:    GET   just use get, id: 'new'
app.factory('RESTful', ['$resource',
  ($resource)->
    (resource_name) ->
      url = "/#{resource_name}/:id:format"
      defaults={format: '.json', id: '@id'}

      actions = {
        index:
          id: ''
          url: "/#{resource_name}:format"
          method: 'GET'
          isArray:false
        edit:
          url: "/#{resource_name}/:id/edit:format"
          method: 'GET'
        update:
          method: 'PUT'
        save:
          url: "/#{resource_name}:format"
          method: 'POST'
      }

      $resource url, defaults, actions
])

# index:  GET  '/parents/:parent_id/children.json'
# save:   POST '/parents/:parent_id/children.json'
# get:    GET  '/parents/:parent_id/children/:id.json'
# update: PUT  '/parents/:parent_id/children/:id.json'
# edit:   GET  '/parents/:parent_id/children/:id/edit.json'
# new:    GET   just use get, id: 'new'
app.factory('NESTful', ['$resource',
  ($resource)->
    (parents, children) ->
      # naive singularize
      parent = parents.replace(/s$/, '')
      url = "/#{parents}/:#{parent}_id/#{children}/:id:format"

      defaults={ format: '.json', id: '@id' }

      actions = {
        index:
          id: ''
          url: "/#{parents}/:#{parent}_id/#{children}:format"
          method: 'GET'
          isArray:false
        edit:
          url: "/#{parents}/:#{parent}_id/#{children}/:id/edit:format"
          method: 'GET'
        update:
          method: 'PUT'
        save:
          url: "/#{parents}/:#{parent}_id/#{children}:format"
          method: 'POST'
      }

      $resource url, defaults, actions
])

路线:

app.config(['$routeProvider', '$locationProvider', ($routeProvider, $locationProvider) ->
  $locationProvider.html5Mode(true)

  # general RESTful routes
  resource_list = ['users', 'photos', 'topics']
  for resource in resource_list
    # naive singularize
    singular = resource.replace(/s$/, "")
    captialize = singular.charAt(0).toUpperCase() + singular.slice(1)
    $routeProvider
      .when "/#{resource}",
          templateUrl: "/tp/#{resource}/index"
          controller: "#{captialize}IndexCtrl"
          resolve:
            index: ["#{captialize}Loader", (Loader)-> Loader('index')]
      .when "/#{resource}/new",
          templateUrl: "/tp/#{resource}/edit"
          controller: "#{captialize}NewCtrl"
          resolve:
            resource: ["#{captialize}Loader", (Loader)-> Loader('new')]
      .when "/#{resource}/:id",
          templateUrl: "/tp/#{resource}/show"
          controller: "#{captialize}ShowCtrl"
          resolve:
            resource: ["#{captialize}Loader", (Loader)-> Loader('show')]
      .when "/#{resource}/:id/edit",
          templateUrl: "/tp/#{resource}/edit"
          controller: "#{captialize}EditCtrl"
          resolve:
            resource: ["#{captialize}Loader", (Loader)-> Loader('edit')]

  # special routes
  $routeProvider
    .when '/',
        templateUrl: '/tp/pages/home'
        controller: 'PageCtrl'

    .otherwise({redirectTo: '/'})
])

控制器应该超级干净,然后我们可以专注于业务

app.controller 'UserShowCtrl', ['$scope', 'resource'
  ($scope, resource)->
    $scope.user = resource.user
]
app.controller 'UserIndexCtrl', ['$scope', 'index',
  ($scope, index) ->
    $scope.users = index.resource.users
    $scope.total_pages = index.pages.total_pages
    $scope.currentPage = index.pages.current_page

    getPage = (page)->
      index.resource.$index
        page: page
        (resource, headers)->
          $scope.users = resource.users

    $scope.$watch 'currentPage', (newval)->
      getPage(newval)
]

问题出在Loader for resolve obj:

app.factory('UserLoader', ['RESTful', '$route', '$q',
  (RESTful, $route, $q) ->
    (action)->
      model = 'users'
      delay = $q.defer()
      fetcher = RESTful(model)
      switch action
        when 'index'
          fetcher.index
            page: $route.current.params.page
            (resource, headers)->
              delay.resolve
                resource: resource
                pages: JSON.parse(headers('X-Pagination'))
            (resource)->
              delay.reject "Unable to fetch #{model} index"
        when 'show'
          fetcher.get
            id: $route.current.params.id
            (resource)->
              delay.resolve(resource)
            (resource)->
              delay.reject "Unable to fetch #{model} #{$route.current.params.id}"
        when 'edit'
          fetcher.edit
            id: $route.current.params.id
            (resource)->
              delay.resolve(resource)
            (resource)->
              delay.reject "Unable to fetch #{model} #{$route.current.params.id} edit"
        when 'new'
          fetcher.get
            id: 'new'
            (resource)->
              delay.resolve(resource)
            (resource)->
              delay.reject "Unable to fetch #{model} new"

      return delay.promise
])

这里有两个问题: 第一个问题是我必须复制 && 粘贴上面的加载器代码,然后 gsub('user', 'photo') 等... (其实只是改了2个字,但我讨厌复制&&粘贴..) 我试过将它移到一个不起作用的 for 循环中,我不知道为什么...... 另一个问题是如何以 DRY 方式设置嵌套资源

最后,感谢您的耐心等待,再次向您说声抱歉... 但是有任何解决方案、建议或最佳实践吗? 我做错了吗?

【问题讨论】:

  • 这个问题太大了,无法尝试做出合理的回答 - 正如您目前所看到的那样。您可以尝试在 codereview.stackexchange.com 上询问,因为该网站专注于代码样式

标签: ruby-on-rails angularjs routes


【解决方案1】:

除了您对问题的描述非常复杂(大量代码)之外,我认为您错过了 REST API 概念的重点。

您将 Rails 后端资源(及其路由)与客户端路由混合在一起。这是错误的恕我直言。您的前端应用程序路由不应跳过(遮蔽)REST 路由。 如果您正在获取users 资源(例如GET /users/5.json),则不应将您的客户端导航到/tp/users/show。您的 REST 和客户端路由是两个独立的抽象。

你的客户端路由应该是这样的

/
/userprofile
/dashboard
/articles/2013?page=4

你的控制器在这些路由上应该使用你的资源服务从 REST API 获取所需的数据,如下所示:

angular.module('myApp').controller 'UserProfileController', ($scope, userResource) ->
  $scope.user = userResource.query({id: SOME_ID})

angular.module('myApp.resources').factory 'userResource', ($resource) ->
  params = {
    id: '@id'
    subItem: '@subItem'
    subItemId: '@subItemId'
  }

  actions = {
    query: {
      method: 'GET'
      params: {}
      isArray: true
    }
    ban: {
      method: 'POST'
      params: {banned: true}
    }
  }

  return $resource('/api-1.0/users/:id/:subItem/:subItemId', params, actions)

【讨论】:

  • 嗯,/tp/user/show 是 templateUrl 而 templateUrl 绝对不是客户端路由。事实上,你永远不应该在 templateUrl 中使用相同的路由。因为当用户点击搜索引擎的链接时,比如/users,服务器只会返回index.html,页面在客户端渲染,然后angular会寻找/users的templateUrl,不能为/users,返回index.html(这将导致无限循环并导致浏览器崩溃)
  • 关于获取数据,好吧,使用解析对象而不是直接在控制器中加载数据会提供更好的用户体验(它可以防止在数据完全加载之前显示损坏的页面)见this question
  • 好的,感谢 cmets。早餐时我很轻松地研究了你的代码。
【解决方案2】:

好吧,我知道为什么解析的加载器不能按预期工作......关键是 angularjs 服务是一个单例。 它不起作用,因为我将循环放在第一个版本的服务定义中......

对于排序,这是可行的:

angular.forEach [
  ['profile', 'profile']
  ['user', 'users']
  ['avatar', 'avatars']
],
(resouce_definition)->
  resource = resouce_definition[1]
  singular = resouce_definition[0]
  captialize = singular.charAt(0).toUpperCase() + singular.slice(1)
  app.factory("#{captialize}Loader", [
    'RESTful', '$route', '$q'
    (RESTful, $route, $q)->
      (action)->
        delay = $q.defer()
        id = $route.current.params.id
        fetcher = RESTful(resource)
        switch action
          when 'index'
            fetcher.index
              page: $route.current.params.page
              (response, headers)->
                delay.resolve
                  resource: response
                  pages: JSON.parse(headers('X-Pagination'))
              (response)->
                delay.reject "Unable to fetch #{resource} index"
          when 'show'
            fetcher.get
              id: id
              (response)->
                delay.resolve(response)
              (response)->
                delay.reject "Unable to fetch #{resource} #{id}"
          when 'edit'
            fetcher.edit
              id: id
              (response)->
                delay.resolve(response)
              (response)->
                delay.reject "Unable to fetch #{resource} #{id}"
          when 'new'
            fetcher.get
              id: 'new'
              (response)->
                delay.resolve(response)
              (response)->
                delay.reject "Unable to fetch #{resource} new"

        return delay.promise
  ])

【讨论】:

    【解决方案3】:

    我尽量避免使用复杂的服务,例如: '/parents/:parent_id/children/:id'
    我将其分为两个服务:
    /parents/:parent_id/孩子。对于 GET(list) 和 POST(Id in Content) 然后我有 /children/id 用于 PUT、DELETE 和单个 GET

    【讨论】:

      猜你喜欢
      • 2012-04-10
      • 1970-01-01
      • 2018-10-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-19
      • 1970-01-01
      相关资源
      最近更新 更多