Reputation: 390
First of all,I would say sorry for my broken English and the broken codes...(many words here come from google translation...so, I'm afraid that I can't make myself clear...so, I paste all the codes...)
Setting up routes in rails is really easy. But when we want to warp it into angurjs, it becomes a little bit verbose... Is there any 'best practice' for this kinds of job:
given some rails route resources:
resources :users
resources :photos
...
resources :topics
How to do it in angular side?
This is how I do (in coffee script, useing angular 1.1.4):
# 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)
]
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
])
There are 2 problems here: The first problem is that I must copy && paste the above loader code and then gsub('user', 'photo'), etc... (Just change 2 words in fact, but I hate copy && paste..) I have tried moving it in a for loop which didn't work and I can't figure out why... Another problem is how to set up the nested resources,in a DRY way
Finally, thanks for your patience and I would say sorry again... But any solution, suggestion or best practices? Am I doing it wrong?
Upvotes: 4
Views: 2114
Reputation: 1931
I try to avoid complex services like:
'/parents/:parent_id/children/:id'
I separate this in two services:
/parents/:parent_id/children. For GET(list) and the POST(Id in Content)
And then I have
/children/id for the PUT,DELETE and single GET
Upvotes: 0
Reputation: 390
Well ,I figure out why the Loader for resolve doesn't work as expected... The key is that an angularjs service is a sigleton. It doesnt work because I put the loop inside the service definition in the first version...
for sort, this works:
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
])
Upvotes: 0
Reputation: 1732
Apart that your description of the question is very complicated (tons of code), I think you missed the point of REST API concept.
You are mixing your Rails backend resources (and its routing) with client-side routing. This is wrong IMHO. Your frontend app routes shouldn't be jumping (shadowing) over REST routes.
If you are fetching users
resource (for example GET /users/5.json
) you shouldn't navigate your client to /tp/users/show
. Your REST and client-side routes are two separate, independent abstractions.
Your client-side routes should be like
/
/userprofile
/dashboard
/articles/2013?page=4
And your Controllers sitting on those routes should use your resource services to fetch required data from REST API like this:
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)
Upvotes: 3