Appending empty query string param to the URL in Rails - ruby-on-rails

Is there a way to append something to the query string with no value set?
I would like to see this kind of URL being generated: http://local/things?magic.
What I'm looking for is when the user goes to http://local/other?magic then every URL in the generated page would contain magic in the end.
Following code is almost a solution but it gives me http://local/things?magic=. There is an extra = that I don't want there.
def default_url_options(options)
if params.has_key?("magic")
{ :magic => "" }
end
end
Setting { :magic => nil } will remove the magic entirely from the generated URLs.

Edited due to the changed question on the comment below:
Ah I see.
You could easily use the same URL for all devices and just separate by checking the request environment for HTTP_USER_AGENT.
But if this distinction is not enough (I think it is enough for more than 80% of all cases) you could do the following:
in config/routes.rb:
map.with_options :prefix => '/m', :format => 'mobile' do |mobile|
mobile.resources :apples
mobile.resource :user
mobile.connect #...
end
# generates
# /m/apples
# /m/apples/new
# ...
# /m/user
# ...
I have not tested this, but maybe you will have to add a MIME type mapping for :format => 'mobile'.

This is what cookies are for! cookies[:magic] = 'magic' and then it all automatically works just the way you're looking for. Not with URL query-string parameters, but whenever the browser requests another URL (until the browser is closed), it will include the cookie in the request.

Related

Case insensitive user names in Rails 3 routes

