URL Structure for Comparing Items - ruby-on-rails

What is the best way to structure a route for comparing multiple items?
Here's the URL example: https://versus.com/en/microsoft-teams-vs-slack-vs-somalia
How to achieve this in routes.rb file? Cannot really find anything in Internet regarding ruby gems. The only thing I can think about is url with optional params, however what if the number of params is unlimited?

you're going to have to parse the a-vs-b-vs-c yourself.
So in routes.rb, you'll have something like:
get 'compare/:compare_string', to 'compare#show'
then you'll get a parameter compare_string that you'll have to parse:
#in compare_controller.rb
def show
compare_items = params[:compare_string].split('-vs-')
# generate the comparison from the compare_items array
end

First - you probably shouldn't allow unlimited #'s of parameters in practice. Even something like 100 might break your page and/or cause performance issues and open you up to DOS attacks. I'd choose some kind of sensible/practical limit and document/enforce it (like 10, 12 or whatever makes sense for your application). At around 2k characters you'll start running into URL-length issues.
Next - is there any flexibility in the URL? Names tend to change so if you want URL's to work over time you'll need to slug-ify each of them (with something like friendly-id) so you can track changes over time. For example - could you use an immutable/unique ID AND human-readable names?
In any case, Rails provides a very flexible system for URL routing. You can read more about the various options / configurations with their Rails routing documentation.
By default a Dynamic Segment supports text like in your example, so (depending on your controller name) you can do something like:
get 'en/:items', to: 'items#compare'
If it's helpful you can add a custom constraint regexp to guarantee that the parameter looks like what you expect (e.g. word-with-dashes-vs-another-vs-something-else)
get 'en/:items', to: 'items#compare', constraints: { items: /(?:(?:[A-Z-]+)vs)+(?:[A-Z-]+)/ }
Then, in your controller, you can parse out the separate strings however you want. Something like...
def compare
items = params[:items].split('-vs-')
end

Related

Rails Routes - Select controller dynamically

Lets say, for the sake of the question, that I have two user types: type1 & type2. I want Rails to use a controller/module depending on the type of user that is being displayed. For example:
If User(id: 1, type: 'type1') has type1 and User(id: 2, type: 'type2') has type2, going to:
/users/1
would select the Type1::UsersController. And going to:
/users/2
would select the Type2::UsersController.
This will allow me to use different controllers and views for each type.
Note: I don't want the type to be displayed in the URL, I want it to be dynamic.
As GoGoCarl says, this isn't really the Rails way to do things. That said, it's not that difficult to get it to work. You can do something like this in routes.rb:
get 'users/:id', to: 'type1/users#show', constraints: lambda { |request|
_id = request.fullpath.gsub('/users/','').to_i
# Note: there might be an easier way to get ID from the request object
User.find(_id)._type == 'type1'
}
get 'users/:id', to: 'type2/users#show', constraints: lambda { |request|
_id = request.fullpath.gsub('/users/','').to_i
User.find(_id)._type == 'type2'
}
I've renamed your type field to _type in my example (because Rails uses type for Single Table Inheritance). I've tested this and it works as desired.
This is possible, but you'd be doing a lot of (probably) unnecessary fighting against the Rails way. I would think you would want one controller as there's probably quite a bit of shared logic (such as saving, deleting, creation, etc).
To answer your question (because I hate when people leave recommendations instead of answers), then you'll need to create a Module that extends Routing, which will allow you to do custom matching. From there, you can do your checks and route appropriately. Here's an example.
That said, a better route to go (no pun intended) would be to have one controller which has a centralized method that can select views.
def find_view view_name
"#{view_name}#{#user.type}"
end
So, a call to render find_view('new') would attempt to render a view named "new-type1." You can put all your type1 user-specific logic in that view. Same for user type2.
Again, since I would think there would be much overlap in your user code, you may want to push this find_view method to a helper class so you can call it from your views, and do things like render specific partials instead based on the user type. That will allow for more code re-use, which is never a bad thing.
Once you get your head wrapped around having a single controller, there are a number of simple ways that you can push user-type-specific code to different avenues -- the views method explained above, you can push all your relevant code to separate helpers which are dynamically called based on the user type, and I'm sure there's more (probably better ones). But all those have one major thing in common -- you'll be fighting Rails a LOT less, and you will have less duplicate code, if you succumb to letting Rails have its way with one route, one controller.
Good luck, hope that helps.

Complex search screens in Rails 3

