How to have custom urls that map to values in the db? - ruby-on-rails

I want to have urls like this:
www.example.com/topic1/...
www.example.com/topic2/...
www.example.com/topic3/...
And these should be served using the TopicController.
The values topic1, topic2, topic3, .. are coming from the table in the database (topics).
Is this possible?
What will my route look like then? These topics will be added ofcourse, it isn't something that is static in nature.

Try:
match '*a/' => 'topic#show' # assume the action is show
params[:a] will equal topic1 etc.

The closest solution I can think of would be to define a route such as
match "/topic/:name" => "topic#process_topic"
and the corresponding action in the TopicController
def process_topic
#topic = Topic.find_by_name(params[:name])
case #topic.name
when topic1
...
when topic2
...
end
end

Related

Clean url with rails

I can't use any of the gems for creating clean Urls in rails. Instead I am rolling out my own implementation. I have created the following entry in routes.rb
match "/:slug" => "cleanurls#index"
Where cleanurl is a controller for handling all such requests. In the cleanurl controller:
class CleanurlsController < ApplicationController
def index
slug = params['slug']
url = Url.where(:slug => slug).first
case(url.url_type)
when 'profile'
user_id = url.id.to_i
#profile = Profile_info.getProfileDetails(user_id)
render '/profiles/index'
end
end
end
I have created the table urls which stores the slug,id (as relevant) and the type of page. Right now I have only the profile page to deal with but in the future I will have different types of pages with clean urls.
My first Question:
1) Is this implementation the right approach? And is this okay from a performance perspective given the tables have all the right indexes.
I am making the profile url like this:
def self.makeProfileUrl(id,name)
name = name.strip.titleize
extension = User.where(:name => name).count - 1
slug = name.split(" ").join("-")
if extension != 0
slug += "-#{extension}"
end
Url.create(:slug => slug, :id => id.to_i, :url_type => 'profile')
end
I am using extension to append a count in case their are users who share the same name.
Question:
Is this the right way to create the slug and ensure it being unique? Fetching the count of a name from the other table does not seem right.
Answering the question #1:
I don't know the details of what's your overall goal, but if you'd like
to have such URLs that are based on records from the database - then yes: it's
a good approach.
Answering question #2 (regarding slugs):
I'd rather use something much more elaborate and well tested like:
https://github.com/norman/friendly_id
My 50 cents about some other things:
Is this one of your first projects in Ruby/Rails? If so - congratulations! :)
I'm asking because I noticed that you're using camel case here and there...
Also:
user_id = url.id.to_i
Why do you call this #to_i method here? Did you set up this id as a string
or something?
Hope this helps

rails "where" statement: How do i ignore blank params

I am pretty new to Rails and I have a feeling I'm approaching this from the wrong angle but here it goes... I have a list page that displays vehicles and i am trying to add filter functionality where the user can filter the results by vehicle_size, manufacturer and/or payment_options.
Using three select form fields the user can set the values of :vehicle_size, :manufacturer and/or :payment_options parameters and submit these values to the controller where i'm using a
#vehicles = Vehicle.order("vehicles.id ASC").where(:visible => true, :vehicle_size => params[:vehicle_size] )
kind of query. this works fine for individual params (the above returns results for the correct vehicle size) but I want to be able to pass in all 3 params without getting no results if one of the parameters is left blank..
Is there a way of doing this without going through the process of writing if statements that define different where statements depending on what params are set? This could become very tedious if I add more filter options.. perhaps some sort of inline if has_key solution to the effect of:
#vehicles = Vehicle.order("vehicles.id ASC").where(:visible => true, if(params.has_key?(:vehicle_size):vehicle_size => params[:vehicle_size], end if(params.has_key?(:manufacturer):manufacturer => params[:manufacturer] end )
You can do:
#vehicles = Vehicle.order('vehicles.id ASC')
if params[:vehicle_size].present?
#vehicles = #vehicles.where(vehicle_size: params[:vehicle_size])
end
Or, you can create scope in your model:
scope :vehicle_size, ->(vehicle_size) { where(vehicle_size: vehicle_size) if vehicle_size.present? }
Or, according to this answer, you can create class method:
def self.vehicle_size(vehicle_size)
if vehicle_size.present?
where(vehicle_size: vehicle_size)
else
scoped # `all` if you use Rails 4
end
end
You call both scope and class method in your controller with, for example:
#vehicles = Vehicle.order('vehicles.id ASC').vehicle_size(params[:vehicle_size])
You can do same thing with remaining parameters respectively.
The has_scope gem applies scope methods to your search queries, and by default it ignores when parameters are empty, it might be worth checking