I am trying to allow users to access their accounts by their user name, regardless of how they capitalize it in the URL. So http://example.com/Username would go to the same page as http://example.com/username.
I am fine with the any other part of the URL being case sensitive (I know that's the standard), but I don't see a need to force a user to get the case of their user name correct in the URL.
Can this be done by adding certain settings in the routes.rb file alone?
I thought this would be straightforward but apparently it isn't.
I found a seemingly simple question here but there's only one answer that I think will help and it's too hacky (by the author's own admission)
I believe this isn't a routing issue and you can address it in your controller code:
User.where("lower(username) = lower(?)", params[:username]).first!
or
User.where("lower(username) = ?", params[:username].downcase).first!
or
User.find(:first, :conditions => [ "lower(username) = ?", params[:username].downcase ])
It should work fine to handle this behaviour in the controller, not in the routes.
# config/routes.rb
match ':username' => 'users#show'
# app/controllers/users_controller.rb
def show
username = params[:username]
user = User.find_by_username(username) || User.find_by_username(username.downcase)
# or something along these lines...
end
An even nicer solution might be to store some kind of slug identification for the user that is always downcased and ready to be used in URLs. You could have a look at the awesome friendly_id gem for that purpose.

Collecting first segments of all routes in rails 3

I'm trying to implement routes where the first segment is the profile's alias or id:
resources :profiles, :path => '' do
...
end
And I need to validate that alias is not already taken by first segments of other(higher) routes. What I have now is:
validates :alias, :exclusion => {:in => Rails.application.routes.routes.map{|r| r.path.spec.to_s.split('(').first.split('/').second}.compact.uniq },
....
In development everything is ok. In production Rails.application.routes.routes.map... returns empty array. But only inside validation in model, if I put it somewhere in view just to test it returns array of first segments of all routes as expected. What am I doing wrong or maybe there is a better solution?
I'd guess that you have a timing problem. Your routing table in Rails.application.routes probably hasn't been built when your model is loaded in production mode; but, in development mode, your model is probably being reloaded on each request so Rails.application.routes will be populated when your model is loaded and your validates call:
validates :alias, :exclusion => { :in => Rails.application.routes.routes.map { ... } }
is executed.
An easy solution would be to switch to a validation method:
class Model < ActiveRecord::Base
validate :alias_isnt_a_route, :if => :alias_changed?
private
def alias_isnt_a_route
routes = Rails.application.routes.routes.map { ... }
if(routes.include?(alias))
errors.add(:alias, "Alias #{alias} is already used for a route")
end
end
This way, you don't look at Rails.application.routes until you need to check an alias and by that time, the routes will have been loaded. You could, of course, cache the route prefix list if you wanted to.
You'll also want to add some sanity checking to your application's initialization phase. Someone in your production environment could add, say, 'pancakes' as their alias while you add a /pancakes route while developing: your validation will miss this new conflict. Something simple like this:
config.after_initialize do
Rails.application.reload_routes! # Make sure the routes have been loaded.
# Compare all the existing aliases against the route prefixes and raise an
# exception if there is a conflict.
end
in your config/application.rb would be sufficient.

Rails: Upadating only a certain field using javascript

I am writing an web application which runs on iPads in taxis. Now i want every device to call the server every X minutes and say "i am still alive".
For that reason i have a 'latest_activity' field which i would like to update with rails' Time.now
What's the best way to do this? I was thinking about sending a GET to "/app?update_id=52" and then rails would look for params[:update_id] and update the field but this feels so dirty and i think there has to be a better way to do this.
Updated with solution below
I would PUT to the update action in the controller for whatever has the latest_activity field. So say the field is on Client, I would use RESTful style routes like so:
# routes.rb
resources :clients
And then use the endpoint:
PUT /clients/52
And of course then ClientsController gets an update method which sets the field to Time.now as you already mentioned.
Backbone supports this type of url scheme natively/easily. You have a Client model on the frontend, you simply do:
someClient.set( { latest_activity: new Date() } );
someClient.save();
Although that sort of says to me that you might just let the frontend generate the timestamp rather than doing it in rails.. otherwise you are really just sending a fake value (either a real date that wont be used or just, say, 1 and then 'notice' that on the backend). If you send a real timestamp you can just do:
# app/controllers/clients_controller.rb
def update
c = Client.find( params[:id] )
c.latest_activity = params[:latest_activity]
c.save
render :json => { :ok => true }
end
Or something along those lines.
Solution
I've taken a slightly different path. Now i've added a ping method to my devices_controller
def ping
if params[:id]
#device = Device.find(params[:id])
#device.latest_activity = Time.now
#device.save()
end
respond_with(#device)
end
And added a member to the Devices resource like so
resources :devices do
# Device Ping ('/api/v1/devices/#{id}/ping')
put 'ping', :on => :member
end
Now i am just sending a PUT request to /api/v1/devices/ID/ping, from Backbone.js in my case, like so
ping: ->
if localStorage.device_id?
setInterval =>
$.ajax
type: "PUT"
url: "/api/v1/devices/#{localStorage.device_id}/ping"
, 1000 * 60 * 15 # every 15 Minutes

Add http(s) to URL if it's not there?

I'm using this regex in my model to validate an URL submitted by the user. I don't want to force the user to type the http part, but would like to add it myself if it's not there.
validates :url, :format => { :with => /^((http|https):\/\/)?[a-z0-9]+([-.]{1}[a-z0-9]+).[a-z]{2,5}(:[0-9]{1,5})?(\/.)?$/ix, :message => " is not valid" }
Any idea how I could do that? I have very little experience with validation and regex..
Use a before filter to add it if it is not there:
before_validation :smart_add_url_protocol
protected
def smart_add_url_protocol
unless url[/\Ahttp:\/\//] || url[/\Ahttps:\/\//]
self.url = "http://#{url}"
end
end
Leave the validation you have in, that way if they make a typo they can correct the protocol.
Don't do this with a regex, use URI.parse to pull it apart and then see if there is a scheme on the URL:
u = URI.parse('/pancakes')
if(!u.scheme)
# prepend http:// and try again
elsif(%w{http https}.include?(u.scheme))
# you're okay
else
# you've been give some other kind of
# URL and might want to complain about it
end
Using the URI library for this also makes it easy to clean up any stray nonsense (such as userinfo) that someone might try to put into a URL.
The accepted answer is quite okay.
But if the field (url) is optional, it may raise an error such as undefined method + for nil class.
The following should resolve that:
def smart_add_url_protocol
if self.url && !url_protocol_present?
self.url = "http://#{self.url}"
end
end
def url_protocol_present?
self.url[/\Ahttp:\/\//] || self.url[/\Ahttps:\/\//]
end
Preface, justification and how it should be done
I hate it when people change model in a before_validation hook. Then when someday it happens that for some reason models need to be persisted with save(validate: false), then some filter that was suppose to be always run on assigned fields does not get run. Sure, having invalid data is usually something you want to avoid, but there would be no need for such option if it wasn't used. Another problem with it is that every time you ask from a model is it valid these modifications also take place. The fact that simply asking if a model is valid may result in the model getting modified is just unexpected, perhaps even unwanted. There for if I'd have to choose a hook I'd go for before_save hook. However, that won't do it for me since we provide preview views for our models and that would break the URIs in the preview view since the hook would never get called. There for, I decided it's best to separate the concept in to a module or concern and provide a nice way for one to apply a "monkey patch" ensuring that changing the fields value always runs through a filter that adds a default protocol if it is missing.
The module
#app/models/helpers/uri_field.rb
module Helpers::URIField
def ensure_valid_protocol_in_uri(field, default_protocol = "http", protocols_matcher="https?")
alias_method "original_#{field}=", "#{field}="
define_method "#{field}=" do |new_uri|
if "#{field}_changed?"
if new_uri.present? and not new_uri =~ /^#{protocols_matcher}:\/\//
new_uri = "#{default_protocol}://#{new_uri}"
end
self.send("original_#{field}=", new_uri)
end
end
end
end
In your model
extend Helpers::URIField
ensure_valid_protocol_in_uri :url
#Should you wish to default to https or support other protocols e.g. ftp, it is
#easy to extend this solution to cover those cases as well
#e.g. with something like this
#ensure_valid_protocol_in_uri :url, "https", "https?|ftp"
As a concern
If for some reason, you'd rather use the Rails Concern pattern it is easy to convert the above module to a concern module (it is used in an exactly similar way, except you use include Concerns::URIField:
#app/models/concerns/uri_field.rb
module Concerns::URIField
extend ActiveSupport::Concern
included do
def self.ensure_valid_protocol_in_uri(field, default_protocol = "http", protocols_matcher="https?")
alias_method "original_#{field}=", "#{field}="
define_method "#{field}=" do |new_uri|
if "#{field}_changed?"
if new_uri.present? and not new_uri =~ /^#{protocols_matcher}:\/\//
new_uri = "#{default_protocol}://#{new_uri}"
end
self.send("original_#{field}=", new_uri)
end
end
end
end
end
P.S. The above approaches were tested with Rails 3 and Mongoid 2.
P.P.S If you find this method redefinition and aliasing too magical you could opt not to override the method, but rather use the virtual field pattern, much like password (virtual, mass assignable) and encrypted_password (gets persisted, non mass assignable) and use a sanitize_url (virtual, mass assignable) and url (gets persisted, non mass assignable).
Based on mu's answer, here's the code I'm using in my model. This runs when :link is saved without the need for model filters. Super is required to call the default save method.
def link=(_link)
u=URI.parse(_link)
if (!u.scheme)
link = "http://" + _link
else
link = _link
end
super(link)
end
Using some of the aforementioned regexps, here is a handy method for overriding the default url on a model (If your ActiveRecord model has an 'url' column, for instance)
def url
_url = read_attribute(:url).try(:downcase)
if(_url.present?)
unless _url[/\Ahttp:\/\//] || _url[/\Ahttps:\/\//]
_url = "http://#{_url}"
end
end
_url
end
I had to do it for multiple columns on the same model.
before_validation :add_url_protocol
def add_url_protocol
[
:facebook_url, :instagram_url, :linkedin_url,
:tiktok_url, :youtube_url, :twitter_url, :twitch_url
].each do |url_method|
url = self.send(url_method)
if url.present? && !(%w{http https}.include?(URI.parse(url).scheme))
self.send("#{url_method.to_s}=", 'https://'.concat(url))
end
end
end
I wouldn't try to do that in the validation, since it's not really part of the validation.
Have the validation optionally check for it; if they screw it up it'll be a validation error, which is good.
Consider using a callback (after_create, after_validation, whatever) to prepend a protocol if there isn't one there already.
(I voted up the other answers; I think they're both better than mine. But here's another option :)

Friendly Form Validations (Rails)

I checked out both of these previously-asked questions, and they're a help but not a full solution for my case.
Essentially I need to validate a user-submitted URL from a form. I've started by validating that it begins with http://, https://, or ftp:// :
class Link < ActiveRecord::Base
validates_format_of [:link1, :link2, :link3,
:link4, :link5], :with => /^(http|https|ftp):\/\/.*/
end
That works great for what it's doing, but I need to go these two steps further:
Users should be allowed to leave the form fields blank if needed, and
If the URL provided by the user does not already start with http:// (say they enter google.com, for example), it should pass the validation but add the http:// prefix while being processed.
I'm having a hard time determining how to make this work cleanly and efficiently.
FYI, you don't have to pass an array to validates_format_of. Ruby will do arrays automagically (Rails parses the output of *args).
So, for your question, I'd go for something like this:
class Link < ActiveRecord::Base
validate :proper_link_format
private
def proper_link_format
[:link1, :link2, :link3, :link4, :link5].each do |attribute|
case self[attribute]
when nil, "", /^(http|https|ftp):\/\//
# Allow nil/blank. If it starts with http/https/ftp, pass it through also.
break
else
# Append http
self[attribute] = "http://#{self[attribute]}"
end
end
end
end
Just to add to the above, I use the Ruby URI module to parse URLs for validity.
http://www.ruby-doc.org/stdlib/libdoc/uri/rdoc/classes/URI.html
It works really well and it helps me to avoid regexes.

Resources