Permanent URL Parameters - ruby-on-rails

I have been building a fashion e-commerce app that is quite far along the development cycle. It caters for both men's and women's products. At the moment, the user selects their gender at the start, and a gender_id is then passed into the user's session. This ID is then used in many of the queries throughout the site, and it determines which gender products the user is shown.
However, for SEO purposes, it is necessary that this information is shown in the URL and not the session. I do not want to have to pass a gender parameter for every link I do, like this...
http://www.shop.com/products?category=Jeans&gender=women
Essentially what I'd like to do is input the gender into the route, and have it stay. I have seen some sites where the URL is structured as follows...
http://www.shop.com/women/products?category=Jeans
How can I achieve this second URL structure with minimal impact to my controllers? Or perhaps there is a different way I could achieve my goal? Thanks!

Put it in your routes with a scope:
scope ':gender', :constraints => {:gender => /women|men/} do
resources :products
resources :cart
# other routes here
end
You can access gender with params[:gender]. Any routes you place in that block will be scoped to a gender context.
Additionally, the gender scope will default to the current scope when generating urls. For example, if I browse to /men/products and in that view I have a link to cart, if the url is generated with cart_path the url will be /men/cart. Once the select a gender you could write redirect them to the proper scoped path. The one problem I see is that you lose the unstopped routes for product and cart with this method.

You can pass in default GET parameters to your routes. For example, the pattern below would match http://www.shop.com/women/products?category=Jeans and automatically append gender=women to your params hash.
get '/women/products' => 'Products#index', :gender => 'women'
get '/men/products' => 'Products#index', :gender => 'men'
You could also generalize this a little more by using a placeholder and a constraint in the route definition.
get '/:gender/products' => 'Products#index', :constraints => {:gender => /men|women/}

Instead of doing a HTTP-GET, you may want to take a look at HTTP-POST which doesn't pass variables in the URL. For example, here is a simple HTML-page with one HETT-GET action and one HTTP-POST action:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=ISO-8859-1">
<title>HTTP example</title>
</head>
<body>
<h1>POST & GET example</h1>
<h2> Post form</h2>
<form method="post" action="post_get2.php">
text:<input type="text" size =40 name="name">
<input type=submit value="Submit Post">
</form>
<h2> GET form</h2>
<form method="get" action="post_get2.php">
text:<input type="text" size =40 name="name">
<input type=submit value="Submit Get" >
</form>
</body>
</html>
And here's a simple PHP-page (post_get2.php) that detects if you made a POST or GET action and prints it on the page:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=ISO-8859-1">
<title>HTTP example</title>
</head>
<body>
<h1>POST & GET example</h1>
<?
function stripFormSlashes($arr)
{
if (!is_array($arr))
{
return stripslashes($arr);
}
else
{
return array_map('stripFormSlashes', $arr);
}
}
if (get_magic_quotes_gpc())
{
$_GET = stripFormSlashes($_GET);
$_POST = stripFormSlashes($_POST);
}
echo ("<br/>");
echo ("<pre>");
echo ("POST info:\n");
print_r($_POST);
echo("</pre>");
echo ("<br/>");
echo ("<pre>");
echo ("GET info:\n");
print_r($_GET);
echo("</pre>");
if($_GET['name'])
{
$name = $_GET['name'];
}
echo($name);
?>
<p>
<a><input type="button" value="back" onclick="history.go(-1)"></a>
</p>
</body>
</html>
The great thing with POST is that it doesn't show what choices you made on your page. It just shows 'http://www.shop.com/products' all the time. Unlike GET 'http://www.shop.com/products?category=Jeans&gender=women'

Related

Refactoring Grails templates to expect maps instead of lists

