Rails 3 and validating if http is included in link - ruby-on-rails-3

When a user submits a link, is there a way to validate if he have included the http:// or not. I other words, is it possible to not only validate but also add the http:// if it is missing?
I am using Rails 3.

You could override the setter method for the link. In your model, something like this:
def link=(str)
str = 'http://' + str if str[0,7] != 'http://'
super(str)
end
This would force adding http:// to the start of all links if not already there.

You can use a custom validation.
Pretending your model is "Post" and the attribute is "url" you can use something like:
class Post < ActiveRecord::Base
validate :ensure_valid_url
protected
def ensure_valid_url
protocol_regexp = %r{
\A
(https?://)
.*
\Z
}iux
self.url = "http://" + url unless url.blank? or url.match(protocol_regexp)
ipv4_part = /\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]/ # 0-255
regexp = %r{
\A
https?://
([^\s:#]+:[^\s:#]*#)?
( (xn--)?[[:alnum:]\w_]+([-.][[:alnum:]\w_]+)*\.[a-z]{2,6}\.? |
#{ipv4_part}(\.#{ipv4_part}){3} )
(:\d{1,5})?
([/?]\S*)?
\Z
}iux
default_message = 'does not appear to be a valid URL'
default_message_url = 'does not appear to be valid'
options = { :allow_nil => false,
:allow_blank => false,
:with => regexp }
message = url.to_s.match(/(_|\b)URL(_|\b)/i) ? default_message_url : default_message
validates_format_of(:url, { :message => message }.merge(options))
end
end
This example is based on validates_url_format_of

variation on Steve Lorek's answer taking in the account that some links submitted by user will contain https:// instead of http://
def link=(str)
str = 'http://' + str if (str[0,7] != 'http://' && str[0,8] != 'https://')
super(str)
end

Related

Parse a URL and remove end portion

I am trying to parse URLs. For example where I am trying to pull out:
~/locations/1 => [locations,1]
~/locations/1/comments => [locations,1]
~/locations/1/comments/22 => [locations,1]
~/locations/1/buildings/3 => [buildings,3]
~/locations/1/buildings/3/comments => [buildings,3]
~/locations/1/buildings/3/comments/34 => [buildings,3]
The format is pretty consistent. I started with arrays but it seems to still fail:
#request_path = request.path.downcase.split('/')
#comment_index = #request_path.index("comments").to_i
if #comment_index > 0
#request_path = #request_path.drop_while { |i| i.to_i >= #comment_index }
end
resource, id = #request_path.last(2)
I added the downcase just incase someone manually typed in an uppercase URL. The drop_while seems to not be working.
What kind of output you have after processing your code?
Edited
Your problem is that you convert element to_i and it is 0. But you want to compare index of element, but can normally get index of element in that situation using Array#index method.
Correct approach:
#request_path.drop_while { |i| #request_path.index(i) >= #comment_index }
You can parse path without drop_while.
My solution:
def resource_details(path)
resource_array = path.downcase.split("/").reject!(&:empty?)
key = resource_array.index("comments")
return key.present? ? (resource_array - resource_array[key..key + 1]).last(2) : resource_array.last(2)
end
It will cut out ["comments"] or ["comments","2"] for your path.
Invoke that method:
1.9.3p0 :051 > resource_details("/locations/1/buildings/3/comments")
=> ["buildings", "3"]
1.9.3p0 :052 > resource_details("/locations/1/comments/2")
=> ["locations", "1"]

Rails current_page? "fails" when method is POST

I have a really simple problem. I have a page of reports and each report has its own tab. I'm using current_page? to determine which tab should be highlighted. When I submit any report, current_page? doesn't seem to work anymore, apparently because the request method is POST.
Is this the intended behavior of current_page? I have a hard time imagining why that would be the case. If it is, how do people normally get around this problem?
Here's an example of a current_page? call:
<li><%= link_to "Client Retention", reports_client_retention_path, :class => current_page?(reports_client_retention_path) ? "current" : "" %></li>
All right, it looks like I figured out the answer to my own question about 5 minutes after putting up a bounty. It looks like current_page? will always return false on POST.
Here's the source code for current_page?:
# File actionpack/lib/action_view/helpers/url_helper.rb, line 588
def current_page?(options)
unless request
raise "You cannot use helpers that need to determine the current " "page unless your view context provides a Request object " "in a #request method"
end
return false unless request.get?
url_string = url_for(options)
# We ignore any extra parameters in the request_uri if the
# submitted url doesn't have any either. This lets the function
# work with things like ?order=asc
if url_string.index("?")
request_uri = request.fullpath
else
request_uri = request.path
end
if url_string =~ %r^\w+:\/\//
url_string == "#{request.protocol}#{request.host_with_port}#{request_uri}"
else
url_string == request_uri
end
end
I don't really understand why they would have gone out of their way to make current_page? work only for GET requests, but at least now I know that that's the way it is.
You could create a new current_path? method in your ApplicationHelper:
def current_path?(*paths)
return true if paths.include?(request.path)
false
end
Pass in one or more paths and it returns true if any match the user's current path:
current_path?('/user/new')
current_path?(root_path)
current_path?(new_user_path, users_path '/foo/bar')
Or, you can create a new current_request? helper method to check the Rails controller + action:
def current_request?(*requests)
return true if requests.include?({
controller: controller.controller_name,
action: controller.action_name
})
false
end
Pass in one or more controller + action and it returns true if any match the user's current request:
current_request?(controller: 'users', action: 'new')
current_request?({controller: 'users', action: 'new'}, {controller: 'users', action: 'create'})
==UPDATE==
Ok, I decided to make using current_request? a little less verbose by not requiring that you type out the controller when you are trying to match multiple actions:
def current_request?(*requests)
requests.each do |request|
if request[:controller] == controller.controller_name
return true if request[:action].is_a?(Array) && request[:action].include?(controller.action_name)
return true if request[:action] == controller.action_name
end
end
false
end
Now you can do this:
current_request?(controller: 'users', action: ['new', 'create'])
I was having the same problem when using POST. My solution was to do something like this
def menu_item link_text, link_path
link_class = (request.original_url.end_with? link_path) ? 'active' : ''
content_tag :li, link_to(link_text, link_path), class: link_class
end
where link_path is just url_for(action: 'action', controller: 'controller')

Rails 3: Controller params default value

I am using a remote form_for for my show action to retrieve content based on the params passed by this form.
= form_tag modelname_path(#modelname), :id=>"select_content_form", :remote => true, :method => 'get' do
= text_field_tag :content_type, params[:content_type], :id=>"select_content_type"
= submit_tag "submit", :name => nil, :id=>"select_content_submit"
And I alter the content in controller as follows:
# Default params to "type1" for initial load
if params[:content_type]
#content_type = params[:content_type];
else
#content_type = "type1"
end
case #content_type
when "type1"
# get the content
#model_content = ...
when "type1"
# get the content
#model_content = ...
My question is, whether the above approach is the only we can set defaults for params or can we do it in a better manner. This works but I would like to know if this is the right approach.
UPDATE
Based on the suggestion below, I used the following and got an error on defaults.merge line:
defaults = {:content_type=>"type1"}
params = defaults.merge(params)
#content_type = params[:content_type]
A good way of setting default options is to have them in a hash, and merge your incoming options onto it. In the code below, defaults.merge(params) will overwrite any values from the params hash over the default ones.
def controller_method
defaults = {:content=>"Default Content", :content_type=>"type1"}
params = defaults.merge(params)
# now any blank params have default values
#content_type = params[:content_type]
case #content_type
when "type1"
#model_content = "Type One Content"
when "type2"
#etc etc etc
end
end
If there is a static list of types you could make it a dropdown box and just don't include a blank option so that something is always selected. But if you're stuck with a textbox you could clean up the controller action by using a before filter:
class FoosController < ActionController::Base
before_filter :set_content_type, :only => [:foo_action]
def foo_action
...
end
protected
def set_content_type
params[:content_type] ||= "type1"
end
end
I wanted to add to this discussion a working way to set default params:
defaults = { foo: 'a', bar: 'b' }
params.replace(defaults.merge(params))
This avoids assigning a local variable via "params =".

Rails3: Prefix to all url_for URLS

I add i18n to my webpage (different content for different languages). My URLs look loke this:
http://host.tld/de/news/15
http://host.tld/en/news/15
...
All my URLs in the application are set by the link_to/url_for method like this
url_for("/news/#{news.id}/#{urlify(news.title)}")
url_for("/news/#{#news.section}")
...
My routing looks like this:
scope "/:language/", :language => /de|en/ do
match "news/:news_id(/:title)" => "news#show_entry", :constraints => { :news_id => /[0-9]+/ }
...
end
I add this to my ApplicationController:
def default_url_options(options={})
{:language => I18n.locale}
end
Now I want to add the language prefix to ALL the URLs without change all the url_for()-calls. Is there a solution (parameter/config-option or something) to add this prefix? It should work with relative paths, too.
If you are looking not to change all the url for calls you could add a method in the application_helper.rb file to override the existing methods to add in the language
def url_for(options={})
if options[:language]
'/options[:language]' + super
else
super
end
end
def link_tolink_to(*args, &block)
options = args[1] || {}
if options[:language]
'/options[:language]' + super
else
super
end
end

XML parsing from API with Nokogiri and Rails 3

Hi Im trying to parse XML from a websites API with Nokogiri. Im just curious to see if Im on the right track. I have a controller wich handles the parsing and then I would like the model to initialize the necessary parameters and then display it as a simple list in the view. I was thinking something like this in the Controller:
def index
doc = Nokogiri::XML(open("http://www.mysomething.com/partner/api/1_0/somerandomkeynumber4b0/channel/11number/material/list/").read)
#news = []
doc.css("news").each do |n|
header = n.css("header").text
source_name = n.css("source_name").text
summary = n.css("summary").text
url = i.css("url").text
created_at = i.css("created_at").text
type_of_media = i.css("type_of_media").text
#news << News.new(
:header => header,)
end
and then the Model:
class News
include ActiveModel::Validations
validates_presence_of :url, :type_of_media
attr_accessor :header, :source_name, :summary, :url, :created_at, :type_of_media
def initialize(attributes = {})
#header = attributes[:header]
#source_name = attributes[:source_name]
#summary = attributes[:summary]
#url = attributes[:url]
#created_at = attributes[:created_at]
#type_of_media = attributes[:type_of_media]
end
Is this how you would do this?! Not sure Im thinking correct on this. Maybe you have any tips on a great way of incorporating Nokogiri with some other thing for the view like Google maps or something. Right now Im getting an error saying
Missing template news/index with {:formats=>[:html], :handlers=>[:builder, :rjs, :erb, :rhtml, :rxml], :locale=>[:en, :en]} in view paths
Thanks in advance!
#noodle: So this:
#news = doc.css('query').map do |n|
h = {}
%w(header source_name summary url created_at type_of_media).each do |key|
h[key.to_sym] = n.css(key).text
end
News.new(h)
end
Is equal to:
#news = []
doc.css("news").each do |n|
header = n.css("header").text
source_name = n.css("source_name").text
summary = n.css("summary").text
url = i.css("url").text
created_at = i.css("created_at").text
type_of_media = i.css("type_of_media").text
#news << News.new(
:header => header,)
end
Did I understand you correctly?! Regarding the template I have located the the problem. It was a minor misspelling. Cheers!
You're really asking two questions here..
Is my xml -> parse -> populate pipeline ok?
Yes, pretty much. As there's no conditional logic in your .each block it would be cleaner to do it like this:
#news = doc.css('query').map do |n|
#...
News.new(:blah => blah, ...)
end
.. but that's a minor point.
EDIT
You could save some typing by initializing a hash from the parsed xml and then passing that to Model.new, like:
#news = doc.css('query').map do |n|
h = {}
h[:header] = n.css('header').text
# ...
News.new(h)
end
EDIT 2
Or even shorter..
#news = doc.css('query').map do |n|
h = {}
%w(header source_name summary url created_at type_of_media).each do |key|
h[key.to_sym] = n.css(key).text
end
News.new(h)
end
In fact #inject could make that shorter still but I think that'd be a little obfuscated.
Why can't rails find my view template?
Dunno, is there one? You've not given enough details to answer that part.