What's the correct way to use Cakephp urls? - url

it's my first post here :)
I'm having some difficulties with dealing with urls and parameters. I've gone through the router class api documentation over and over again and found nothing useful.
First of all, I'd like to know if there is any 'universal' format in CakePHP(1.3) for handling urls. I'm currently handling all my urls as simple arrays(in the format that Router::url and $html->link accepts) and it's easy as long as I only need to pass them as arguments to cake's own methods. It usually gets tricky if I need something else.
Mainly I'm having problems with converting string urls to the basic array format.
Let's say I want to convert $arrayUrl to string and than again into url:
$arrayUrl=array('controller'=>'SomeController','action'=>'someAction','someValue');
$url=Router::url($arrayUrl); //$url is now '/path/to/site/someController/someAction/someValue'
$url=Router::normalize($url); //remove '/path/to/site'
$parsed=Router::parse($url); /*$parsed is now
Array(
[controller] => someController
[action] => someAction
[named] => Array()
[pass] => Array([0] => someValue)
[plugin] =>
) */
That seems an awful lot of code to do something as simple as to convert between 2 core formats. Also, note that $parsed is still not in the same as $arrayUrl. Of course I could tweak $parsed manually and actually I've done that a few times as a quick patch but I'd like to get to the bottom of this.
I also noticed that when using prefix routing, $this->params in controller has the prefix embedded in the action(i.e. [action] => 'admin_edit') and the result of Router::parse() does not. Both of course have the prefix in it's own key.
To summarize, how do I convert an url between any of these 3(or 4, if you include the prefix thing) mentioned formats the right way?
Of course it would be easy to hack my way through this, but I'd still like to believe that cake is being developed by a bunch of people who have a lot more experience and insight than me, so I'm guessing there's a good reason for this "perceived misbehavior".
I've tried to present my problem as good as I can, but due to my rusty english skills, I had to take a few detours :) I'll explain more if needed.

The "official" format for Cake URLs should be the array notation array('controller' => 'foo', 'action' => 'bar', 'baz', 'admin' => true). Whenever you write URLs, you should use this format. The Router class will translate those to either strings (/admin/foo/bar/baz) or information needed for the Dispatcher (array('named' => array(), 'pass' => array(), …)), depending on where the URL is used.
You should think of it in terms of which controller action you want to invoke. URLs (as strings) are only a necessary evil to accomplish this in a web context. There shouldn't be any need for you to use the Dispatcher format. You should also not use the string notation when specifying URLs, since these can't be reverse-routed if you ever want to change your URL scheme.
Maybe you could explain with an example why you need to convert these three forms from one to the other?

Related

Are There Any Rails Modules or Classes Which Provide Frozen HTML Content Type Strings?

Ive been searching through source for a while, and it appears to me that there are no given Rails tools for retrieving the String representation of various HTML content types. Ive also found this to be a very difficult concept to search for in general.
What I want is something like this:
Mime::Mimes::CONTENT_TYPE_JSON = 'application/json'.freeze
or, Mime::Mimes::CONTENT_TYPES[:json] etc.
...because I want to do a lot of things like some_value == 'application/json' or some_value = 'application/json' etc.
I want to use the expression "application/json" often, and I dont want to create new String instances for something that is pretty well within the domain of web application development. Ive thought of creating my own app consts or vars so I dont have to allocate HTML Content Type strings more than once, but also feel this should just be available for me in any web application framework (at least, those written in languages where every string is a new memory allocation).
Is there a better tool or resource within the Rails 5 source that I am missing that allows easy retrieval of content type strings? Do I have to get a gem / create my own for this?
Note: Im away of how heavy of an "optimization" this may appear to be. Let's then entertain this query from a position of being pragmatic about organizational style, for a project that requires elimination of any duplication of domain-specific string literals, and to keep them symbolized or as some frozen const. Let's pretend its a personal project for the sheer joy of experimenting with such style!
There is a shorthand for it:
Mime[:json]
Mime#[] -
https://github.com/rails/rails/blob/e2efc667dea886e71c33e3837048e34b7a1fe470/actionpack/lib/action_dispatch/http/mime_type.rb#L41
which uses
Mime::Type#lookup_by_extension -
https://github.com/rails/rails/blob/e2efc667dea886e71c33e3837048e34b7a1fe470/actionpack/lib/action_dispatch/http/mime_type.rb#L149
If you want to get the actual content type you might need to call a #to_s on it:
Mime[:json].to_s
Creating a new module to facilitate simple storage and retrieval using the ActionPack Mime::Type system would work as follows:
# Build hash of type name to value, e.g., { xml: "application/xml" }
CONTENT_TYPES = {}.tap do |simple_content_types_hash|
# Get each registered Mime Type
Mime::EXTENSION_LOOKUP.each do |mime|
simple_content_type_hash[mime.first.to_sym] = mime.last.instance_variable_get("#string").freeze
end
end.freeze
Note: the above is untested, its just a generalization of what I am looking for. Thanks to #Fire-Dragon-DoL for the tip.
This could be added via an initializer, patched into an existing module, or into a new helper module.

