I am trying to develop a simple site that lets users add posts and view them all in one aggregated stream.
The problem is that I have posts of 2 "types", the "message" post and the "link" post.
All post must have a message and post can have a link.
If post has a link it must be unique so you cant add a post with link that already was submitted (by you or some other user).
So, in case if user adds the Post with provided link URL I need validations for link like:
- is this a link?
- is this link new (not already in db)?
- is this a valid link (like the domain exists and the server response is adequate (400,301,...)
Now I am stuck with just one model for开发者_如何转开发 all the posts (with links and without them) that looks like this:
#
# Table name: posts
#
# id :integer(4) not null, primary key
# message :string(255) default(""), not null
# link :string(2000)
# src_site_id :integer(4)
# link_type :integer(4) default(0)
# explicit :boolean(1) default(FALSE)
# view_times :integer(4)
# hidden_times :integer(4)
# tasted_times :integer(4)
# uid :integer(4)
# created_at :datetime
# updated_at :datetime
#
class Post < ActiveRecord::Base
default_scope :order => 'created_at desc'
attr_accessible :link, :message, :explicit
validates :message, :presence => true,
:length => { :maximum => 255 }
end
The problem as I see it is that I cant apply model validation to link (cant check uniqueness or format) because it can be NULL, so I apply all the validations in posts_controller like this:
class PostsController < ApplicationController
def create
@post = Post.new(params[:post])
if @post.link.empty?
@post.link = nil
@post.save
else
if looks_like_link(@post.link)
if is_new_link(@post.link)
if is_valid_link (@post.link)
@post.save
else # url is not available
flash['error'] = 'link is not available'
end
else # link is already in db
flash['error'] = 'link is already added'
end
else
flash['error'] = 'doesnt look like a link'
end
end
redirect_to(root_path)
end
private
def looks_like_link(link)
link.match(/^(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/ix)? true : false
end
def is_new_link(link)
Post.find_by_link(link) ? false : true
end
def is_valid_link(link)
require "net/http"
url = URI.parse(link)
req = Net::HTTP.new(url.host, url.port)
res = req.request_head(url.path)
flash[:error] = 'res code is ' + res.code
return res.code == '200' || res.code == '301' ? true : false
end
end
How to make this the right way? I got a suggestion to use STI for this, but I don't really know how to do it the right way and how to apply validation. If you know of good resource about using STI and validations please give me a link.
In rails, whenever you find yourself doing something that is unusual, it is probably wrong. Or if not wrong, it will probably mean a lot of work trying to achieve what you want to achieve. Validation is usually done on the model, and there should never be code in the controller that is not to do with simple routing. So the right way to address this is to put the validation code in the model. In rails 3, I would have a validator something like this - I've not put in the detail of your controller code, but hopefully you get the idea ...
class LinkValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
if record.empty?
return true
else
Post.find_by_link(record) ? false : true
end
end
end
Then in the Post model you call the validator:
validates :link, :link=>true, :allow_nil => true
As to using validation in STI - take a look at this post
精彩评论