Nested resource route triggers parent resource route - javascript

I have such router in my app:
TravelApi.Router.map ->
#resource 'stars', ->
#resource 'star', { path: '/:star_id' }
When I go to http://localhost/#/stars
I see in web console that there is one request
GET http://localhost:3000/stars
And when I go to http://localhost/#/stars/1
I see in web console that there are two requests
GET http://localhost:3000/stars
GET http://localhost:3000/stars/1
Question: why there are two requests in the second case?
Stars route:
TravelApi.StarRoute = Ember.Route.extend(
model: (params) ->
TravelApi.Star.find(params.star_id)
)
TravelApi.StarsRoute = Ember.Route.extend(
model: ->
TravelApi.Star.find()
)
And my templates:
application.js.hbs.hamlbars
= hb "linkTo 'stars'" do
stars
%div= hb 'outlet'
stars.js.hbs.hamlbars
%ul
=hb "each star in controller" do
%li<
=hb 'star.name'
star.js.hbs.hamlbars
Star:
= hb 'name'
store.js.coffee
TravelApi.store = DS.Store.create(
revision: 11
adapter: DS.RESTAdapter.create()
)

The way nested resources work, if the first request returns a promise (which ember data is supposed to do), the nested resource/route model function will not be called until after the ajax request will complete.
Following this logic, there should only be 1 ajax call.
TravelApi.Star.find() should create an ajax request to fetch all records
TravelApi.Star.find(1) should not require an ajax request because the record should be there.
In your case however, the star resource is not waiting for the stars resource to finish the ajax. The reason is TravelApi.Star.find() returns results immediately (which are all available records in the store which of course is empty), instead of a promise (I think).
To solve this, you can return a findQuery promise by writing the following in your model:
TravelApi.Star.find({})
This will cause the star resource to wait for the stars resource to finish its ajax request.
The route should look like this:
TravelApi.StarsRoute = Ember.Route.extend
model: ->
TravelApi.Star.find({})
TravelApi.StarRoute = Ember.Route.extend
model: (params) ->
TravelApi.Star.find(params.star_id)

It seems that you don't want to fetch all the stars when you load /star/:id
The reason why all the stars are fetched if you access /star/1 directly is because the star route is nested inside stars
The reason why you would nest your routes, is because your UI is nested. This means that when you are looking at one star, somewhere in your UI (in the side bar maybe), you are showing the list of stars. In that case, you need the request localhost:3000/stars because you need to display all the stars even if you are looking at one star. This means that the ajax requested is necessary and therefore not a problem.
If however, you are not displaying the list of all the the stars when looking at one star, then your routes shouldn't be nested in the first place. In that case, fix your routes to look like this:
TravelApi.Router.map ->
#resource 'stars'
#resource 'star', { path: '/:star_id' }

Related

Getting BadMethodCallException when using JavaScript package

I am sending a get request to a route and my controller picks up a collection from the database and then puts it into a javascript variable using Laracasts Javascript class (can be found here. It is a Php variable to javascript variable transformer- and I think it does so in a pretty standard way)
I do so like this:
$unReadNotifications = \App\Notification::join('users', 'notifications.notifier_user_id', '=', 'users.id')
->where('notifications.user_id', '=', $user->id)
->where('is_read', '=', 0)
->select('users.id as notifierId', 'users.username as username', 'notifications.id as id', 'notifications.subject as subject', 'notifications.body as body', 'notifications.notifiable_type as notifiable_type', 'notifications.notifiable_id as notifiable_id')
->orderBy('notifications.created_at', 'desc')
->with('notifiable')->get();
JavaScript::put(['unReadNotifications' => $unReadNotifications]);
and I am getting:
BadMethodCallException in Builder.php line 2405:
Call to undefined method Illuminate\Database\Query\Builder::getTagNameListAttribute()
I did a grep -r getTagNameListAttribute * and the only place it appears in my project is in a model (the model is actually related: some of my notifications belongTo a Notifiable which belongsTo a Content- this Content model is the one that has this method called getTagNameListAttribute and there is an appends array in that model which appends the output of that method to the model like so: protected $appends = ['tag_name_list'];).
The error is happening when running JavaScript::put(['unReadNotifications' => $unReadNotifications]); because when I put a dd right before this, all is fine, and anytime after I still get the error.
I am guessing that the Javascript::put is somehow calling some sort of get on a notification's notifiable's Content. This code works when all of the notifications are the kind that their notifiable have a Content, but now when the notification's notifiable does not have a content, I am getting this error. But I don't really understand why it would do that.
Any ideas as to what is going on?