a nested route or done as a parameter

I have the following call:
http://localhost:3000/arc/v1/api/menus/51/only_items_with_notes
and I'm curious what is the preferred structure of the url - This reads fine and is totally clear what it means. But I am not sure if this is the canonical way to do this. One issue is that it does proliferate the routes.rb file. I have:
get '/menus/:menu_id/only_items_with_notes' => 'api_menus#only_items_with_notes'
One think I don't like is that it reads a bit like a pseudo nested attribute. What is the proper, canonical way to do this?
That’s pretty deep nesting. What other routes do you have?
In the absence of more information, I’d suggest that only_items_with_notes is really a filter on the functionality of the index action. You can use a query parameter to restrict the items to those with notes.

Rails 3 - Friendly params in url (GET)

I have a rails 3 app and now i implementing filter for my catalog. Filters form pass data to controller through GET request. As a result i have link like this in my browser after i submit
my form (apply search):
http://localhost:3001/shoes?filter%5BShoeBottomType%5D%5B%5D=2&filter%5BShoeClassification%5D%5B%5D=1&filter%5BShoeClassification%5D%5B%5D=2&filter%5BShoeElation%5D%5B%5D=3&filter%5BShoeElation%5D%5B%5D=4&filter%5BShoeElation%5D%5B%5D=5&filter%5BShoeLiningColor%5D%5B%5D=2&filter%5BShoeLiningColor%5D%5B%5D=3&filter%5BShoeLiningColor%5D%5B%5D=4&filter%5BShoeTopColor%5D%5B%5D=1&filter%5BShoeTopColor%5D%5B%5D=2&filter%5Bonly_action%5D%5B%5D=1&page=2
Is there a way to do URL more beautiful?
PS i dont want use POST request, because I read that it is bad for SEO
TLDR: just leave it.
HTML forms serialize in a straightforward manner; the parameters are named after the HTML elements. The actual issue here is how the form elements are named. It looks like they have names like filter[ShoeBottomType][]; look into your HTML to see the name attributes. Since you're in Rails, I'm guessing you having a filter hash passed to your Rails controller method as a single argument, and since Rails expects hashes to use a certain URL format for hashes and arrays (it has to know how to deserialize it from the request), the form helper writes the form that way. And yours is especially complicated because the hash values are arrays, hence the extra set of brackets. Then it's URL encoded and you end up with an ugly mess.
You could avoid some of this problem by passing the inputs individually back to the controller instead of as a big hash. Something like:
def index
shoe_bottom_types = params[:bottom_types]
shoe_classifications = params[:classifications]
shoe_elations = params[:elations]
...
which will get you to: /shoes?bottomTypes[]=1&bottomTypes[]=2.... That doesn't seem much better, and now your controller is all gross. And I don't see how you're going to get rid of the brackets entirely if you want to have more than one of the same filter. I guess you could get crazy and do your own parsing in your controller, like breaking apart shoeBottomTypes=1|2, but then you'll have to do your own form serialization too. Again, just not worth it.
Backing up for a sec, the SEO stuff doesn't make much sense. Search engines won't fill out your form; they just follow links. The real reason you should use GET is that (presumably), submitting your form doesn't have side effects, since it's just a search. See here; it's important to use the right HTTP methods. If you use POST, you'll get weird warnings on reloads and you won't be able to bookmark the search.
Backing up even further, why do you care, especially now that SEO is out of the picture? Just as a quick demo, I did a google search for the word "thing" and this was the URL:
https://www.google.com/#hl=en&output=search&sclient=psy-ab&q=thing&pbx=1&oq=thing&aq=f&aqi=g2g-s1g1&aql=1&gs_sm=3&gs_upl=764l1877l0l1980l6l6l0l0l0l0l89l432l5l5l0&bav=on.2,or.r_gc.r_pw.r_cp.r_qf.,cf.osb&fp=220ef4545fdef788&biw=1920&bih=1086
So URLs for form submissions can be long. The user won't even look at it.
The only possibility I can think of for why you'd care about the length/ugliness of your URL here is that you want, separately from the form, to create links to certain searches. There are several ways to handle that, but since I don't know whether that's relevant to you, I'll let that be a follow-up.
So bottom line, it looks like I'd expect, and trying to fix it sounds ugly and pointless.
If you do not want to use a POST request, then there is no other way then to put the form values in the URL -- they have to get to the server one way or another.
On the other hand however, I do not see why doing a POST would be bad for SEO and I would love to see the article that stated so.
My suggestion is that you could add some custom routes to beautify your urls.
For example :
http://localhost:3001/shoes/Type/2/Classification/1,2/Elation/3,4,5/LiningColor/2,3,4/TopColor/1,2/only_action/1/page/2
This is far much shorter than your initial URL ;)
The counterpart is that, as far as I know, you have to use always the same order for params in your url.
The routing rule is the following :
match "shoes/Type/:type/Classification/:classification/Elation/:elation/LiningColor/:liningcolor/TopColor/:topcolor/only_action/:only_action/page/:page" => "shoes#show"
You can retrieve the passed values in params array. You have to split the string containing , in order to retrieve the multiple values.

