Why does invoking a method with parameters return a 404 error? - ruby

I'm pretty new to Ruby, I know a little bit of Sinatra but what I actually need for my app is Grape for rest api.
Working with a method with parameters at all works like a charm, but when I'm trying to add parameters I get 404 not found exception.
Where am I going wrong here? Thanks
resource :devs do
desc "Get all devs"
get do
authenticate!
Dev.all
end
desc "Get dev by email"
params do
requires :email, type: String, desc: "Dev's email"
end
route_param :email do
get do
authenticate!
#devs = Dev.all(:email == params[:email])
#!error('email not found', 204) unless #devs.length > 0
end
end
desc "Get dev by API key"
get :key do
authenticate!
#dev = Dev.first(:api_key == params[:key])
!error('email not found', 204) unless #devs.length > 0
end
end
This is the call I make in PostMan (I also added the header for Apikey there)
localhost:9292/devs/email/orelzion#gmail.com
But it always give me the same result 404

The route_param directive doesn't work the way you think it does. Given the code you posted, the link you should be visiting is localhost:9292/devs/orelzion#gmail.com.
If you want the code to match the URL you wrote instead, use a nested namespace (or equivalently, a nested resource):
resource :devs do
...
namespace :email do
desc "Get dev by email"
params do
requires :email, type: String, desc: "Dev's email"
end
route_param :email do
get do
...
end
end
end
...

Related

Can't understand Grape API route param

I am having a lot of trouble understanding Grape API, specifically route_param and how it works with just params.
Consider this code:
desc "Return a status."
params do
requires :id, type: Integer, desc: "Status id."
end
route_param :id do
get do
Status.find(param[:id])
end
end
What route does this block produce? I get that this is a get request, but why is it wrapped in route_param block? Why can't it be in params block?
Your block produces this route:
http://yourdomain.com/<resource>/<id>
Note that your code and the code below do the same thing and produce the same route:
desc "Return a status."
params do
requires :id, type: Integer, desc: "Status id."
end
get ':id' do
Status.find(params[:id])
end
You can use route_param to group methods that receive the same params, for example:
resource :categories do
route_param :id do
get do # produces the route GET /categories/:id
end
put do # produces the route PUT /categories/:id
end
end
end

How do I improve this RSpec code in rspec way?