Rails: Request-based (& Database-based) Routing

I am trying to get rid of some scope-prefixes I am currently using in my app.
At the moment my Routes look like this (simplified example):
scope 'p'
get ':product_slug', as: :product
end
scope 't' do
get ':text_slug', as: :text
end
which for example generates these paths:
/p/car
/t/hello-world
Now I want the paths to work without the prefixed letters (p & t). So I restrict the slugs to the existing database entries (which btw works great):
text_slugs = Text.all.map(&:slug)
get ':text_slug', as: :text, text_slug: Regexp.new( "(#{text_slugs.join('|')})"
product_slugs = Product.all.map(&:slug)
get ':product_slug', as: :product, product_slug: Regexp.new( "(#{product_slugs.join('|')})"
The problem:
This is a multi-tenant app which means that someones text_slug could be another ones product_slug and vice versa. That's why I have to filter the slugs by the current site (by domain).
A solution would look like this:
text_slugs = Site.find_by_domain(request.host).texts.all.map(&:slug)
get ':text_slug', as: :text, text_slug: Regexp.new( "(#{text_slugs.join('|')})"
But request isn't available in routes.rb and I everything I tried won't work.
The direct call to Rack::Request needs the correct env variable which doesn't seem to be present in Application.routes, otherwise this could work:
req = Rack::Request.new(env)
req.host
I really tried alot and am thankful for any hint!
You may be able to use advanced constraints for this: http://guides.rubyonrails.org/routing.html#advanced-constraints.
class SlugConstraint
def initialize(type)
#type = type
end
def matches?(request)
# Find users subdomain and look for matching text_slugs - return true or false
end
end
App::Application.routes.draw do
match :product_slug => "products#index", :constraints => SlugConstraint.new(:product)
match :tag_slug => "tags#index", :constraints => SlugConstraint.new(:tag)
end
BTW - You may run into problems with testing, but that's another issue...

Get current action's path/url including query string? (Rails)

Simple question - how do I get the path or full URL of the current action INCLUDING the query string?
I wish to save it to the session variable like so:
def show
#thingy = Thingy.find(params[:id])
session[:some_var] = current_url
...
end
At the moment I'm doing the following, but it seems a bit heavy-handed (especially the specifying of query string params individually):
def show
#thingy = Thingy.find(params[:id])
session[:some_var] = thingy_path(#thingy, :q1 => params[:q1], :q2 => params[:q2])
...
end
request.url is probably what you are looking for.
access params variable,it will give you query as well as controller and action.
By using request object you can dig more deeper if you want.

Rails - Get contents but only when url param is there

This is a rails newb question. I have a Contents model that has a content_type attribute. I have a few different content_types that I would like to filter, passing the type through the URL like so: /contents?content_type=blog
I understand I can get the contents based on that parameter like so:
#contents = Content.where({:content_type => params[:content_type]})
But when the URL parameter is not present, it's not getting any contents. I would prefer that when a URL param isn't being passed, that all contents (regardless of type) would be retrieved. How do I do that?
I would define a scope, like this (inside your model)
class Content
scope :by_content_type, lambda { |contenttype|
where({:content_type => contenttype}) unless contenttype.blank?
}
end
and then you use this in your controller as follows:
#contents = Content.by_content_type(params[:content_type])
This should work:
if params[:content_type].blank?
#contents = Content.scoped
else
#contents = Content.where({:content_type => params[:content_type]})
end
Theres a reasonable pattern here using a series of chained scopes to narrow filters based on query params:
#contents = Content.scoped # Start with no filter
# Optionally narrow filter if filter param is present
type = params[:content_type]
#contents = #contents.where(:content_type => type) if type
#contents = Content.where({(:content_type => params[:content_type]} unless params[:content_type].blank?))

Resources