I have a basic CRUD with "Company" model. To make the company name show up, I did
def to_param
name.parameterize
end
Then I accessed http://localhost:3000/companies/american-express which runs show action in the companies controller.
Obviously this doesn't work because the show method is as following:
def show
#company = Company.find_by_id(params[:id])
end
The params[:id] is american-express. This string is not stored anywhere.
Do I need to store the short string (i.e., "american-express") in the database when I save the record? Or is there any way to retrieve the company data without saving the string in the database?
Send the ID with the parameterized value;
def to_param
new_record? ? super : "#{id}-#{name}"
end
And when you collect the data in the show method, you can use the whole parameter;
def show
#company = Company.find("12-american-express"); // equals to find(12)
end
There's also a plugin called permalink_fu, which you can read more about here.
I think friendly_id is more usable.
I do something similar with the Category model in my blog software. If you can guarantee that the only conversion the parameterize method is doing to your company names is replacing space characters with dashes then you can simply do the inverse:
def show
#company = Company.find_by_name(params[:id].gsub(/-/, ' '))
end
Try permalink_fu plugin, which creates SEO friendly URLs in rails
http://github.com/technoweenie/permalink_fu
cheers
sameera
I would suggest the friendly_id gem also.
It gives you the flexibility to use persited permalink slugs, also strip diacritics, convert to full ASCII etc.
Basically it makes your life a lot easier, and you get "true" permalinks (no to_param and the id workaround needed) with little effort.
Oh and did i mention that the permalinks are also versioned, so you can make old outdated permalinks to redirect to the current one? :)
Related
I am trying to fetch a random record in rails, to render in my home page.
I have a post model with content and title attributes. Lets say i wanted to fetch a random post(content and title) for some reason, How can i go about it in ruby. Thanks in advance.
You might find this gem handy : Faker
It allows to generate random strings with some meaning.
For example, a name :
Faker::Name.name => “Bob Hope”
Or an e-mail
Faker::Internet.email
In addition to this gem, if you want to be able to generate mock models very easily, I recommend the gem Factory Girl
It allows you to create factories for your model, sou you can generate a model with random attributes quickly.
Posting another answer since the first one answered to an unclear question.
As #m_x said, you can use RANDOM() for SQL.
If you don't mind loading all the dataset, you can do it in ruby as well :
Post.all.sample
This will select one random record from all Posts.
I know this is an old question, but since no answer was chosen, answering it might be helpful for other users.
I think the best way to go would be generating a random offset in Ruby and using it in your Active Record statement, like so:
Thing.limit(1).offset(rand(Thing.count)).first
This solution is also performant and portable.
In your post controller,
def create
#post = Post.new(params[:post])
if you_want_some_random_title_and_content
title_length = 80 #choose your own
content_length = 140 #choose your own
#post.title = (0...title_length).map{(65+rand(26)).chr}.join
#post.content = (0...content_length).map{(65+rand(26)).chr}.join
end
if #post.save
redirect_to #post
else
render 'new'
end
end
Using Kent Fedric's way to generate random string
unfortunately, there is no database-agnostic method for fetching a random record, so ActiveRecord does not implement any.
For postgresql you can use :
Post.order( 'RANDOM()' ).first
To fetch one random post.
Additionnally, i usually create a scope for this:
class Post < ActiveRecord::Base
scope :random_order, ->{ order 'RANDOM()' }
end
so if you change your RDBMS, you just have to change the scope.
I'm trying to figure out how to obfuscate the ids of my records in rails.
For example: a typical path might look like http://domain/records/1, so it's pretty easy for people to deduce how much traffic the site is getting if they just create a new record.
One solution that I've used is to hash the id with a salt, but since I'm not sure whether that function is bijective, I end up storing it in another column in my database and double check for uniqueness.
Another option I was thinking about was generating a random hash and storing that as another column. If it isn't unique ... just generate another one.
What's the best way of doing this?
You could use the built-in OpenSSL library to encrypt and decrypt your identifiers, that way you would only need to overwrite to_param on your models. You'll also need to use Base64 to convert the encrypted data into plain text. I would stick this in a module so it can be reused:
require 'openssl'
require 'base64'
module Obfuscate
def self.included(base)
base.extend self
end
def cipher
OpenSSL::Cipher::Cipher.new('aes-256-cbc')
end
def cipher_key
'blah!'
end
def decrypt(value)
c = cipher.decrypt
c.key = Digest::SHA256.digest(cipher_key)
c.update(Base64.decode64(value.to_s)) + c.final
end
def encrypt(value)
c = cipher.encrypt
c.key = Digest::SHA256.digest(cipher_key)
Base64.encode64(c.update(value.to_s) + c.final)
end
end
So now your models would need to look something like this:
class MyModel < ActiveRecord::Base
include Obfuscate
def to_param
encrypt id
end
end
Then in your controller when you need to find a record by the encrypted id, you would use something like this:
MyModel.find MyModel.decrypt(params[:id])
If you're looking to encrypt/decrypt ids without storing them in the database, this is probably the easiest way to go.
Instead of numeric ids, use some kind of friendly url or human readable slug. There are lots of tools to choose from in this department. Not only are they more friendly to your users, but well chosen slugs can give a nice advantage with search engines.
Here's a gem that keeps it numeric, requires no database migrations, and no routing changes: https://github.com/namick/obfuscate_id
I've found that this gem doesn't work in concert with some other gems, notably paper_trail. This is because of the way it replaces the find method, and paper_trail causes find to be called with the actual record id.
So I've been using the gem's "scatter_swap" functionality, but not the rest of it. Here's the model:
require 'obfuscate_id/scatter_swap'
class Page < ActiveRecord::Base
# This is a random number that, if changed, will invalidate all existing URLs. Don't change it!
##obfuscate_spin = # random number here, which is essentially the encryption key
##
# Generate URL parameter to be used in the URL as the "id"
def to_param
# Use the obfuscate_id gem's class to "spin" the id into something obfuscated
spun_id = ScatterSwap.hash(self.id, ##obfuscate_spin)
# Throw any additional attributes in here that are to be included in the URL.
"#{spun_id} #{name}".parameterize
end
def self.find_by_slug!(slug)
spun_id = slug[/^[0-9]+/]
begin
find_by_id! ScatterSwap.reverse_hash(spun_id, ##obfuscate_spin)
rescue ActiveRecord::RecordNotFound => e
raise ActiveRecord::RecordNotFound, "Couldn't find matching Page."
end
end
end
And in the controller:
class PagesController < InheritedResources::Base
# Find the page using its URL slug
before_filter :find_page, except: [:index, :create, :new]
def find_page
#page = Page.find_by_slug! params[:id]
# If the URL doesn't match exactly, and this is a GET.
# We'll redirect to the new, correct URL, but if this is a non-GET, let's let them finish their request instead.
if params[:id] != #page.to_param && request.get?
redirect_to url_for({ id: #page.to_param }), status: 301
end
end
end
As an alternative to the redirection that takes place there, you could simply include a canonical URL in the page. The redirection has the bug of ignoring any query parameters in the URL. This was not a problem for my project, as I didn't have any. But a canonical URL would be better.
It's pretty easy to generate unique random identifiers for your records either using a randomized string generator or a simple call to Digest::SHA1.hexdigest which produces reasonably random and cryptographically unique results.
For instance, you can create a secondary column called ident or unique_id that stores your public identifiers. You can then over-write to_param to use this instead:
class MyModel < ActiveRecord::Base
before_create :assign_ident
def self.from_param(ident)
find_by_ident(ident)
end
def to_param
self.ident
end
protected
def assign_ident
self.ident = Digest::SHA1.hexdigest(SecureRandom.random_number(1<<256).to_s)
end
end
Theoretically there is a chance of collision on SHA1 but the odds are so astronomically low you're more liable to have a software crash because of a memory error or hardware malfunction. You can test this to see if it suits your needs by generating a few billion identities to see if they ever collide, which they shouldn't. A 256-bit random number should provide a sufficient amount of data for the SHA1 algorithm to chew on.
After reading through #siannopollo's post, I created a Gem based on the idea of his post (but with some improvements): https://github.com/pencil/encrypted_id
Just because it hasn't been mentioned here: You could simply use UUIDs (wikipedia article)
There are multiple ways of using UUID as primary keys in Rails, depending on your Rails version and database engine. It's easy to find.
Just as a possibility, in case you depend too much on your existing integer primary key, you can also just add a UUID to your table and make your model use it automatically when it comes to generating URLs by overwriting Model#to_param more details in the docs
I have a model, let's say user, with both an id and a slug. I'd like to be able to generate a url using user_path(#user) that contains both the id and slug.
I know that user_path will use to_param method for the parameter it puts at the end of the url, but is there a way to use 2 (or more parameters) and get something like this:
http://domain.com/users/id/slug
Thanks!
Friendly-id is a great way to generate permalinks. It also offers pretty good customization options.
Did you try this in your model?
def to_param
"#{id}-#{slug)"
end
I am currently developing a blogging system with Ruby on Rails and want the user to define his "permalinks" for static pages or blog posts, meaning:
the user should be able to set the page name, eg. "test-article" (that should be available via /posts/test-article) - how would I realize this in the rails applications and the routing file?
for user-friendly permalinks you can use gem 'has_permalink'. For more details http://haspermalink.org
Modifying the to_param method in the Model indeed is required/convenient, like the others said already:
def to_param
pagename.parameterize
end
But in order to find the posts you also need to change the Controller, since the default Post.find methods searches for ID and not pagename. For the show action you'd need something like this:
def show
#post = Post.where(:pagename => params[:id]).first
end
Same goes for the other action methods.
You routing rules can stay the same as for regular routes with an ID number.
I personally prefer to do it this way:
Put the following in your Post model (stick it at the bottom before the closing 'end' tag)
def to_param
permalink
end
def permalink
"#{id}-#{title.parameterize}"
end
That's it. You don't need to change any of the find_by methods. This gives you URL's of the form "123-title-of-post".
You can use the friendly_id gem. There are no special controller changes required. Simple add an attribute for example slug to your model..for more details check out the github repo of the gem.
The #63 and #117 episodes of railscasts might help you. Also check out the resources there.
You should have seolink or permalink attribute in pages' or posts' objects. Then you'd just use to_param method for your post or page model that would return that attribute.
to_param method is used in *_path methods when you pass them an object.
So if your post has title "foo bar" and seolink "baz-quux", you define a to_param method in model like this:
def to_param
seolink
end
Then when you do something like post_path(#post) you'll get the /posts/baz-quux or any other relevant url that you have configured in config/routes.rb file (my example applies to resourceful urls). In the show action of your controller you'll just have to find_by_seolink instead of find[_by_id].
So basically I have a controller. something like this
def show
#user = User.find[:params[id]]
#code to show in a view
end
User has properties such as name, address, gender etc. How can I access these properties in the model? Can I overload the model accesser for name for example and replace it with my own value or concatenate something to it. Like in the show.html.erb view for this method I might want to concatenate the user's name with 'Mr.' or 'Mrs.' depending upon the gender? How is it possible?
I would hesitate to override the attributes, and instead add to the model like this:
def titled_name
"#{title} #{name}"
end
However, you can access the fields directly like this:
def name
"#{title} #{self[:name]}"
end
You can create virtual attributes within your model to represent these structures.
There is a railscast on this very subject but in summary you can do something like this in your model
def full_name
[first_name, last_name].join(' ')
end
def full_name=(name)
split = name.split(' ', 2)
self.first_name = split.first
self.last_name = split.last
end
If you wish to explicitly change the value of an attribute when reading or writing then you can use the read_attribute or write_attribute methods. (Although I believe that these may be deprecated).
These work by replacing the accessor method of the attribute with your own. As an example, a branch identifier field can be entered as either xxxxxx or xx-xx-xx. So you can change your branch_identifier= method to remove the hyphens when the data is stored in the database. This can be achieved like so
def branch_identifier=(value)
write_attribute(:branch_identifier, value.gsub(/-/, '')) unless value.blank?
end
If you are accessing data stored directly in the database you can do this in you view:
<%= #user.firstname %>
<%= #user.gender %>
etc.
If you need to build custom representations of the data, then you will either need to create helpers, or extend the model (as above).
I tend to use helper methods added to the model for things like that:
def formatted_name
"#{title} #{first_name} #{last_name}"
end
(Edit previous post. Looked back at my code and realized helpers are supposed to be for presentation-related (mark-up) stuff only.)
(Edit again to remove left-over parameter... Geez, not enough coffee this morning.)
(Edit again to replace $ with #... Perhaps I should just remove this one huh?)
You can easily overload the attributes as you suggest.
i.e. if name is a field in the users database table, you can do:
def name
"#{title} #{read_attribute[:name]}"
end
The read_attribute function will return the database column value for the field.
Caveat: I am not sure this is a good idea. If you want a method that displays model data in a modified way, I would be tempted not to overload the default methods, and call them something different - this will avoid a certain level of obfuscation.
Documentation here: http://api.rubyonrails.org/classes/ActiveRecord/Base.html (under 'Overwriting default accessors')
in http://api.rubyonrails.org/classes/ActionController/Base.html
search for
Overwriting default accessors