I'm new to Ruby and RSpec. I come from Java background that's why my test really looks like junit code. I'm trying to learn more about RSpec but I don't quite understand subject, let, !let. So, if anybody can guide me to clean up this code, I'd be really appreciated.
I have sinatra, RSpec which it's doing Sign-in with Twitter.
get '/login/twitter' do
begin
request_token = TwitterService.new.authentication_request_token
session[:request_token_twitter] = request_token
redirect request_token.authorize_url
rescue Exception => e
logger.error(e.message)
redirect '/'
end
end
get '/login/twitter/success' do
request_token = session[:request_token_twitter]
twitter_service = TwitterService.new
access_token = twitter_service.authorize(request_token, params[:oauth_verifier])
begin
twitter_user_info = twitter_service.verify_credentials
twitter_id = twitter_user_info["id"]
response.set_cookie("auth_token", :value => twitter_id, :path => '/')
response.set_cookie(#social_flag, :value => "t", :path => '/')
expected_user = #user_manager.find_by_id(twitter_id.to_s)
if expected_user.is_null?
twitter_user = User.new(twitter_id, access_token.token, access_token.secret, "t")
twitter_user.save
logger.info("Saving ...")
logger.info("Twitter ID #{twitter_id}")
redirect '/signup'
else
expected_user.token = access_token.token
expected_user.secret = access_token.secret
expected_user.update
logger.info("Updating token and secret ...")
logger.info("Twitter ID #{twitter_id}")
end
rescue Exception => e
logger.error(e.message)
logger.error("There's something wrong with Twitter and user cannot log in")
redirect '/'
end
redirect '/t'
end
And here's my RSpec. I know it's really ugly.
describe "Twitter route" do
include TwitterOAuth
def app
Sinatra::Application
end
context "/login/twitter" do
it "should redirect to twitter authorized url" do
request_token = OpenStruct.new
request_token.authorize_url = "http://api.twitter.com/oauth/authenticate?oauth_token"
TwitterService.any_instance.stub(:authentication_request_token).and_return(request_token)
get '/login/twitter'
last_response.header["Location"].should include "http://api.twitter.com/oauth/authenticate?oauth_token"
last_response.status.should eql 302
session[:request_token_twitter].authorize_url.should == "http://api.twitter.com/oauth/authenticate?oauth_token"
end
it "should redirect back to home page if error occurs" do
TwitterService.any_instance.stub(:authentication_request_token).and_raise("Unauthorized")
get '/login/twitter'
last_response.header["Location"].should include "http://example.org/"
last_response.status.should eql 302
session[:request_token_twitter].should eql nil
end
it "should save a user after a success callback from twitter" do
user_manager = UserManager.new
access_token = OpenStruct.new
access_token.token = "token"
access_token.secret = "secret"
TwitterService.any_instance.stub(:authorize).with(anything(), anything()).and_return(access_token)
TwitterService.any_instance.stub(:verify_credentials).and_return({"id" => "id1"})
get '/login/twitter/success'
last_response.header["Location"].should include "/signup"
rack_mock_session.cookie_jar["auth_token"].should eql "id1"
rack_mock_session.cookie_jar["s_flag"].should eql "t"
last_response.status.should eql 302
user_manager = UserManager.new
expected_user = user_manager.find_by_id("id1")
expected_user.id.should eql "id1"
expected_user.token.should eql "token"
expected_user.secret.should eql "secret"
end
it "should update user token and secret if the user already exists" do
User.new("id1", "token", "secret", "t").save
access_token = OpenStruct.new
access_token.token = "token1"
access_token.secret = "secret1"
TwitterService.any_instance.stub(:authorize).with(anything(), anything()).and_return(access_token)
TwitterService.any_instance.stub(:verify_credentials).and_return({"id" => "id1"})
get '/login/twitter/success'
last_response.header["Location"].should include "/t"
rack_mock_session.cookie_jar["auth_token"].should eql "id1"
rack_mock_session.cookie_jar["s_flag"].should eql "t"
last_response.status.should eql 302
user_manager = UserManager.new
expected_user = user_manager.find_by_id("id1")
expected_user.id.should eql "id1"
expected_user.token.should eql "token1"
expected_user.secret.should eql "secret1"
end
it "should redirect back to the home page" do
access_token = OpenStruct.new
access_token.token = "token1"
access_token.secret = "secret1"
TwitterService.any_instance.stub(:authorize).with(anything(), anything()).and_return(access_token)
TwitterService.any_instance.stub(:verify_credentials).and_raise
get '/login/twitter/success'
last_response.header["Location"].should include "http://example.org/"
end
end
end
Any improvement I'd be grateful not just the code. May be if I miss something obvious.
Thanks a lot guys.
Okay, a lot happening here!
First off, you should try to stick to one test per example. Your examples are currently testing a whole bunch of behavior, which means that your tests are rather all-or-nothing, and may make it unclear what specifically breaks if you've broken something.
First off, I'm going to add a new matcher. You'd usually put this in somewhere like spec/support/matchers.rb or something. It's just going to extend rspec so that we can test that a response was a redirect, and that the redirect goes to a given location:
RSpec::Matchers.define :redirect_to do |expected|
match do |actual|
actual.should be_redirect
actual.location.should include expected
end
end
Now, onto the code!
The unannotated source is here: https://gist.github.com/cheald/5908093 - that will probably be less annoying to read :)
let defines a method that will run exactly once per example, no matter how many times it's invoked. This lets us have a "variable" that is defined at example-time, which lets us override it in nested examples. Here, I have access_token defined up top, but we'll let another access_token in a deeper example. This suite doesn't really show this off too well, but this lets you do nice things where something from one let is referenced in another. Imagine, if you will, that we have
let(:user) { user_manager.find(access_token.id) }
This will use the deepest-nested user_manager and deepest-nested access_token without having to redeclare user in each nested scope. Handy!
let blocks aren't invoked until they're used (as opposed to let! blocks, which are always invoked when declared)
describe "Twitter route" do
include TwitterOAuth
let(:app) { Sinatra::Application }
let(:request_token) { double("request_token", authorize_url: "http://api.twitter.com/oauth/authenticate?oauth_token") }
let(:access_token) { double("token", token: "token", secret: "secret") }
let(:user_manager) { UserManager.new }
You'll notice that I've broken your tests up into nested contexts, to group similar behavior. That is, all the tests that should pass with an authorized token get nested under the authorized token context, and our before block sets up the context so that all examples in this context get a valid token.
We also go ahead and do the get in the before block, so we can just test the results directly.
context "/login/twitter" do
context "with an authorized token" do
before do
TwitterService.any_instance.stub(:authentication_request_token).and_return(request_token)
TwitterService.any_instance.stub(:authorize).with(anything(), anything()).and_return(access_token)
TwitterService.any_instance.stub(:verify_credentials).and_return({"id" => "id1"})
get '/login/twitter'
end
You see here that I'm using our new matcher. It lets us check for a redirect to a given URL in one test.
it "should redirect to twitter authorized url" do
last_response.should redirect_to "http://api.twitter.com/oauth/authenticate?oauth_token"
end
it "should set a the request token in the session" do
session[:request_token_twitter].authorize_url.should == "http://api.twitter.com/oauth/authenticate?oauth_token"
end
context "after a success callback" do
let(:user) { user_manager.find_by_id("id1") }
context "when there is not an existing user" do
before do
get '/login/twitter/success'
end
it "should redirect to /signup" do
last_response.should redirect_to "/signup"
end
it "should set an auth_token cookie" do
rack_mock_session.cookie_jar["auth_token"].should == "id1"
end
it "should set an s_flag cookie" do
rack_mock_session.cookie_jar["s_flag"].should == "t"
end
Here you'll see subject. It just defines what the variable subject returns, and makes its blocks operate on it. In this case, the subject is the User record. Since subject is the user record, I can use the more concise form to check its attributes.
context "the authenticated user" do
subject { user }
its(:id) { should == "id1" }
its(:token) { should == "token" }
its(:secret) { should == "secret" }
end
end
You'll see here that I provide a new definition for access_token. When these examples run, the before block way up at the top (that sets up the "authorized token") will use this access_token rather than the one defined way up there. This lets us override the variables used to set up the context with variables specific to this particular context.
context "when there is an existing user" do
let(:access_token) { double("token", token: "newtoken", secret: "newsecret") }
before do
User.new("id1", "oldtoken", "oldsecret", "t").save
get '/login/twitter/success'
end
it "should set an auth_token cookie" do
rack_mock_session.cookie_jar["auth_token"].should == "id1"
end
it "should set an s_flag cookie" do
rack_mock_session.cookie_jar["s_flag"].should == "t"
end
it "should redirect to /t" do
last_response.should redirect_to "/t"
end
context "the authenticated user" do
subject { user }
its(:id) { should == "id1" }
its(:token) { should == "newtoken" }
its(:secret) { should == "newsecret" }
end
end
end
end
context "with an invalid token" do
before do
TwitterService.any_instance.stub(:authentication_request_token).and_raise("Unauthorized")
get '/login/twitter'
end
it "should redirect back to home page if error occurs" do
last_response.should redirect_to "http://example.org/"
end
it "should not set a session value" do
session[:request_token_twitter].should be_nil
end
end
end
end

ActionController::RoutingError in Rspec eventhough the controller and action exists

I have run into a problem with Rspec while writing tests for my Omniauth authorizations controller.
Heres my routes.rb
MyWebApp::Application.routes.draw do
get "static/index"
match "login" => 'user_sessions#new'
match 'logout' => 'user_sessions#destroy'
match "api" => "api#content", :via => :get
match "api/node_tree" => "api#node_tree", :via => :get
match "/auth/:provider/callback" => "oauth_authorizations#create"
match "/auth/failure" => "oauth_authorizations#failure"
match "/auth/:provider" => "oauth_authorizations#blank"
resources :users do
resources :apps do
resources :nodes
end
end
resources :user_sessions
end
oauth_authorization_controller_spec.rb
it "should create a new authorization entry for the user" do
expect {get :create }.to change(Authorization, :count).by(1)
end
oauth_authorization_controller.rb
class OauthAuthorizationsController < ApplicationController
def create
end
end
When i am running my spec, I get the following error
Failures:
1) OauthAuthorizationsController when a current user session already exists should create a new authorization entry for the user
Failure/Error: expect {get :create }.to change(Authorization, :count).by(1)
ActionController::RoutingError:
No route matches {:controller=>"oauth_authorizations", :action=>"create"}
Could any one please help me find out whats the reason behind this, because as its clear from the controller code, {:controller=>"oauth_authorizations", :action=>"create"} does exist.
Try to replace the get http verb with a post:
expect {post :create }.to change(Authorization, :count).by(1)
The problem was that the provider parameter specified in the route,
match "/auth/:provider/callback" => "oauth_authorizations#create"
was not passed from the test.
Passing it fixed the test.
get :create, :provider => omniauth_hash['provider']
So the test would be re written as.
it "should create a new authorization entry for the user" do
expect {get :create, provider => omniauth_hash['provider'] }.to change(Authorization, :count).by(1)
end
May be this would help some one.