I need to implement some search functionality within a Rails application. Most of the stuff I have found is generally aimed at simple plain-text search. I am trying to implement something much more specific. The sort of functionality I am looking to create is this (from a C application):
http://andyc.ac/query.gif
The form just submits the data entered by the user. So I need to translate strings like "3..7" into SQL conditions for the where method e.g.
TestLine.where( "test_int >= ? and test_int <= ?", MinInt, MaxInt )
It seems like this is something that already exists somewhere. The exact format expected is not too important, as the users are not shared between the Rails and C applications. How would this be done?
FWIW the specific functionality you describe is actually supported directly. Well.. almost. From the docs:
A range may be used in the hash to use the SQL BETWEEN operator:
Student.where(:grade => 9..12)
Of course then it's a matter of translating the user's string input to a Range, which isn't very complex, e.g.:
def str_to_range str
str =~ /(\d+)\.\.(\d+)/
Range.new *$~.captures.map(&:to_i)
end
It would probably make the most sense in a scope on your model. (Of course a shortcut would be to simply eval '9..12' but evaling input from the end user is a really, really bad idea.)
Give a look at thinking sphinx(http://freelancing-god.github.com/ts/en/). It might make your task a lot easier. You can search in that:
http://freelancing-god.github.com/ts/en/searching.html#basic

Ruby on Rails build query in pieces

There was a very similar question before but i still struggle.
Is it possible to build a query up in stages?
Let's say I have a search form with many text and select fields that may be chained with and/or or which could be blank.
So the sql statement should consist of several parts that are connected individually for each search.
I tried to create strings for every option and put them to a symbol? (i mean #options) and put that in the where clause (e.g. Product.where(#options) ). That works somehow but i have got troubles with this part: 'params[:query]' when it's in quotes. Either my sql statement says 'select products from products where (name like params[:query]') or if i try #{params[:query]} it says: select products from products (where 'name' like ''.)
So how can i chain different parts of a query?
I looking forward to your answers!
Never, ever, ever embed raw strings in your SQL. This is extremely bad form. You should always use the escaping mechanism provided by Rails or something equivalent to avoid ending up in serious trouble. Inserting content from params is very dangerous and should never be done as it only takes this to nuke your app: { :query => '\"-- DROP TABLE users;' }
Generally you use the helper methods provided by ActiveRecord to build up your query in stages:
scope = Product
if (params[:query].present?)
scope = scope.where([ 'name LIKE ?', "%#{params[:query]}%" ])
end
if (params[:example].present?)
scope = scope.where(:example => true)
end
#products = scope.all
You can build it up in stages like this, modifying the scope in-place each time, and then execute the final call to retrieve it. Generally that's when you use your paginator to split up the results.
It's okay to put pretty much anything in your options because it should be escaped by the time it hits the SQL phase, much as anything on the HTML side is escaped for you as well.
Don't confuse instance variables like #options with a symbol like :query. The two are very different things. Instance variables have the benefit of propagating to your view automatically, so they are often used extensively in controllers. Views should avoid modifying them whenever possible as a matter of style.

Which characters in a search query does Google ignore (versus treating them as spaces?)

I want to give my pages human-readable slugs, but Rails' built-in parameterize method isn't SEO-optimized. For example, if I have a post called "Notorious B.I.G. is the best", parameterize will give me this path:
/posts/notorious-b-i-g-is-the-best
which is suboptimal since Google construes the query "Notorious B.I.G." as "Notorious BIG" instead of "Notorious B I G" (i.e., the dots are removed rather than treated as spaces)
Likewise, "Tom's fave pizza" is converted to "tom-s-fave-pizza", when it should be "toms-fave-pizza" (since Google ignores apostrophe's as well)
To create a better parameterize, I need to know which characters Google removes from queries (so I can remove them from my URLs) and which characters Google treats as spaces (so I can convert them to dashes in my URLs).
Better still, does such a parameterize method exist?
(Besides stringex, which I think tries to be too clever. 2 representative problem cases:
[Dev]> "Notorious B.I.G. is the best".to_url
=> "notorious-b-dot-i-g-is-the-best"
[Dev]> "No, Curren$y is the best".to_url
=> "no-curren$y-is-the-best"
I would try using a gem that has been designed for generating slugs. They often make good design decisions and they have a way of updating the code for changing best practices. This document represents Google's best practices on URL design.
Here is a list of the best gems for solving this problem. They are sorted by rank which is computed based on development activity and how many people "watch" changes to the gems source code.
The top one right now is frendly_id and it looks like it will generate good slugs for your use in SEO. Here is a link to the features of the gem. You can also configure it and it looks like it is perfect for your needs.
Google appears to have good results for both the "b-i-g" and "big" in the url slugs.
For the rails side of things, yes a parameterize method exists.
"Notorious B.I.G. is the best".parameterize
=> "notorious-b-i-g-is-the-best"
I think you can create the URLs yourself... something like
class Album
before_create :set_permalink
def set_permalink
self.permalink = name.parameterize
end
def to_params
"#{id}-#{permalink}"
end
end
This will create a url structure of:
/albums/3453-notorious-b-i-g-is-the-best
You can remove the id section in to_params if you want to.
Use the title tag and description meta tag to tell google what the page is called: these carry more weight than the url. So, leave your url as /posts/notorious-b-i-g-is-the-best but put "Notorious B.I.G. is the best" in your title tag.

how to parse multivalued field from URL query in Rails

I have a URL of form http://www.example.com?foo=one&foo=two
I want to get an array of values ['one', 'two'] for foo, but params[:foo] only returns the first value.
I know that if I used foo[] instead of foo in the URL, then params[:foo] would give me the desired array.
However, I want to avoid changing the structure of the URL if possible, since its form is provided as a spec to a client application. is there a good way to get all the values without changing the parameter name?
You can use the default Ruby CGI module to parse the query string in a Rails controller like so:
params = CGI.parse(request.query_string)
This will give you what you want, but note that you won't get any of Rails other extensions to query string parsing, such as using HashWithIndifferentAccess, so you will have to us String rather than Symbol keys.
Also, I don't believe you can set params like that with a single line and overwrite the default rails params contents. Depending on how widespread you want this change, you may need to monkey patch or hack the internals a little bit. However the expeditious thing if you wanted a global change would be to put this in a before filter in application.rb and use a new instance var like #raw_params
I like the CGI.parse(request.query_string) solution mentioned in another answer. You could do this to merge the custom parsed query string into params:
params.merge!(CGI.parse(request.query_string).symbolize_keys)

Resources