Grails 2.4.4 here. I currently have a GSP that displays a list of Widget objects like so:
// The Widget domain object
#Canonical
class Widget {
String title
String history
}
// The controller
class WidgetController {
def widgetService // Grails Service provided below
def index() {
def widgetType = params.widgetType
def model = widgetService.getAllWidgetsByType(widgetType)
render(view: 'index', model: model)
}
}
// WidgetService
class WidgetService {
def widgetDataService // Omitting for brevity; makes a call to a DB
def getAllWidgetsByType(widgetType) {
def model = [:]
def widgetList = widgetDataService.getWidgets(widgetType)
model.put('widgetList': widgetList)
model.put('foo', 'baz')
model.put('fizz', 'buzz')
model
}
}
// grails-app/views/widget/index.gsp
<!DOCTYPE html>
<html>
<head>
<!-- omitted for brevity -->
</head>
<body>
<div class="content">
<h2>Here are the widgets:</h2>
<g:render model="[providedWidgetList:widgetList]" template="/shared/widgetBlock"></g:render>
</div>
</body>
</html>
// grails-app/views/shared/_widgetBlock.gsp
<g:each var="widget" in="${providedWidgetList}">
<div id="widgetBlock">
<h3>${widget.title}</h3>
<span>${widget.history}</span>
</div>
</g:each>
So, to recap:
WidgetService pulls back a List<Widget> from a DB and plops that into a model map (along with) some other stuff
WidgetController feeds this model to a widget/index.gsp
widget/index.gsp renders a shared/widgetBlock template that generates a widgetBlock div tag for each widget in the returned List
So, if there are 3 widgets returned by the DB, the resultant HTML might look like:
<!DOCTYPE html>
<html>
<head>
<!-- omitted for brevity -->
</head>
<body>
<div class="content">
<h2>Here are the widgets:</h2>
<div id="widgetBlock">
<h3>Hello There, Little Widget!</h3>
<span>What a nice little widget.</span>
</div>
<div id="widgetBlock">
<h3>My Fair Widget</h3>
<span>The best there ever was.</span>
</div>
<div id="widgetBlock">
<h3>A Time of Dogs!</h3>
<span>It was a time of tradition. It was a time of Dogs.</span>
</div>
</div>
</body>
</html>
I now need to add a new property to the Widget, a favoriteFood property, so that now Widget looks like:
#Canonical
class Widget {
String title
String history
String favoriteFood // values might be 'Pizza', 'French Fries' or 'Ice Cream'
}
And now, in the UI, I need the widget list visually-grouped by favoriteFood, so that widgets who share the same favorite food appear in their own section like so:
<!DOCTYPE html>
<html>
<head>
<!-- omitted for brevity -->
</head>
<body>
<div class="content">
<h2>Here are the widgets:</h2>
<h3>Pizza</h3>
<hr/>
<div id="widgetBlock">
<h3>Hello There, Little Widget!</h3>
<span>What a nice little widget.</span>
</div>
<div id="widgetBlock">
<h3>My Fair Widget</h3>
<span>The best there ever was.</span>
</div>
<h3>Ice Cream</h3>
<hr/>
<div id="widgetBlock">
<h3>A Time of Dogs!</h3>
<span>It was a time of tradition. It was a time of Dogs.</span>
</div>
</div>
</body>
</html>
So in the above example, the first two widgets both have favoriteFood = 'Pizza' and the last widget alone loves Ice Cream.
To accomplish this, I need to group all the widgets (returned from the DB) according to favoriteFood, so something like this:
def widgetsByFavoriteFood = widgetList.groupBy { widget -> widget.favoriteFood}
However because of how the service returns a model map, and how the index.gsp invokes and renders the template, and because the template is expecting a list (not a map), I'm just not seeing where I would make these changes.
Very important: This is a gross simplification of my actual Grails app. There are several things I cannot change without enormous refactoring (which I really don't want to have to do):
I really can't change the fact that the WidgetService returns a model map
I really can't change the fact that the widget/index.gsp invokes the _widgetBlock
Anyone have any ideas?
You can make the change right in the GSP. I consider your case UI logic, so it makes sense to put it in the GSP.
Oops, that was an opinion ;)
<g:each var="entry" in="${widgetList.groupBy { widget -> widget.favoriteFood } }">
<!-- entry.key is the favorite food,
and entry.value is the list of widgets grouped under
the favorite food -->
</g:each>
Iterating over a Map, which is what groupBy() returns, produces Map.Entrys.
Tip:
You can build your model much more succinctly like this:
[widgetList: widgetList, foo: 'baz', fizz: 'buzz']
So you get a grossly simplified answer... :)
The service returns a map -- still a map, just the inner details of the widgetList elements are different (rather than a list of individual items, have it return a list of lists of items. Maybe extract the favorite food into a parent element.
[
widgetList:[
[
favoriteFood:'Pizza',
widgets:[
[
title: 'title1',
history: 'blah'
]
]
],
[
favoriteFood:'Eggses',
widgets:[]
]
],
foo: 'baz',
fizz: 'buzz'
]
Widget block has to change no? Not sure what your groupBy yields exactly but,
g:each var foodGroup in widgetList
output the foodGroup stuff: h3, hr
g:each var widget in foodGroup.widgets
output the widget div

How to make asynchronous GET request on RAILS and only get view form (not whole body and headers)

I created a simple asynchronous jquery function on my rails app so I can print the response on a "display" div. But when I make the GET request to my rails url it responds the whole html: "body", "head" and "includes" as if it were a new page on a browser, so the resulting source is the following:
Before I push the button:
<html>
<head>
<title>Myapp1</title>
</head>
<body>
<button onclick="callPage()">Call</button>
<div id="display"></div>
</body>
<html>
After I push the button and the "display" div is filled with my response:
<html>
<head>
<title>Myapp1</title>
</head>
<body>
<button onclick="callPage()">Call</button>
<div id="display">
*<html>
<head>
<title>Myapp1</title>
</head>
<body>
-----Content of page user/1 which is the only thing I want to get.------
</body>
<html>*
</div>
</body>
My route to users on routes.rb (RUBY ON RAILS) working fine
resources :users
#get 'welcome/index'
match ':controller(/:action(/:id))', :via => :get
My Jquery GET callPage (working fine)
<script>
function callPage()
{
url="user/1";
div="display";
$.ajax({
type: "GET",
dataType: "text",
url: url,
success: function(msg){
if(parseInt(msg)!=0)
{
$("#"+div).html(msg);
}
}
}).setRequestHeader('X-CSRF-Token', AUTH_TOKEN);
}
</script>
The question is how do I prevent RAILS from printing out every html tag all over (body, heads,...) and get it to print only the view of the controller I want (in this case, users). ¿Is it a configuration somewhere or do I wave to trim this off somehow?
Thanks a million in advance for your time.
To only display the view you have to change your controller action to have render layout: false at the end. This will prevent the layout from rendering, leaving only the content in your view file.
For example:
class YourController < ApplicationController
def show
# bla bla
render 'your_show_view_name', layout: false
end
end
If you want all your controller actions to have this behavior you can put it right at the top
class YourController < ApplicationController
layout false
def show
# bla bla
end
end

how to correctly render controller output to GSP

in my grails application, I have a controller that reads a directory and returns a list of files in it.
My issue is that in my GSP (view) it is not matching the out put from the controller.
Here is my controller:
package frametest
import groovy.io.FileType
class Read_dirController {
def index() {
def list = []
def dir = new File("D:\\TestRepoLocal\\APIAutomation\\src\\test\\cucumber\\features")
dir.eachFileRecurse (FileType.FILES) { file ->
list << file
}
list.each {
println it.name
}
def found = []
dir.eachFileMatch(FileType.ANY, ~/.feature/) {
found << it.name
}
render(view: "index", model: [name:name])
}
}
here is my GSP (view):
<%# page contentType="text/html;charset=UTF-8" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/>
<meta name="layout" content="main"/>
<title>Welcome to The Marriot test Page</title>
</head>
<body>
<div class="body">
<h2>Marriott Automation Test Page</h2>
<br>
<p></p>
<h3>This is where we can launch our tests and get reports</h3>
<br>
<br>
<p></p>
${name}
</div>
</body>
</html>
The output should just list the file names. It does in the controller output (shown in console), but in the view it shows this:
[D:\TestRepoLocal\APIAutomation\src\test\cucumber\features\APIWAL_525_Account_Security_otp.feature, D:\TestRepoLocal\APIAutomation\src\test\cucumber\features\AssignRoomToReservation.feature, D:\TestRepoLocal\APIAutomation\src\test\cucumber\features\DeleteAccount.feature, D:\TestRepoLocal\APIAutomation\src\test\cucumber\features\DeleteConsumersPaymentMethods.feature, D:\TestRepoLocal\APIAutomation\src\test\cucumber\features\GetActivitiesByID.feature, D:\TestRepoLocal\APIAutomation\src\test\cucumber\features\GetAddressType.feature, D:\TestRepoLocal\APIAutomation\src\test\cucumber\features\GetAddrMiscType.feature, D:\TestRepo
Local\APIAutomation\src\test\cucumber\features\GetAlerts.feature,
the console output for the controller shows this:
APIWAL_525_Account_Security_otp.feature
AssignRoomToReservation.feature
DeleteAccount.feature
DeleteConsumersPaymentMethods.feature
GetActivitiesByID.feature
GetAddressType.feature
GetAddrMiscType.feature
GetAlerts.feature
GetAttractionsByID.feature
What do i need to do to make the view match the controller output from console??
thanks!
ironmantis7x
UPDATE!!!!!
to solve my listing issue I did this:
I changed the controller file to do this little trick:
render(view: "index", model: [name:list.name])
then to make the gsp list the file names on a new line, I did this:
<ul>
<g:each in="${name}" var="fileName">
${fileName}<br>
</g:each>
</ul>
And presto!!
This is where we can launch our tests and get reports
APIWAL_525_Account_Security_otp.feature
AssignRoomToReservation.feature
DeleteAccount.feature
DeleteConsumersPaymentMethods.feature
GetActivitiesByID.feature
GetAddressType.feature
GetAddrMiscType.feature
.....
Thanks guys for encouraging me to struggle to learn it and helping me along the way!
Your gsp is rendering the list but your list of names is in the variable found, not name. Anyway, your last action line should be:
render(view: "index", model: [name: found])
In other hand, your gsp is rendering the list, but should give it some style. An example:
<ul>
<g:each in="${name}" var="fileName">
<li>${fileName}</li>
</g:each>
</ul>
You are storing your o/p in field named found, but in model you are using field name to be included as name, which doesn't contain your o/p. You haven't declared name, or have added anything to it, idk how it is showing that o/p even on gsp.

Simple Hello World Grails Form Submission

I am new to Grails and I am trying to get a very simple example to work. I should just submit a form and display "Hello World" on the screen. It consists of the following controller:
package surface
class SearchController {
def index() {
render(view: "search")
}
def result() {
render "Hello World"
}
}
and a view, with the form:
<%# page contentType="text/html;charset=UTF-8" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>
<g:form name="searchform" url="result">
<g:textArea cols="80" rows="30" name="searchfield"/>
<g:actionSubmit value="Ask"/>
</g:form>
</body>
</html>
When I click on "Ask" I get a 404 error but the browser correctly accesses "/surface/search/result". When I enter that address directly without using the form the "Hello World" appears correctly. This is probably a no-brainer but I seem to be unable to find out why this does not work from the documentation.
Complementing the #Tom Metz answer, what you need to keep in mind in the Grails controller structure is that every public method is considered an action. This action is mapped to a url. In your example will exists /search/index and /search/result (controller + action).
The documentation of the g.form is corret, since this says that;
url (optional) - A map containing the action,controller,id etc.
So to correct your view you can set the action as commented or you can adjust the way you use url:
<g:form name="myForm" url="[action:'result',controller:'search']">

Grails action works for GET request, returns 404 for POST request

I'm learning to use Grails and have run into a situation I don't understand when handing what should be a simple form submission.
I have created a controller called 'add' (there is an AddController.groovy source file and an appropriate add/index.gsp view) and have defined a very sparse 'process' action which currently renders a small amount of HTML to verify that the action is being called.
The URL for the process action on the add controller is (not surprisingly) http://localhost:8080/frontend/add/process/.
I would like to submit a very simple form to the process action as a first step towards integrating with some existing Java libraries.
Sending a GET request to http://localhost:8080/frontend/add/process/ causes the process action to be called and the browser to display the relevant simple HTML content.
Sending a POST request to http://localhost:8080/frontend/add/process/ returns a HTTP 404 error.
I appreciate I'm missing some fundamental addition to my application such that the above action works with both GET and POST requests. I had assumed by default the request type would not matter.
I'd be very happy at this stage if I can send a POST request to the appropriate action and have some markup rendered just to demonstrate that things are working.
What fundamentally essentials piece of the puzzle am I missing?
controllers/frontend/AddController.groovy:
package frontend
class AddController {
def index = { }
def process = {
render "<h1>process action being performed</h1>"
}
}
views/add/index.gsp
<html>
<head>
<title>Test View for index action</title>
<meta name="layout" content="main" />
</head>
<body>
<g:form controller="add" action="process">
<label for="title">Title:</label>
<g:textField name="title" id="title" />
<label for="content">Content:</label>
<g:textArea name="content" id="content" />
<g:actionSubmit value="Add" />
</g:form>
</body>
</html>
The <g:actionSubmit /> directive needs an action attribute to indicate the action to be handled. I had assumed the form action would have been sufficient.
I needed to change:
<g:actionSubmit value="Add" />
to:
<g:actionSubmit value="Add" action="process" />

Resources