Rails blog using Mongoid - Auto generate Short URL on post creation - ruby-on-rails

I have a simple blog engine using Rails and Mongoid ORM.
I have 2 models in the blog, 'Article' and 'Url'.
The Article model contains all of the post content, and the Url class is the generator function that takes the slug of the Article and creates a Short URL for it.
E.g. my-sample-blog-post -> ai3n etc. etc.
The problem is I am having problems linking the two. I can't embed the URL class in the Article class either.
My question is, can I generate a Short URL on the fly, as the post is created, inside the Article model? The Article model already uses Mongoid::slug to give me nice post slugs, but I also need short URLs for each post.
Any help on this would be much appreciated.

I think you could probably use an after create callback to generate the short url and then store it in a field inside the Article model.
Something like this:
class Article
field :title
slug :title
field :short_url
after_create :generate_short_url
def generate_short_url
self.short_url = shorten_it(self.slug) # assuming you implement shorten_it
self.save
end
end

Related

Can I use Friendly Id on a Rails model without having to create a slug column on the model table?

On Rails 4.2, I would like to use Friendly Id for routing to a specific model, but dont wan't to create a slug column on the model table. I would instead prefer to use an accessor method on the model and dynamically generate the slug. Is this possible? I couldn't find this in the documentation.
You can not do this directly using friendly id because of the way it uses the slug to query the database (relevant source).
However it is not difficult to accomplish what you want. What you will need are two methods:
Model#slug method which will give you slug for a specific model
Model.find_by_slug method which will generate the relevant query for a specific slug.
Now in your controllers you can use Model.find_by_slug to get the relevant model from the path params. However implementing this method might be tricky especially if your Model#slug uses a non-reversible slugging implementation like Slugify because it simply get rids of unrecognized characters in text and normalizes multiple things to same character (eg. _ and - to -)
You can use URI::Escape.encode and URI::Escape.decode but you will end up with somewhat ugly slugs.
As discussed here I went with the following approach for custom routing based on dynamic slug.
I want custom route like this: /foos/the-title-parameterized-1 (where "1" is the id of the Foo object).
Foo model:
#...
attr_accessor :slug
#dynamically generate a slug, the Rails `parameterize`
#handles transforming of ugly url characters such as
#unicode or spaces:
def slug
"#{self.title.parameterize[0..200]}-#{self.id}"
end
def to_param
slug
end
#...
routes.rb:
get 'foos/:slug' => 'foos#show', :as => 'foo'
foos_controller.rb:
def show
#foo = Foo.find params[:slug].split("-").last.to_i
end
In my show view, I am able to use the default url helper method foo_path.

customized rails routing url