"NetworkError: 404 Not Found" - $.post() with jQuery

Here is js code in our Rails 3.2 app responding to change of fields whose ids start with 'order_order_items_attributes':
$(function (){
$(document).on('change', "[id^='order_order_items_attributes'][id$='_name']", function (){
$.post(window.location, $('form').serialize(), null, "script");
return false;
});
});
The $.post() causes the error:
"NetworkError: 404 Not Found - http://localhost:3000/po/orders/new?parent_record_id=4&parent_resource=ext_construction_projectx%2Fprojects&project_id=4%22"
Here is the window.location:
If we replace $.post() with $.get(), then the code works fine and fires up the ajax response on the server:
$.get(window.location, $('form').serialize(), null, "script"); #works!
But we have to use $.post() because of the large amount of data being posted to the server. The jquery document shows that $.get() and $.post() have exactly the same format. What we missed here with $.post()?
Update
rake routes output:
Routes for PurchaseOrderx::Engine:
search_order_items GET /order_items/search(.:format) purchase_orderx/order_items#search
search_results_order_items GET /order_items/search_results(.:format) purchase_orderx/order_items#search_results
stats_order_items GET /order_items/stats(.:format) purchase_orderx/order_items#stats
stats_results_order_items GET /order_items/stats_results(.:format) purchase_orderx/order_items#stats_results
order_items GET /order_items(.:format) purchase_orderx/order_items#index
POST /order_items(.:format) purchase_orderx/order_items#create
new_order_item GET /order_items/new(.:format) purchase_orderx/order_items#new
edit_order_item GET /order_items/:id/edit(.:format) purchase_orderx/order_items#edit
order_item GET /order_items/:id(.:format) purchase_orderx/order_items#show
PUT /order_items/:id(.:format) purchase_orderx/order_items#update
DELETE /order_items/:id(.:format) purchase_orderx/order_items#destroy
search_orders GET /orders/search(.:format) purchase_orderx/orders#search
search_results_orders GET /orders/search_results(.:format) purchase_orderx/orders#search_results
stats_orders GET /orders/stats(.:format) purchase_orderx/orders#stats
stats_results_orders GET /orders/stats_results(.:format) purchase_orderx/orders#stats_results
event_action_order GET /orders/:id/event_action(.:format) purchase_orderx/orders#event_action
acct_approve_order PUT /orders/:id/acct_approve(.:format) purchase_orderx/orders#acct_approve
acct_reject_order PUT /orders/:id/acct_reject(.:format) purchase_orderx/orders#acct_reject
gm_approve_order PUT /orders/:id/gm_approve(.:format) purchase_orderx/orders#gm_approve
gm_reject_order PUT /orders/:id/gm_reject(.:format) purchase_orderx/orders#gm_reject
gm_rewind_order PUT /orders/:id/gm_rewind(.:format) purchase_orderx/orders#gm_rewind
submit_order PUT /orders/:id/submit(.:format) purchase_orderx/orders#submit
list_open_process_orders GET /orders/list_open_process(.:format) purchase_orderx/orders#list_open_process
orders GET /orders(.:format) purchase_orderx/orders#index
POST /orders(.:format) purchase_orderx/orders#create
new_order GET /orders/new(.:format) purchase_orderx/orders#new
edit_order GET /orders/:id/edit(.:format) purchase_orderx/orders#edit
order GET /orders/:id(.:format) purchase_orderx/orders#show
PUT /orders/:id(.:format) purchase_orderx/orders#update
DELETE /orders/:id(.:format) purchase_orderx/orders#destroy
root / purchase_orderx/orders#index
Here is the rake routes output for purchase order engine. Most of the routes are not relevant to the question and still listed as it is.
here is routes.rb:
resources :order_items do
collection do
get :search
get :search_results
get :stats
get :stats_results
end
end
resources :orders do
collection do
get :search
get :search_results
get :stats
get :stats_results
end
end
Workflow related actions were removed in routes.rb for easy read.
Your backend routing is not properly routing that URL to a valid controller when using the POST HTTP verb. In the root of your Rails project in the terminal, run rake routes to see all available routes, and where they end up. Without seeing your routes.rb I can't explain exactly what's wrong, but it's definitely a backend routing issue.
I wouldn't recommend what some of the comments are saying, to just "stick this in routes.rb and it will work". Your routes should be well maintained and using the correct Route helper for the job. If you throw misc. routes in there to solve problems as they come up, you'll end up with a pile of spaghetti for your routing, and maintenance of your application will become more difficult over time.
Edit: Updated to reference the update from the question
The current page URL is /po/orders/new. Looking at your rake routes output, this maps to new_order_path, evidenced by this row:
new_order GET /orders/new(.:format) purchase_orderx/orders#new
If you look directly above it, you'll see the real route for the create action:
POST /orders(.:format) purchase_orderx/orders#create
This create action is a POST to orders_path, which resolves as /po/orders/. If you POST to this URL, everything should work. If you want to be able to post to URL you're currently using and have it work, just modify your routes.rb to match this:
resources :order_items do
collection do
get :search
get :search_results
get :stats
get :stats_results
end
end
resources :orders do
# Manually route POSTs to /new to the create action
post "/new", :controller => :orders, :action => :create
collection do
get :search
get :search_results
get :stats
get :stats_results
end
end
Now, when you make a POST to this URL (/po/orders/new, it will hit the OrdersController create method. You can still hit this method by POSTing to /po/orders as well (as I recommended above).

Making code using JavaScript for dependent selects RESTful

Ruby 2.0.0, Rails 4.0.3, Windows 8.1 Update, PostgreSQL 9.3.3
I have code that uses JavaScript to power dependent selects. To do so, it references a controller method that retrieves the data for the following select. I'm told that, because that method is non-standard, this is not RESTful.
I understand that REST is a set of specific constraints regarding client/server communications. I've read some information about it but certainly don't have in-depth knowledge. I am curious about the impact and resolution. So, regarding the question about my configuration and REST: First, would that be accurate that it is not RESTful? Second, how does that impact my application? Third, what should/could I do to resolve that? Providing one example:
The route is: (probably the concern?)
post 'cars/make_list', to: 'cars#make_list'
This is the first select: (OBTW, I use ERB but removed less than/percent)
= f.input(:ymm_year_id, {input_html: {form: 'edit_car', car: #car, value: #car.year}, collection: YmmYear.all.order("year desc").collect { |c| [c.year, c.id] }, prompt: "Year?"})
This is the dependent select:
= render partial: "makes", locals: {form: 'edit_car', car: #car}
This is the partial:
= simple_form_for car,
defaults: {label: false},
remote: true do |f|
makes ||= ""
make = ""
make = car.make_id if car.class == Car and Car.exists?(car.id)
if !makes.blank?
= f.input :ymm_make_id, {input_html: {form: form, car: car, value: make}, collection: makes.collect { |s| [s.make, s.id] }, prompt: "Make?"}
else
= f.input :ymm_make_id, {input_html: {form: form, car: car, value: make}, collection: [], prompt: "Make?"}
end
end
JS:
$(document).ready(function () {
...
// when the #year field changes
$("#car_ymm_year_id").change(function () {
var year = $('select#car_ymm_year_id :selected').val();
var form = $('select#car_ymm_year_id').attr("form");
var car = $('select#car_ymm_year_id').attr("car");
$.post('/cars/make_list/',
{
form: form,
year: year,
car: car
},
function (data) {
$("#car_ymm_make_id").html(data);
});
return false;
});
...
});
And the method:
def make_list
makes = params[:year].blank? ? "" : YmmMake.where(ymm_year_id: params[:year]).order(:make)
render partial: "makes", locals: {car: params[:car], form: params[:form], makes: makes}
end
If I had to describe if, being RESTful means that:
You provide meaningful resources names
You use the HTTP verbs to express your intents
You make proper use of HTTP codes to indicate status
Provide meaningful resource names
As you probably heard it before, everything in REST is about resources. But from the outside, it's just the paths you expose. Your resources are then just a bunch of paths such as:
GET /burgers # a collection of burgers
GET /burger/123 # a burger identified with id 123
GET /burger/123/nutrition_facts # the nutrition facts of burger 123
POST /burgers # with data: {name: "humble jack", ingredients: [...]} to create a new burger
PUT /burger/123 # with data: {name: "chicken king"} to change the name of burger 123
For instance, if you had a path with the url
GET /burger_list?id=123
That would not be considered good practice.
It means you need to think hard about the names you give your resources to make sure the intent is explicit.
Use HTTP verbs to express your intents
It basically means using:
GET to read a resource identified by an identifier (id) or a collection of resources
PUT to update a specific resource that you identify by an identifier (id)
DELETE to destroy a specific resource that you identify by an id
POST to create a new resource
Usually, in Rails, those verbs are, by convention, used to map specific actions in your controller.
GET goes to show or index
PUT goes to update
DELETE goes to destroy
POST goes to create
That's why people usually say that if you have actions in your controllers that don't follow that pattern, you're not "RESTful". But in the end, only the routes you expose count. Not really your controller actions. It is a convention of course, and conventions are useful for readability and maintainability.
You make proper use of HTTP codes to indicate status
You already know the usual suspects:
200 means OK, everything went fine.
404 means NOT FOUND, could not find resource
401 means UNAUTHORIZED, authentication failed, auth token invalid
500 means INTERNAL SERVER ERROR, in other words: kaput
But there are more that you could be using in your responses:
201 means CREATED, it means the resource was successfully created
403 means FORBIDDEN, you don't have the privileges to access that resource
...
You get the picture, it's really about replying with the right HTTP code that represents clearly what happens.
Answering your questions
would that be accurate that it is not RESTful?
From what I see, the first issue is your path.
post 'cars/make_list', to: 'cars#make_list'
What I understand is that you are retrieving a collection of car makes. Using a POST to retrieve a collection is against REST rules, you should be using a GET instead. That should answer your first question.
how does that impact my application?
Well, the impact of not being restful in your case is not very big. It's mainly about readability, clarity and maintainability. Separating concerns and putting them in the right place etc... It's not impacting performance, nor is it a dangerous issue. You're just not RESTful and that makes it more complicated to understand your app, in your case of course.
what should/could I do to resolve that?
Besides the route problem, the other issue is that your action is called make_list and that doesn't follow Rails REST conventions. Rails has a keyword to create RESTful routes:
resources :car_makes, only: [:index] # GET /car_makes , get the list of car makes
This route expresses your intent much better than the previous one and is now a GET request. You can then use query parameters to filter the results. But it means we need to create a new controller to deal with it.
class CarMakesController < ApplicationController
def index
makes = params[:year].blank? ? "" : YmmMake.where(ymm_year_id: params[:year]).order(:make)
render partial: "makes", locals: {car: params[:car], form: params[:form], makes: makes}
end
private
# Strong parameters stuff...
end
And of course we also need to change your jquery to make a GET request instead of a POST.
$(document).ready(function () {
...
// when the #year field changes
$("#car_ymm_year_id").change(function () {
// ...
$.get({
url: '/car_makes',
data: {
form: form,
year: year,
car: car
},
success: function (data) {
$("#car_ymm_make_id").html(data);
});
return false;
});
...
});
This is a much better solution, and it doesn't require too much work.
There is an excellent tutorial on REST on REST API tutorial, if you want to know more about the specifics. I don't know much about the small details, mostly what is useful on a day to day basis.
Hope this helps.

Angular.js accessing and displaying nested models efficiently

I'm building a site at the moment where there are many relational links between data. As an example, users can make bookings, which will have booker and bookee, along with an array of messages which can be attached to a booking.
An example json would be...
booking = {
id: 1,
location: 'POST CDE',
desc: "Awesome stackoverflow description."
booker: {
id: 1, fname: 'Lawrence', lname: 'Jones',
},
bookee: {
id: 2, fname: 'Stack', lname: 'Overflow',
},
messages: [
{ id: 1, mssg: 'For illustration only' }
]
}
Now my question is, how would you model this data in your angular app? And, while very much related, how would you pull it from the server?
As I can see it I have a few options.
Pull everything from the server at once
Here I would rely on the server to serialize the nested data and just use the given json object. Downsides are that I don't know what users will be involved when requesting a booking or similar object, so I can't cache them and I'll therefore be pulling a large chunk of data every time I request.
Pull the booking with booker/bookee as user ids
For this I would use promises for my data models, and have the server return an object such as...
booking = {
id: 1,
location: 'POST CDE',
desc: "Awesome stackoverflow description."
booker: 1, bookee: 2,
messages: [1]
}
Which I would then pass to a Booking constructor, which would resolve the relevant (booker,bookee and message) ids into data objects via their respective factories.
The disadvantages here are that many ajax requests are used for a single booking request, though it gives me the ability to cache user/message information.
In summary, is it better practise to rely on a single ajax request to collect all the nested information at once, or rely on various requests to 'flesh out' the initial response after the fact.
I'm using Rails 4 if that helps (maybe Rails would be more suited to a single request?)
I'm going to use a system where I can hopefully have the best of both worlds, by creating a base class for all my resources that will be given a custom resolve function, that will know what fields in that particular class may require resolving. A sample resource function would look like this...
class Booking
# other methods...
resolve: ->
booking = this
User
.query(booking.booker, booking.bookee)
.then (users) ->
[booking.booker, booking.bookee] = users
Where it will pass the value of the booker and bookee fields to the User factory, which will have a constructor like so...
class User
# other methods
constructor: (data) ->
user = this
if not isNaN(id = parseInt data, 10)
User.get(data).then (data) ->
angular.extend user, data
else angular.extend this, data
If I have passed the User constructor a value that cannot be parsed into a number (so this will happily take string ids as well as numerical) then it will use the User factorys get function to retrieve the data from the server (or through a caching system, implementation is obviously inside the get function itself). If however the value is detected to be non-NaN, then I'll assume that the User has already been serialized and just extend this with the value.
So it's invisible in how it caches and is independent of how the server returns the nested objects. Allows for modular ajax requests and avoids having to redownload unnecessary data via its caching system.
Once everything is up and running I'll write some tests to see whether the application would be better served with larger, chunked ajax requests or smaller modular ones like above. Either way this lets you pass all model data through your angular factories, so you can rely on every record having inherited any prototype methods you may want to use.

How do I correct [object%20Object] in URL path when using EmberJS transitionToRoute from Controller?

I'm attempting to setup an action in a template to transition users to another route based on a dynamically generated navbar, using a combination of json / and js that is executed client side. I've managed to configure my application so that I can generate regular paths that work, but I would like to use transitions to avoid additional page loads.
A gist with what is (hopefully) the necessary information is:
https://gist.github.com/rjfranco/5289201
JS:
App.Router.map ->
#route 'index', {path: "/#{short_name}"}
#route 'customList', {path: "/#{short_name}/custom-list/:list_name"}
#route 'multiLevelList', {path: "/#{short_name}/multi-level-list/:list_name/:list_level"}
App.EventController = Em.ObjectController.extend
goTo: (navbar_icon) ->
#transitionToRoute navbar_icon.route, navbar_icon.context
# Sample passed navbar_icon
navbar_icon =
img_src: '/somesource_or_another.jpg'
display_name: 'My Link Name!'
route: 'customList'
context: {list_name: 'Exhibitors'}
template:
{{#each controller.navbarIcons}}
<li><a {{action goTo this}}><img {{bindAttr src="image_src"}} width="36" height="36" />{{display_name}}</a></li>
{{/each}}
The expected page actually loads fine, but the url the application transitions to is not correct, and refreshing breaks the application.
I'm expecting a URL along the lines of: /ruhaha/custom-list/Exhibitors
and I end up with one like: /ruhaha/custom-list/[object%20Object]
Slight update:
I've found that I could abuse a couple of methods on the app to produce the results I want:
App.handleURL('/ruhaha/custom-list/Exhibitors')
App.Router.router.replaceURL('/ruhaha/custom-list/Exhibitors')
This will actually trigger the route, and replace the url effectively transitioning my application to the state I want without a page request. Although this works I'm pretty certain this is the wrong way to take care of this issue.
I ended up resolving this issue (with some help from nerdyworm on freenode) by specifying a serialize method in my route that just returned the param I was expecting:
App.CustomListRoute = Em.Route.extend
serialize: (params) ->
params

Categories

Resources