RESTful routes in Padrino controllers - sinatra

Padrino supports the idea of nested routes. Here's one example from the documentation:
SimpleApp.controllers :product, :parent => :user do
get :index do
# "/user/#{params[:user_id]}/product"
end
get :show, :with => :id do
# "/user/#{params[:user_id]}/product/show/#{params[:id]}"
end
end
However, what I'd like is to be able to have the following mappings:
GET /users # '/' in :users controller
GET /users/:id # '/:id' in :users controller
GET /users/:user_id/tweets # '/' in :tweets controller
GET /users/:user_id/tweets/:id # '/:id' in :tweets controller
GET /tweets # '/' in :tweets controller, too
GET /tweets/:id # '/:id' in :tweets controller, too
Is that possible?

SimpleApp.controllers :tweets, :parent => :user do
get :index do
# "/user/#{params[:user_id]}/tweets"
end
get :index, :with => :id do
# "/user/#{params[:user_id]}/tweets/#{params[:id]}"
end
end

Have you tried :optional => true in your route defininition (1st line)?

Related

How to run multiple Rack::Handler::WEBrick in one Sinatra/Grape file

Basically I have multiple API applications in one Sinatra / GrapeAPI file
require 'sinatra'
require 'grape'
require 'webrick'
require 'webrick/https'
require 'openssl'
CERT_PATH = '/opt/myCA/server/'
class WebApp < Sinatra::Base
post '/' do
"Hellow, world!"
end
end
class Api1 < Grape::API
get '/test1' do
{xyz: 'test1' }
end
end
class Api2 < Grape::API
get '/test2' do
{xyz: 'test2' }
end
end
webrick_options1 = {
:Port => 8443,
:Logger => WEBrick::Log::new($stderr, WEBrick::Log::DEBUG),
:DocumentRoot => "/ruby/htdocs",
:SSLEnable => true,
:SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE,
:SSLCertificate => OpenSSL::X509::Certificate.new( File.open(File.join(CERT_PATH, "my-server.crt")).read),
:SSLPrivateKey => OpenSSL::PKey::RSA.new( File.open(File.join(CERT_PATH, "my-server.key")).read),
:SSLCertName => [ [ "CN",WEBrick::Utils::getservername ] ]
}
webrick_options2 = {...}
Rack::Handler::WEBrick.run Api1, webrick_options1 # this will work
Rack::Handler::WEBrick.run Api2, webrick_options2 # but when I try another one,
# the other will not
Grape gem is recommending to use cascade:
run Rack::Cascade.new [Api1, Api2, WebApp]
However this will ignore my precious SSL settings.
How can I run multiple server configurations?

Rails 4 Devise 3 confirmation token invalid when no password required