I am little bit confused in using rails route. I need some suggestion about customizing my url.
This is my current url
http://localhost:3000/posts/product/41?product_id=2
and
http://localhost:3000/posts/product/41?model_id=24&product_id=2
This is my link
<%= link_to product_model.name, controller: :posts,action: :product,product_id: params[:product_id],model_id: product_model.id
Logically product should come first in url. But why model prefers first here.
And i need my url something like this
http://localhost:3000/posts/product/41/mobile
and
http://localhost:3000/posts/product/41/mobile/nokia
Since i am not familiar with rails route i didn't write any special coding in my route
Here is the simple route exist
resources :posts
Ok your question here actually contains two different problems, so i will give suggestions to both.
1. Nested resources
Your first problem is to use "nested routes". Rails guide has a long and good article about routes and how to write and use them, including nested routes. You can check it out here: http://guides.rubyonrails.org/routing.html#nested-resources.
However in your situation would the solution look something like this:
resources :category do
resources :sub_category do
resources :products do
resources :models
end
end
end
You can now greate links like this
<%= link_to product_model.name, category_sub_category_product_model_path(#category, #sub_category, #product, product_model) %>
You can see that i have removed posts, see 3. Refactor design to see why. If you really want this as a action on posts, should you however do something like this (but would recommend this!):
get "posts/product/:category_id/:subcategory_id/:product_id/:model_id", to "posts#product", as: :posts_product
This would be used like this in your views:
<%= link_to product_model.name, posts_product_path(#category, #sub_category, #product, product_model) %>
2. Pretty URL's
Your second problem is to use model names instead of id's in your urls. The simpels solution for this is having a unique attribute on your model that you can use instead of id, and then just add a to_param method. Fx for product could we do something like this:
class Product < ActiveRecord::Base
def to_param
name
end
end
Ryan Bates have made a good screencast about this: http://railscasts.com/episodes/63-model-name-in-url-revised. If you want something more flexible should you use the gem Friendly Id. And again does Ryan comes to the rescue with another great RailsCast: http://railscasts.com/episodes/314-pretty-urls-with-friendlyid.
3. Change design
Ok well so this is just my opinion, feel free to ignore it. But their is some bad practices and signs in your examples, so let me just quickly go through what i think you should improve on.
Restful actions
You should, when possible always avoid creating controller actions that is not restful (simply put is the base actions index, show, new, create, edit, update and destroy the only restful actions). In your example does this mean that the product action of the posts controller should be changed to something restful. Why not move it to the product model controller and call it "show"?
Deeply nested resources
You should avoid nesting your routes to deeply. Is it really important to show both the category, the sub category, the product AND the model in your url? Maybe that's how your models are associated internally in your application but why should the user know this? If you don't have a list of subcategories at "/posts/product" and a list of products at "posts/product/41" is there no reason to have so long a route. A rule of thumb is "nest no deeper then two levels", ie. ":category/:sub_category". Further more does short routes mean better SEO.
As i said, feel free to ignore these suggestions, your application would work without these changes. However changing these things would greatly help you structure you code, and keep your codebase clean and maintainable. These rules and principles is not something i have just conjured out of nothing, but very accepted principles in the Rails community. You can google each of these principles or patterns and see a lot of articles and posts on why it's a good idea to follow them, especially when you work with Rails.
Resources
Rails Routing from the Outside In — Ruby on Rails Guides
norman/friendly_id - Github
#314 Pretty URLs with FriendlyId - RailsCasts
#63 Model Name in URL (revised) - RailsCasts
Take a look at the friendly-id gem
There's a great RailsCast about it
Add this to your model inmodel.rb
def to_param
name
end
and then add
#model = Model.find_by_name(params[:id]) to your show method, then you can get the url as you mentioned above.
PS: You Should have name field for Model table in your schema.
I think you are looking for nested routes. Please refer this link http://guides.rubyonrails.org/routing.html#nested-resources
and use to_param method in the model if you want to display model_name instead of id as explained by #Ajay Kumar
def to_param
name
end
where name is the model attribute for that specific model.
Why not a namespace?
namespace :posts do
resources :products
end
This should do I think..
Namespace does not include restful ids into the scope..

Get same url structure as on Stack Overflow

A question on SO has this url structure:
http://stackoverflow.com/questions/18474799/changing-the-input-value-in-html5-datalist
If we assume that the number section is the ID, the first two sections (after the domain extension) are obtained by simply using the following in routes.rb
resources :questions
The question is already identified by it's ID, so how do we add the (optional) decorating slug in the simplest of manners? Do we need to use a new link helper (and including additional params) or can the 3-section url be resolved elsewhere?
Update:
To focus this question more on the route-handling, let's presume there is already a slug saved on the object (upon creation) as an attribute, e.g. #question.slug
It would really be an advantage if a rule in routes.rb or/and in the controller could enable and handle the optional slug, instead of having to write long link helpers in all views.
resources :questions do
member: title
end
for slug use friendly_id and yes don't forget to have a look at Rails Routing
You might be able to use the to_param method to create a "friendly id".
Something like this:
class Question < ActiveRecord::Base
def to_param
[id, name.parameterize].join("/")
end
end
More info in this gist
If you just want to handle the GET requests in that manner, it's easy to do:
get '/questions/:id/:title' => 'questions#show', as: :question_with_title
resources :questions
This way you can handle incoming URLs with or without the title (just as StackOverflow can -- try it!). You can create urls dynamically with something like:
question_with_title_path(#question.id, #question.title.to_s.downcase.gsub(/ /, '-')
# probably will want to use a method for processing titles into url-friendly format
More at http://guides.rubyonrails.org/routing.html#static-segments

Model and controller designs in rails for friendly URL

Another question from rails newbie. I am using friendly_id gem with mysql in rails 3.x
This is a design problem (may be easy in rails). I am expecting advises from rails experts. I am building a library listing app. Where user can view library by "metro-area" or by "city" in it. For example:
I wish to have URLs like:
www.list.com/library/san-francisco-bay-area
or
www.list.com/library/san-francisco-bay-area/palo-alto/
In database I have tables:
library
-------
id, name, city_id, slug
name is slugged here and city_id is FK
city
----
city_id, name, metro_area_id, slug
name is slugged here and metro_area_id is FK
metro_area
----------
metro_area_id, name, state, slug
name is slugged here
So when a user points browser to www.list.com/library/san-francisco-bay-area/palo-alto
I wish to get list of libraries in san-francisco-bay-area/palo-alto. But my library table model is containing slug only for library's name. So how this URL can be parsed to find the city_id that can be used in library model and controller to get the list.
Please remember, I cannot rely only on the name of the city. I would have to find city_id of 'palo-alto' which is in metro 'san-francisco-bay-area'. Since slug for metro_area and city is in other tables, so what is the best way to design model and controller.
Show method of controller is:
def show
#user = Library.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #library }
end
end
and model is
class Library < ActiveRecord::Base
attr_accessible :name, :city_id, :slug
extend FriendlyId
friendly_id :name, use: :slugged
end
This will not work per my friendly URL requirement. So I would appreciate advice from experts :)
Maybe you have found your solution, I'm new to rails as well, so I'm just guessing this would work out.
Since you wanna display slugs from two different models. I'm assuming the way to display ids would be something like
www.list.com/libraries/:id/cities/:id/metro_areas/:id
Which can be done through editing the route file by adding Nested Resources
As for just displaying two ids like
www.list.com/library/:city_id/:metro_area_id
Rails guide refers it as Dynamic Segments
After that, it's just a matter of converting the ids to slugs.
I also found this in FriendlyId's documentation which is addressing to your case.
Hope it helps

Is there any way the restful URL not to be displayed in the URL box in browser?

Assume that there are Post and Comment models.
class Post < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :post
end
In config/routes.rb
resources posts do
resources comments
end
In this situation, if we retrieve the comment id 12 of the post id 1, the proper restful routing path will be as follows:
post_comments_path(#comment.post, #comment)
and this will be displayed in the URL box of the client's browser as follows:
post/1/comments/12
What I am concerning in this context is whether there are ways to hide the above url string including ids and replace others expression excluding id data, for exmaple, "post/comments", or not.
If this questions is not significant, although id data are exposed in the query string, I am curious about whether there is any security problem or not.
You may want to checkout a Railscast on displaying model attributes in the url. However, its much more easy to manage your resources keeping the id in the url.
http://railscasts.com/episodes/63-model-name-in-url
Even on the Railscast, you'll find Ryan Bates talking of tricky issues if you do not have the id in the url and simply have, say, name as posts/name-of-post instead of posts/1.
To change the url, all you need to do is override the to_param method that Rails uses to convert the model to a url. However, without passing the id, you'll need to modify your code in other places too.

Resources