Generating JSON for Sinatra

I'm having an issue with passing the generated JSON notation of my object to my Sinatra application. The problem I have is twofold:
I have 2 classes that are mapped to a database using the Sequel gem. When they generate JSON it is ok and properly implemented.
I have a custom class called registration that maps one of the classes with an additional field. The goal is to generate JSON out of this and pass that JSON to the application using cucumber (test purpose)
The application code responsible for handling the request has the following function defined:
post '/users' do
begin
hash = JSON.parse(self.request.body.read)
registration = Registration.new.from_json(#request.body.read)
registration.user.country = Database::Alaplaya.get_country_by_iso_code(registration.user.country.iso_code)
return 400 unless(registration.is_valid?)
id = Database::Alaplaya.create_user(registration.user)
# If the registration failed in our system, return a page 400.
return 400 if id < 1
end
problem 1: I cannot use the params hash. It exists but is just an empty hash. Why?
problem 2: I cannot deserialize the JSON generated by the class itself. Why?
The registration class looks like this:
require 'json'
class Registration
attr_accessor :user, :project_id
def to_json(*a)
{
'json_class' => self.class.name,
'data' => [#user.to_json(*a), #project_id]
}.to_json(*a)
end
def self.json_create(o)
new(*o['data'])
end
# Creates a new instance of the class using the information provided in the
# hash. If a field is missing in the hash, nil will be assigned to that field
# instead.
def initialize(params = {})
#user = params[:user]
#project_id = params[:project_id]
end
# Returns a string representing the entire Registration.
def inspect
"#{#user.inspect} - #{#user.country.inspect} - #{#project_id}"
end
# Returns a boolean valid representing whether the Registration instance is
# considered valid for the API or not. True if the instance is considered
# valid; otherwise false.
def is_valid?
return false if #user.nil? || #project_id.nil?
return false if !#user.is_a?(User) || !#project_id.is_a?(Fixnum)
return false if !#user.is_valid?
true
end
end
I had to implement the methods to generate the JSON output correctly. When I run this in console I get the following output generated:
irb(main):004:0> r = Registration.new(:user => u, :project_id => 1)
=> new_login - nil - 1
irb(main):005:0> r.to_json
=> "{\"json_class\":\"Registration\",\"data\":[\"{\\\"json_class\\\":\\\"User\\\
",\\\"login\\\":\\\"new_login\\\"}\",1]}"
Which looks like valid JSON to me. However when I POST this to the application server and try to parse this, JSON complains that at least 2 octets are needed and refuses to deserialize the object.
If you're using Sequel as your ORM, try something like this:
In your model:
class Registration < Sequel::Model
many_to_one :user
many_to_one :project
plugin :json_serializer
end
The server:
before do
#data = JSON.parse(request.body.read) rescue {}
end
post '/users' do
#registration = Registration.new #data
if #registration.valid?
#registration.save
#registration.to_json #return a JSON representation of the resource
else
status 422 #proper status code for invalid input
#registration.errors.to_json
end
end
I think you may be overcomplicating your registration process. If the HTTP action is POST /users then why not create a user? Seems like creating a registration is overly complex. Unless your user already exists, in which case POST /users would be incorrect. If what you're really intending to do is add a user to to a project, then you should PUT /projects/:project_id/users/:user_id and the action would look something like this:
class User < Sequel::Model
many_to_many :projects
end
class Project < Sequel::Model
many_to_many :users
end
#make sure your db schema has a table called users_projects or projects_users
put '/projects/:project_id/users/:user_id' do
#find the project
#project = Project.find params[:project_id]
raise Sinatra::NotFound unless #project
#find the user
#user = Project.find params[:project_id]
raise Sinatra::NotFound unless #user
#add user to project's users collection
#project.add_user #user
#send a new representation of the parent resource back to the client
#i like to include the child resources as well
#json might look something like this
#{ 'name' : 'a project name', 'users' : ['/users/:user_id', '/users/:another_user_id'] }
#project.to_json
end

Ruby JSON issue

I know the title is a bit vague, but I dont know what to put on there.
I'm developing an API with Sinatra for our backend in Ruby. The thing is that I need to be able to pass JSON to the service representing a User. The problem I'm facing is that when I run my tests it does not work, but doing it manually against the service it does work. I'm guessing there is an issue with the JSON format.
I've updated my User model to rely on the helpers from ActiveModel for the JSON serialization. I was running in too much problems with manual conversions. This is what the base User model looks like:
class User
include ActiveModel::Serializers::JSON
attr_accessor :login, :email, :birthday, :created_at, :updated_at, :password_sha, :password_salt
# Creates a new instance of the class using the information stored
# in the hash. If data is missing then nill will be assigned to the
# corresponding property.
def initialize(params = {})
return if params.nil?
self.login = params[:login] if params.key?("login")
self.email = params[:email] if params.key?("email")
self.birthday = Time.parse(params[:birthday]) rescue Time.now
if params.key?("password_salt") && params.key?("password_sha")
self.password_salt = params["password_salt"]
self.password_sha = params["password_sha"]
elsif params.key?("password")
self.set_password(params[:password])
end
self.created_at = Time.now
end
def attributes
{:login => self.login, :email => self.email, :birthday => self.birthday, :created_at => self.created_at, :updated_at => self.updated_at, :password_sha => self.password_sha, :password_salt => self.password_salt}
end
def attributes=(params = {})
self.login = params['login']
self.email = params['email']
self.birthday = params['birthday']
self.created_at = params['created_at']
self.updated_at = params['updated_at']
self.password_sha = params['password_sha']
self.password_salt = params['password_salt']
end
end
I'm using Cucumber, Rack::Test and Capybara to test my API implementation.
The code of the API application looks like this:
# This action will respond to POST request on the /users URI,
# and is responsible for creating a User in the various systems.
post '/users' do
begin
user = User.new.from_json(request.body.read)
201
rescue
400
end
end
In the above piece I expect the json representation in the request body. For some reason the params hash is empty here, don't know why
The test section that makes the actuall post looks like this:
When /^I send a POST request to "([^\"]*)" with the following:$/ do |path, body|
post path, User.new(body.hashes.first).to_json, "CONTENT_TYPE" => "application/json"
end
The example output JSON string generated by the User.rb file looks like this:
"{"user":{"birthday":"1985-02-14T00:00:00+01:00","created_at":"2012-03-23T12:54:11+01:00","email":"arne.de.herdt#gmail.com","login":"airslash","password_salt":"x9fOmBOt","password_sha":"2d3afc55aee8d97cc63b3d4c985040d35147a4a1d312e6450ebee05edcb8e037","updated_at":null}}"
The output is copied from the Rubymine IDE, but when I submit this to the application, I cannot parse it because:
The params hash is empty when using the tests
doing it manually gives me the error about needing at least 2 octets.

Resources