I can not successfully confirm my user. Many posts have been made about an invalid token but most of them were due to the changes from Devise 2 to Devise 3. That is not my issue.
I over rid my user confirmation using
https://github.com/plataformatec/devise/wiki/How-To:-Override-confirmations-so-users-can-pick-their-own-passwords-as-part-of-confirmation-activation
app/controllers/confirmations_controller.rb
class ConfirmationsController < Devise::ConfirmationsController
# Remove the first skip_before_filter (:require_no_authentication) if you
# don't want to enable logged users to access the confirmation page.
skip_before_filter :require_no_authentication
skip_before_filter :authenticate_user!
# PUT /resource/confirmation
def update
with_unconfirmed_confirmable do
if #confirmable.has_no_password?
#confirmable.attempt_set_password(params[:user])
if #confirmable.valid?
do_confirm
else
do_show
#confirmable.errors.clear #so that we wont render :new
end
else
self.class.add_error_on(self, :email, :password_already_set)
end
end
if !#confirmable.errors.empty?
render 'devise/confirmations/new' #Change this if you don't have the views on default path
end
end
# GET /resource/confirmation?confirmation_token=abcdef
def show
with_unconfirmed_confirmable do
if #confirmable.has_no_password?
do_show
else
do_confirm
end
end
if !#confirmable.errors.empty?
self.resource = #confirmable
render 'devise/confirmations/new' #Change this if you don't have the views on default path
end
end
protected
def with_unconfirmed_confirmable
original_token = params[:confirmation_token]
confirmation_token = Devise.token_generator.digest(User, :confirmation_token, original_token)
#confirmable = User.find_or_initialize_with_error_by(:confirmation_token, confirmation_token)
if !#confirmable.new_record?
#confirmable.only_if_unconfirmed {yield}
end
end
def do_show
#confirmation_token = params[:confirmation_token]
#requires_password = true
self.resource = #confirmable
render 'devise/confirmations/show' #Change this if you don't have the views on default path
end
def do_confirm
#confirmable.confirm!
set_flash_message :notice, :confirmed
sign_in_and_redirect(resource_name, #confirmable)
end
end
The HTML that is sent to the user for confirmation in the email is
<%= link_to 'Confirm', confirmation_url(#resource, :confirmation_token => #token) %>
The problem I am having is that initially the user is being sent to the show page since I by-passed the need for a password and no confirmation is made but no error is shown.
# GET /resource/confirmation?confirmation_token=abcdef
def show
with_unconfirmed_confirmable do
if #confirmable.has_no_password?
do_show
else
do_confirm
end
end
When I removed the if statement to leave only
# GET /resource/confirmation?confirmation_token=abcdef
def show
with_unconfirmed_confirmable do
do_confirm
end
The user is now sent to the confirmation New page but with the error
Confirmation token is invalid
My routes are
as :user do
patch '/user/confirmation' => 'confirmations#update', :via => :patch, :as => :update_user_confirmation
end
devise_for :users, :controllers => { :confirmations => "confirmations", registrations: "registrations" }
devise_scope :user do
authenticated :user do
root :to => 'devise/sessions#destroy', as: :authenticated_root
end
unauthenticated :user do
root :to => 'devise/registrations#new', as: :unauthenticated_root
end
end
Why is this token not working correctly?

How can I use a local (or per view) variable in Sinatra with Haml partials?

I have a Haml partial in Sinatra to handle all of my 'page open' items like meta tags.
I would love to have a variable for page_title in this partial and then set that variable per view.
Something like this in the partial:
%title #page_title
Then in the view, be allowed to do something like:
#page_title = "This is the page title, BOOM!"
I have read a lot of questions/posts, etc. but I don't know how to ask for the solution to what I am trying to do. I'm coming from Rails where our devs usually used content_for but they set all that up. I'm really trying to learn how this works. It seems like I have to define it and use :locals in some way but I haven't figured it out. Thank you in advance for any tips!
You pass variables into Sinatra haml partials like this:
page.haml
!!!
%html{:lang => 'eng'}
%body
= haml :'_header', :locals => {:title => "BOOM!"}
_header.haml
%head
%meta{:charset => 'utf-8'}
%title= locals[:title]
In the case of a page title I just do something like this in my layout btw:
layout.haml
%title= #title || 'hardcoded title default'
Then set the value of #title in routes (with a helper to keep it short).
But if your header is a partial then you can combine the two examples like:
layout.haml
!!!
%html{:lang => 'eng'}
%body
= haml :'_header', :locals => {:title => #title}
_header.haml
%head
%meta{:charset => 'utf-8'}
%title= locals[:title]
app.rb
helpers do
def title(str = nil)
# helper for formatting your title string
if str
str + ' | Site'
else
'Site'
end
end
end
get '/somepage/:thing' do
# declare it in a route
#title = title(params[:thing])
end

How do I submit this form in Rails so it lands at /users/:id?

I'm trying to do something that seems conceptually simple, but I just can't get it working. Here's what I'm trying to do:
I have a simple "search" form on the /users/index page. It
leverages jQuery Tokeninput to autocomplete a user (name/username)
when the current user types into the search field. What I want to do
is let the user type a name, select a user from the list, then click
"submit" and be taken to the selected user's profile (/users/:id/ - which is
Users#show). I have jQuery Tokeinput configured to submit the user_id as :user_token.
I can't seem to get this working. The autocomplete part works correctly, but I can't figure out how to "submit" so that the entered user's profile is shown.
Here's what happens when I hit the "submit" button on the form (pulled from the development log in the terminal):
Started PUT "/users/2" for 127.0.0.1 at 2012-05-08 11:19:56 -0400
Processing by UsersController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"blah blah=", "user"=>{"user_token"=>"41"}, "commit"=>"Go to profile", "id"=>"2"}
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = 2 LIMIT 1
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", "2"]]
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1
Completed 500 Internal Server Error in 83ms
NoMethodError (undefined method `downcase!' for nil:NilClass):
app/controllers/users_controller.rb:119:in `update'
So it's calling the update action on the users controller, I assume because "#user" already exists (specifically, it's the current_user who clicks the submit button).
On screen, I see:
The path shown up top is .../users/2 (the id of current user), and in
the browser I see:
NoMethodError in UsersController#update
undefined method `downcase!' for nil:NilClass
I'm getting that because it's trying to run the "update" action in the Users controller, and there's a "downcase!" call on one of the params at the beginning up the update action. That parameter ([:user][:email]) obviously doesn't exist since it's not in the form I'm submitting.
What I really want to do is go to "/users/41" (the show page for the user whose id is passed as params[:user][:user_token]). How do I do this?
Here's all the relevant code:
#users_controller.rb#Index
def index
#title = "All users"
#label = "All users"
#list_users = User.order(:name).page(params[:page]) #generates users shown on index page
#user = current_user
# This is used to populate the autocomplete field in the little search form
#users = User.where("LOWER(name) like ? OR LOWER(username) like ?", "%#{params[:q].downcase}%", "%#{params[:q].downcase}%").order('name ASC').limit(10) if params[:q]
respond_to do |format|
format.html # index.html.erb
format.json { render json: #users, :only => [:id, :name, :username] }
end
end
My routes...
#routes.rb
resources :comments
resources :invitations
resources :sessions, :only => [:new, :create, :destroy]
resources :shares, :controller => "item_shares", :as => "item_shares" do
resources :comments
end
resources :posts, :controller => "item_posts", :as => "item_posts" do
resources :comments
end
resources :items
resources :relationships, only: [:create, :destroy]
resources :users do
member do
get :following, :followers
end
end
resources :password_resets
match '/signup', :to => 'users#new'
match '/signup/:invitation_token' => 'users#new', :as => :signup_with_invitation
match '/signin', :to => 'sessions#new'
match '/signout', :to => 'sessions#destroy'
match '/invite', :to => 'invitations#new'
match '/users/:id/shared', :to => 'users#shared'
match '/users/:id/received', :to => 'users#received'
match '/users/:id/saved', :to => 'users#saved'
match '/users/:id/posts', :to => 'users#posts'
match '/reciprocal_followers', :to => 'users#reciprocal_followers'
root :to => 'pages#home'
Here is my form (this definitely does NOT work, although the jQuery Tokeninput does work):
#_user_search_form.html.erb
<div class="form">
<span class="form-label-right round-bottom-left-5 round-top-right-5 gray-gradient">Find someone</span>
<%= form_for #user, :action => "show" do |f| %>
<div class="field">
<%= f.label :user_token, "Name or Username" %></br>
<%= f.text_field :user_token, :placeholder => 'John Doe or JohnDoe123', "data-pre" => (#pre_populate_data.to_json(:only => [:id, :name, :username]) unless #pre_populate_data.nil?) %>
</div>
<div class="actions">
<%= f.submit "Go to profile" %>
</div>
<% end %>
</div>
Here's the relevant part of my user model:
#user.rb
attr_accessible :user_token
attr_reader :user_token
<% form_for #project, :url => { :controller => "project", :action => "thumbnail_save" } do |form| %>
....
<% end %>
Also write something like this in your config file (put it at the top)
get "users/show"
If that doesn't work try this. (you might have to change your form for below to work)
match "project/thumbnail_save", :to => "project#thumbnail_save"
Let us know if any more issues.

Sinatra on Thin: How to hide or change HTTP 'Server' response header

What is the cleanest way to do this? Some Rack middleware? I tried to modify env['SERVER_SOFTWARE'] but I still get in response:
Server: thin 1.3.1 codename Triple Espresso
How to change the value of that header, or remove it completetly from response?
EDIT
Another try:
before do
headers 'Server' => 'ipm'
end
after do
headers 'Server' => 'ipm'
end
But still no changes.
This works here:
require 'sinatra'
get '/' do
[200, {'Server' => 'My Server'}, 'contents']
end
If you want to do it for all requests:
class ChangeServer
def initialize(app)
#app = app
end
def call(env)
res = #app.call(env)
res[1]['Server'] = 'My server'
return res
end
end
And then you use ChangeServer in your app.