Cakephp url customisation for SEO

I am developing a medical products search site. I need to display my site in search engines whenever a user try to search [COMPANY NAME] medicine [DISEASE]. For this, i created a page in my site which reads COMPANY NAME and DISEASE from url and lists all products.
Now i need to give this page a url like www.sitename.com/[COMPANY NAME]_medicine_[DISEASE].html
I am using Cakephp framework for development. Is there anyway to implement this url formatting in routes.php ? Or is there any other way ? please help.
how about separating them with slashes?
// www.sitename.com/[COMPANY NAME]/medicine/[DISEASE]
Router::connect('/:company/medicine/:disease', array('controller' => 'diseases', 'action' => 'index'),
array('pass'=>array('company','disease'),
'company'=>"[a-zA-Z\.]+*",
'disease'=>'[a-zA-Z\.]+'));
and the controller
function diseases($company,$disease){
}
I'm not sure if you can use the underscore instead of the slashes, I have never tried it before. if you do try I'd like to know the results =)
Good Luck
EDITED: ok, i was too curious about this issue and i wrote a route like this
Router::connect('/:company_medicine_:disease', array('controller' => 'pages', 'action' => 'test'),
array('pass'=>array('company','disease'),
'company'=>'[a-zA-Z]+',
'disease'=>'[a-zA-Z]+'));
and its not working u_U
as i suspected, the problem is that Cake thinks that the name of the custom route element is :company_medicine and not :company.. after a few minutes regarding/reading the code of Cake i found out the exact place where Cake parses the route and extracts the names of the passed elements. It's in /cake/libs/router.php in the class CakeRoute, method _writeRoute() (about line 1369):
preg_match_all('#:([A-Za-z0-9_-]+[A-Z0-9a-z])#', $parsed, $namedElements);
so as you can see in the regexp, the names of the elements can contain an underscore,therefore Cake thinks the name of the parameter is ":company_medicine".
So you have four solutions:
use slashes as separators for your urls
change the order of your parameter so it would be medicine_[COMPANY]_[DISEASE]
modify the line 1369 of the router.php to this (NOT RECOMENDED, i think it will break routes for plugins):
preg_match_all('#:([A-Za-z0-9]+)#', $parsed, $namedElements);
use url rewrite in your .htaccess to redirect all [COMPANY]_medicine_[DISEASE] to [COMPANY]/medicine/[DISEASE] so cake will see it separated by slashes and the browser will see it separated bu underscores. (I haven't tested it, i've never added another rule to the .htaccess for Cake =P)

What is the thing I'm trying to do called?

I think there must be a name for whatever it is I'm trying to do. I want to have a list of... requests and what the responses should look like. But the different requests and responses are not formatted the same. They are simple strings, so here's some examples:
request => response
foo%% => "OK"
foo? => "%%"
%%%%bar => "OK"
Version? => "Baz-%%%%"
Where % could be a number or letter.
I can code for each possible command with a big switch/case, but I wanted to make it more extend-able and maybe testable. Sorry this question is so vague. Please rename it and/or tag it correctly.
I'm doing this in javascript/node if it matters.
I don't know what you're inventing but it sounds like regular expressions.
You would call % a wildcard in this case.
Your code will need parse the input to verify syntactic correctness and output a data structure that represents the input. Then you will need to analyze your intermediate data structure to determine semantics. The simplest way would be to use objects that implement a common interface.
object FooRequest (value)
implements Request
method Process ()
return "OK"

Resources