I've got this Rails 4 controller like this:
class UsersController < ApplicationController
before_action :set_user, only: [:edit, :update, :delete]
# ...
api :POST, '/users', "Creates a new user"
param :username, String, :required => true, :desc => "Desired username"
param :email, /[a-zA-Z0-9\-.]+\#[a-zA-Z0-9\-.]+\.[a-z]{2,}/, :required => true, :desc => "User's e-mail address"
param :password, String, :required => true, :desc => "SHA256(username:password), hex encoded"
param :screenname, String, :required => true, :desc => "Publicly visible name"
param :avatar, String, :required => true, :desc => "Uploaded avatar filename"
error :code => 422, :desc => "Unprocessable entity"
def create
#user = User.new(user_params)
respond_to do |format|
if #user.save
format.json { render :show, status: :created, location: #user }
else
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
# ...
private
# ...
def user_params
params.require(:user).permit(:username, :email, :password, :screenname)
end
end
and then I have a rspec test like this
describe "POST /users.json" do
let!(:daniel_attrs) { FactoryGirl.attributes_for(:user, username: "daniel", email: "daniel#bigbob.com", password: "999d53a384afd57ebc36986fe9455c6e941cd844467e28f975289845d6e984ee", screenname: 'Daniel', avatar: "default.png") }
before :each do |example|
post '/users.json', daniel_attrs
end
# ...
end
which fails like this
1) Users API POST /users.json creates the user with the right parameters
Failure/Error: post '/users.json', daniel_attrs
ActionController::ParameterMissing:
param is missing or the value is empty: user
# ./app/controllers/users_controller.rb:155:in `user_params'
# ./app/controllers/users_controller.rb:72:in `create'
# ./spec/requests/usersapi_spec.rb:85:in `block (3 levels) in <top (required)>'
What I'm not clear on is why would the controller even require a 'user' key in the request, since if I forge a request (via curl, postman, whatever) and I only include a json representation of my attributes, it creates the object correctly. If anyway I provide the key (as in :user => daniel_attrs) the test fails like this
1) Users API POST /users.json creates the user with the right parameters
Failure/Error: post '/users.json', :user => daniel_attrs
Apipie::ParamMissing:
Missing parameter username
# ./spec/requests/usersapi_spec.rb:85:in `block (3 levels) in <top (required)>'
and I know for a fact (because I used let! and because I printed it out) that daniel_attrs has all the keys and values at the right places.
I'm sure I'm missing something very obvious, I haven't been at rails testing for a veeery long time. So, what is it that I'm missing?
EDIT: generally, it would be massively helpful to inspect requests before they get performed. Is there any way to do this?
EDIT 2: Works when modifying the apipie definition like this
api :POST, '/users', "Creates a new user"
param :user, Hash, :required => true do
param :username, String, :required => true, :desc => "Desired username"
param :email, /[a-zA-Z0-9\-.]+\#[a-zA-Z0-9\-.]+\.[a-z]{2,}/, :required => true, :desc => "User's e-mail address"
param :password, String, :required => true, :desc => "SHA256(username:password), hex encoded"
param :screenname, String, :required => true, :desc => "Publicly visible name"
param :avatar, String, :required => true, :desc => "Uploaded avatar filename"
end
error :code => 422, :desc => "Unprocessable entity"
def create
#user = User.new(user_params)
respond_to do |format|
if #user.save
format.json { render :show, status: :created, location: #user }
else
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
This is because of user_params method of your controller.
I think you can try this :
def user_params
params.permit(:username, :email, :password, :screenname)
end
Edit : Sorry I missed a part of your post, so you know the problem is because of 'user' key.
If you want to keep the user key, you can try the following syntax ( I tried it and it works )
post :create, user: daniel_attrs
Hope this helps.
Related
I have the following rails_spec test:
require 'rails_helper'
RSpec.describe UsersController, type: :controller do
fixtures :users, :clients
include AuthHelper
before do
#request.headers["Content-Type"] = 'application/json'
#request.headers["Accept"] = 'application/json'
end
describe '#create' do
context 'for a user with a valid authorization token' do
context 'having admin privileges' do
before do
init_headers_for_admin
end
context 'submitted with a valid set of parameters' do
it 'should create a new user having the submitted parameters' do
post :create, params: {
:name => 'Roland Deschain',
:email => 'roland#foamfactorybrewing.com',
:role => 'user',
:username => 'roland'
}
expect(response).to have_http_status(:ok)
user = json
expect(user).to_not be_nil
expect(user.name).to eq('Roland Deschain')
expect(user.email).to eq('roland#foamfactorybrewing.com')
expect(user.role).to eq('user')
expect(user.username).to eq('roland')
expect(user.created_at).to eq(user.updated_at)
end
end
I also have the following controller definition:
def create
# get_required_fields.each { |field|
# unless params.has_key? field
# render :json => { :error => 'A ' + field.to_s + ' must be specified in order to create a new User'}, :status => :unprocessable_entity and return
# end
# }
username = params[:username]
unless User.find_by_username(params[:username]) == nil
render :json => { :error => I18n.t('error_username_conflict') }, :status => :conflict and return
end
unless User.find_by_email(params[:email]) == nil
render :json => { :error => I18n.t('error_email_conflict') }, :status => :conflict and return
end
end
However, when I run the test, the params variable is always an empty hash. If I uncomment the code that detects whether I have the required parameters, it always returns an UNPROCESSABLE_ENTITY, rather than the NO_CONTENT I would expect if a username, name, and email are present. I'm not quite sure why, but I can't seem to get it to pass actual data via the post method. I've been looking at this for a while, and trying to debug it, but I can't seem to get it to pass the parameters correctly.
I'm using Rails 5 and rspec_rails 3.8.1.
EDIT:
Something is really wrong here, because if I simplify create to be:
def create
unless params.has_key?('name') && params.has_key?('username') && params.has_key?('email')
render :json => {:error => I18n.t('error_must_specify_username_email_and_name')}, :status => :unprocessable_entity and return
end
end
And execute the same spec as before (obviously expecting it to return no_content, and not unprocessable_entity), I end up with the same thing happening - in debug mode, it looks like the params variable upon entering this method is:
{"controller"=>"users", "action"=>"create", "user"=>{}}
So, first off, it's setting the user parameter, which isn't exactly what I intended, and moreover, it's setting it to an empty hash. I'm not sure what's causing this. I feel like it's something I'm forgetting to do from Rails 5.0, because this pattern seems to work fine in Rails 4.0.
EDIT 2:
I modified the test to be:
it 'should create a new user record' do
post :create, params: { user: #user }, as: :json
expect(response).to have_http_status(:ok)
expect(json.name).to eq(#user.name)
expect(json.email).to eq(#user.email)
expect(json.username).to eq(#user.username)
expect(json.role).to eq(#user.role)
end
And modified the UsersController class to look like:
class UsersController < ApplicationController
include AuthHelper
before_action :validate_api_key_for_auth, only: :login
before_action :authenticate_as_admin, only: [:create]
def create
p user_params
unless user_params.has_key?('name') && user_params.has_key?('username') && user_params.has_key?('email')
render :json => {:error => I18n.t('error_must_specify_username_email_and_name')}, :status => :unprocessable_entity and return
end
end
def user_params
params[:user].permit([:username, :name, :email, :role]).to_h
end
end
It is printing out: {} for the users hash from user_params, but I don't understand why this is the case.
EDIT 3:
authenticate_as_admin is screwing up the parameters somehow. If I change the definition of authenticate_as_admin to true, it shows the correct hash and returns no_content as expected. The definition of the authenticate_as_admin function is below (defined in ApplicationController):
def authenticate_as_admin
authenticate_as :admin
end
def authenticate_as(role)
# First, get the headers for the request
authorization_header = get_authorization_header
unless authorization_header
render :json => {:error => I18n.t('error_must_be_logged_in')}, :status => :forbidden and return false
end
auth_token = authorization_header.split[1]
decoded_token = decode_authorization_header authorization_header
unless decoded_token
render :json => {:error => I18n.t('error_invalid_token')}, :status => :forbidden and return false
end
payload = decoded_token[0]
user_id = payload['user_id']
auth_token_expiration_date = payload['expiration_date']
#authenticated_user = User.find(user_id) rescue nil
unless #authenticated_user and #authenticated_user.auth_token == auth_token
render :json => {:error => I18n.t('error_must_be_logged_in')}, :status => :forbidden and return false
end
if auth_token_expiration_date < DateTime.now
render :json => {:error => I18n.t('error_auth_token_expired')}, :status => :forbidden and return false
end
unless #authenticated_user.role_at_least? role
render :json => {:error => I18n.t('error_insufficient_permission')}, :status => :forbidden and return false
end
true
end
I can't seem to see why this would muck up the params, though, especially considering this worked fine in Rails 4.2. Anyone see anything I'm missing?
EDIT 4:
I feel like I'm on the right track. It seems as though if the authenticate_as() function returns false, I get this behavior. If a before_action returns false, what should be the expected result of a controller action? I expected that the method called in the before_action would perform the appropriate render ... call, and the before_action would silently fail, never calling the function that would have been called. I'm still not quite sure why the function is failing, but I'm trying to understand the behavior that should happen. Perhaps authentication checking isn't a great use-case for before_actions in Rails 5?
Update Not valid for rails 5
Try this,
post :create, {
:name => 'Roland Deschain',
:email => 'roland#foamfactorybrewing.com',
:role => 'user',
:username => 'roland'
}
when you have the params: {} it gets sent as a nested hash, { params: {...} }
EDIT 1
Instead of passing params to post :create try stubbing out params call in controller, something like
allow(subject).to receive(:params).and_return(params)
and inside describe block,
let(:params) { ActionController::Parameters.new({ :name => '', :email => '' ... }) }
or just a regular hash if you don't need ActionController::Parameters
and call the :create without any parameters,
post :create
see if this helps, I am using this pattern in our specs in another project which is on route to rails 5.
I am attempting to get a user registration endpoint setup for my rails application so that I can access the app's functionality in an iOS rendition. I've gone ahead and namespaced my API, and so far have managed to get user authentication working using Devise and JWT's.
This is great, however, I also need to ability to register a user via the API. To be frank, I have no idea how to correctly implement this. Several Google searches either bring up outdated articles, use the deprecated token authenticatable, or have never been answered.
Below is the code that I believe pertains most to this question:
routes.rb (Namespaced section for API)
namespace :api do
namespace :v1 do
devise_for :users, controllers: { registrations: 'api/v1/registrations' }
resources :classrooms
resources :notifications
end
end
end
registrations_controller.rb (API contorller)
class Api::V1::RegistrationsController < Devise::RegistrationsController
respond_to :json
def create
if params[:email].nil?
render :status => 400,
:json => {:message => 'User request must contain the user email.'}
return
elsif params[:password].nil?
render :status => 400,
:json => {:message => 'User request must contain the user password.'}
return
end
if params[:email]
duplicate_user = User.find_by_email(params[:email])
unless duplicate_user.nil?
render :status => 409,
:json => {:message => 'Duplicate email. A user already exists with that email address.'}
return
end
end
#user = User.create(user_params)
if #user.save!
render :json => {:user => #user}
else
render :status => 400,
:json => {:message => #user.errors.full_messages}
end
end
private
# Never trust parameters from the scary internet, only allow the white list through.
def user_params
devise_parameter_sanitizer.permit(:sign_up, keys: [:attribute, :first_name, :last_name, :access_code])
end
end
End Point for registration
http://localhost:3000/api/v1/users
Sample Postman response
{
"message": [
"Email can't be blank",
"Password can't be blank",
"Access code is invalid [Beta]."
]
}
Any help would greatly be appreciated, as I am keen on learning more (and getting this to work!).
UPDATE 1
Here is what I get on the server after making a post request to generate a user...
Started POST "/api/v1/users" for 127.0.0.1 at 2017-02-22 09:22:11 -0800
Processing by Api::V1::RegistrationsController#create as */*
Parameters: {"user"=>{"email"=>"user#sampleapi.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]", "access_code"=>"uiux"}}
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."email" IS NULL LIMIT $1 [["LIMIT", 1]]
Completed 400 Bad Request in 2ms (Views: 0.2ms | ActiveRecord: 0.4ms)
Updated Registrations_controller
class Api::V1::RegistrationsController < Devise::RegistrationsController
before_action :configure_sign_up_params, only: [:create]
respond_to :json
def create
#user = build_resource(sign_up_params)
if #user.persisted?
# We know that the user has been persisted to the database, so now we can create our empty profile
if resource.active_for_authentication?
sign_up(:user, #user)
render :json => {:user => #user}
else
expire_data_after_sign_in!
render :json => {:message => 'signed_up_but_#{#user.inactive_message}'}
end
else
if params[:user][:email].nil?
render :status => 400,
:json => {:message => 'User request must contain the user email.'}
return
elsif params[:user][:password].nil?
render :status => 400,
:json => {:message => 'User request must contain the user password.'}
return
end
if params[:user][:email]
duplicate_user = User.find_by_email(params[:email])
unless duplicate_user.nil?
render :status => 409,
:json => {:message => 'Duplicate email. A user already exists with that email address.'}
return
end
end
render :status => 400,
:json => {:message => resource.errors.full_messages}
end
end
protected
# If you have extra params to permit, append them to the sanitizer.
def configure_sign_up_params
devise_parameter_sanitizer.permit(:sign_up, keys: [:attribute, :first_name, :last_name, :access_code])
end
end
I'm pretty sure my main issue at this point is the format of my params, so any push in the right direction for this would be great. I did find this post but am finding it a little difficult to follow in terms of what got their API to work...
Here is 2 solution, choose one you like.
Override devise_parameter_sanitizer:
class ApplicationController < ActionController::Base
protected
def devise_parameter_sanitizer
if resource_class == User
User::ParameterSanitizer.new(User, :user, params)
else
super # Use the default one
end
end
end
Override sign_up_params:
def sign_up_params
params.require(:user).permit(:email, :password, :password_confirmation)
end
Why?
If you go deeping to Devise ParameterSanitizer, the resource_name will be :api_v1_user, not just :user because of your routes:
namespace :api do
namespace :v1 do
devise_for :users, controllers: { registrations: 'api/v1/registrations' }
end
end
Error resource_name will cause sign_up_params always return empty hash {}
Why don't you try something like this:
user = User.create(sign_up_params)
if user.save
render status: 200, json: #controller_blablabla.to_json
else
render :status => 400,
:json => {:message => #user.errors.full_messages}
end
or even better. You might use something like tiddle gem to make session more secure:
respond_to :json
def create
user = User.create(sign_up_params)
if user.save
token = Tiddle.create_and_return_token(user, request)
render json: user.as_json(authentication_token: token, email:
user.email), status: :created
return
else
warden.custom_failure!
render json: user.errors, status: :unprocessable_entity
end
end
You might use httpie --form to make the request:
http --form POST :3000/users/sign_up Accept:'application/vnd.sign_up.v1+json' user[email]='he#llo.com' user[username]='hello' user[password]='123456789' user[password_confirmation]='123456789'
do not forget:
def sign_up_params
params.require(:user).permit(:username, :email, :password, :password_confirmation)
end
I don't know what i'm missing, let me know if i'm wrong or something is wrong and i did't realize!
Regards!
Why not use the simple_token_authentication gem ?
Extremely simple to setup:
# Gemfile
gem "simple_token_authentication"
bundle install
rails g migration AddTokenToUsers "authentication_token:string{30}:uniq"
rails db:migrate
# app/models/user.rb
class User < ApplicationRecord
acts_as_token_authenticatable
# [...]
end
In your routes:
# config/routes.rb
Rails.application.routes.draw do
# [...]
namespace :api, defaults: { format: :json } do
namespace :v1 do
resources :classrooms
resources :notifications
end
end
end
In your controllers:
# app/controllers/api/v1/classrooms_controller.rb
class Api::V1::ClassroomsController < Api::V1::BaseController
acts_as_token_authentication_handler_for User
# [...]
end
Example call using the RestClient gem:
url = "http://localhost:3000/api/v1/classrooms/"
params = {user_email: 'john#doe.com', user_token: '5yx-APbH2cmb11p69UiV'}
request = RestClient.get url, :params => params
For existing users who don't have a token:
user = User.find_by_email("john#doe.com")
user.save
user.reload.authentication_token
double opt-in confirmation email still goes through, any idea what's wrong? Was having problems with the gibbon gem, so opted for mailchimp gem instead.
Gemfile
gem "mailchimp-api", "~> 2.0.4"
application_controller.rb
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
before_action :setup_mcapi
def setup_mcapi
#mc = Mailchimp::API.new('mailchimp api key goes here')
#list_id = "list id goes here"
end
end
welcome_controller.rb
class WelcomeController < ApplicationController
def index
end
def subscribe
email = params[:email][:address]
if !email.blank?
begin
#mc.lists.subscribe(#list_id, {'email' => email}, 'double_optin' => false)
end
respond_to do |format|
format.json{render :json => {:message => "Success!"}}
end
rescue Mailchimp::ListAlreadySubscribedError
respond_to do |format|
format.json{render :json => {:message => "#{email} is already subscribed to the list"}}
end
rescue Mailchimp::ListDoesNotExistError
respond_to do |format|
format.json{render :json => {:message => "The list could not be found."}}
end
rescue Mailchimp::Error => ex
if ex.message
respond_to do |format|
format.json{render :json => {:message => "There is an error. Please enter valid email id."}}
end
else
respond_to do |format|
format.json{render :json => {:message => "An unknown error occurred."}}
end
end
end
else
respond_to do |format|
format.json{render :json => {:message => "Email Address Cannot be blank. Please enter valid email id."}}
end
end
end
end
index.html.erb
<h3>Add a New Member</h3>
<p>Please enter your email address to subscribe to our newsletter.</p>
<%= form_tag('/welcome/subscribe', method: "post", id: "welcome", remote: "true") do -%>
<%= email_field(:email, :address, {id: "email", placeholder: "email address"}) %>
<%= submit_tag("Subscribe") %>
<% end %>
<div id="response">Response Will be displayed here</div>
routes.rb
Rails.application.routes.draw do
root 'welcome#index'
post 'welcome/subscribe' => 'welcome#subscribe'
end
From mailchimp-api document
subscribe(id, email, merge_vars = nil, email_type = 'html', double_optin = true, update_existing = false, replace_interests = true, send_welcome = false) ⇒ Hash
The double_optin is the parameter which will be passed directly not like what you did.
So, it will be:
#mc.lists.subscribe(#list_id, email, nil, 'html', false)
The answer above kept giving me errors, this worked for me:
#mc.lists.subscribe(#list_id, { "email" => email }, merge_vars = nil, email_type = 'html', double_optin = false)
I have the "updates the requested user" part of my test that fails and I cannot understand why.
describe "PUT/PATCH #update_profile" do
context "with valid params" do
it "updates the requested user" do
user = create(:john_doe)
# Assuming there are no other users in the database, this
# specifies that the User created on the previous line
# receives the :update_attributes message with whatever params are
# submitted in the request.
User.any_instance.should_receive(:update_profile).with({identity_attributes:{"last_name" => "Bidon", "first_name" => "Bidon", "dob" => "1970-07-15", "id"=>user.identity.id}})
put :update_profile, {:id => user.to_param, :user => {identity_attributes:{"last_name" => "Bidon", "first_name" => "Bidon", "dob" => "1970-07-15", "id"=>user.identity.id}}}
end
it "assigns the requested user as #user" do
user = create(:john_doe)
put :update_profile, {:id => user.to_param, :user => {identity_attributes:{"last_name" => "Bidon", "first_name" => "Bidon", "dob" => "1970-07-15", "id"=>user.identity.id}} }
expect(assigns(:user)).to eq(user)
end
it "redirects to the user" do
user = create(:john_doe)
put :update_profile, {:id => user.to_param, :user => {identity_attributes:{"last_name" => "Bidon", "first_name" => "Bidon", "dob" => "1970-07-15", "id"=>user.identity.id}}}
expect(response).to redirect_to foundry_users_url
end
end
the 2 other parts (assigns and redirect) pass fine, and all works as expected when testing in browser.
The error message is "RSpec::Mocks::MockExpectationError: Exactly one instance should have received the following message(s) but didn't: update_profile"
EDIT : I add here the users controller (I keep here only the relevant parts of code: create action update action (for reference) and update_profile action (which causes the spec fail). Remember that only this spec is failing, all other works as expected, the problem is just in the way I wrote the test.
User has_one :identity and accepts_nested_attributes_for :identity
class Foundry::UsersController < ApplicationController
before_action :set_user, only: [:show, :edit, :update, :edit_profile, :update_profile, :destroy]
def create
#user = User.new(user_params)
respond_to do |format|
if #user.save
format.html { redirect_to foundry_users_url, flash: {success: "User was successfully created."} }
format.json { render action: 'show', status: :created, location: #user }
else
format.html { render action: 'new' }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if #user.update(user_params_for_update)
format.html { redirect_to foundry_users_url, notice: 'Credentials were successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
def update_profile
respond_to do |format|
if #user.update(user_params_for_update_profile)
format.html { redirect_to foundry_users_url, notice: 'Profile was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit_profile' }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
def user_params
# used only on creation
params.require(:user).permit(:email, identity_attributes: [:last_name, :first_name])
end
def user_params_for_update
# used only on 'regular' update action -- updates only credentials that are user's attributes
params.require(:user).permit(:email, :password, :password_confirmation)
end
def user_params_for_update_profile
# used only on update_profile action (later should have identity_attributes, addresses_attributes, and some others...)
params.require(:user).permit(identity_attributes: [:last_name, :first_name, :email_rescue, :dob, :bio, :gender, :id])
end
I suppose I'm doing something wromg somewhere but I cannot see where and why...
Thanks for your help
I have it to work ! Thanks #DNNX who puts me on the right direction the problem as expected was in the way I wrote the test, user.any_instance should receive :update_profile instead of update. I put here the passing spec for information..
describe "PUT/PATCH #update_profile" do
context "with valid params" do
it "updates the requested user" do
user = create(:john_doe)
User.any_instance.should_receive(:update_profile).with({"identity_attributes"=>{"last_name" => "BIDON", "first_name" => "Bidon", "dob" => "1970-07-15"}})
put :update_profile, {:id => user.to_param, :user => {:identity_attributes =>{last_name: 'BIDON', first_name: 'Bidon', dob: "1970-07-15"}}}
end
it "assigns the user as #user" do
user = create(:john_doe)
# Trigger the behavior that occurs when valid params are submitted
User.any_instance.stub(:update_profile).and_return(true)
put :update_profile, {:id => user.to_param, :user => { identity_attributes:{"last_name" => "Bidon", "first_name" => "Bidon", "dob" => "1970-07-15"}}}
expect(assigns(:user)).to eq(user)
end
it "redirects to users list" do
user = create(:john_doe)
# Trigger the behavior that occurs when valid params are submitted
User.any_instance.stub(:update_profile).and_return(true)
put :update_profile, {:id => user.to_param, :user => {identity_attributes:{"last_name" => "Bidon", "first_name" => "Bidon", "dob" => "1970-07-15"}}}
expect(response).to redirect_to foundry_users_url
end
end
context "with invalid params" do
it "assigns the user as #user" do
user = create(:john_doe)
# Trigger the behavior that occurs when invalid params are submitted
User.any_instance.stub(:update_profile).and_return(false)
put :update_profile, {:id => user.to_param, :user => { identity_attributes:{"last_name" => "Bidon", "first_name" => "Bidon", "dob" => "1970-07-15", "id" => user.identity.id}}}
expect(assigns(:user)).to eq(user)
end
it "re-renders the 'edit_profile' template" do
user = create(:john_doe)
# Trigger the behavior that occurs when invalid params are submitted
User.any_instance.stub(:update_profile).and_return(false)
put :update_profile, {:id => user.to_param, :user => {identity_attributes:{"last_name" => "Bidon", "first_name" => "Bidon", "dob" => "1970-07-15", "id" => user.identity.id}}}
expect(response).to render_template :edit_profile
end
end
end
the rest of code posted in my question still the same.. and now, all the tests are green
EDIT as said in to DNNX in comments I forgot to mention the essential modif to the controller itself, I put it here :
def update_profile
respond_to do |format|
if #user.update_profile(user_params_for_update_profile) // changed to call update_profile instead of update !
// rest of code still the same
Cheers
Your controller doesn't call update_profile on any instance of User. It calls User#update. Try this:
User.
any_instance.
should_receive(:update).
with(identity_attributes: {
"last_name" => "Bidon",
"first_name" => "Bidon",
"dob" => "1970-07-15",
"id" => user.identity.id})
Or this if the code above doesn't work:
User.any_instance.should_receive(:update)
I'm building an application that handles different types of transactions for different services such as subscriptions, gift subscriptions and purchases.
I have an issue with the gift transactions and activemerchant. Ill give you a brief overview of how it works.
The user creates a gift subscription and fills out the data for it, it is stored in the db and then shown back to the user for review in a custom "show_view", the user then proceeds to enter credit card information in a separate form and when he submits the data, a method from the controller is called to handle the transaction and here is where Im having issues.
This is the gift_subscription.rb model
def gift_purchase
response = GATEWAY.purchase(price, credit_card, gift_purchase_options)
GiftTransaction.create!(:action => "gift_purchase", :amount => price, :response => response)
response.success?
end
private
def gift_purchase_options
{
:ip => ip_address,
:billing_address => {
:name => name + last_name,
:address1 => address1,
:city => city,
:state => state,
:country => "Mexico",
:zip => zip
}
}
end
def validate_card
unless credit_card.valid?
credit_card.errors.full_messages.each do |message|
errors[:base] << message
end
end
end
def credit_card
#credit_card = ActiveMerchant::Billing::CreditCard.new(
:brand => card_type,
:number => card_number,
:verification_value => card_verification,
:month => card_expires_on.month,
:year => card_expires_on.year,
:first_name => name,
:last_name => last_name
)
And here is the gift_subscription_controller.rb
def review
#gift_subscription = GiftSubscription.find(params[:id])
end
def edit_review
#gift_subscription = GiftSubscription.find(params[:id])
end
def update_review
#gift_subscription = GiftSubscription.find(params[:id])
respond_to do |format|
if #gift_subscription.update_attributes(params[:gift_subscription])
format.html { redirect_to "gift_subscriptions/review/#{#gift_subscription.id}", :notice => 'Gift subscription was successfully updated.' }
format.json { head :no_content }
else
format.html { render :action => "edit_review" }
format.json { render :json => #gift_subscription.errors, :status => :unprocessable_entity }
end
end
end
def do_gift_transaction
#gift_subscription = GiftSubscription.find(params[:id])
if #gift_subscription.gift_purchase
redirect_to '/thank_you'
else
redirect_to "/gift_subscriptions/#{#gift_subscription.id}/failed_transaction"
end
end
def failed_transaction
#gift_subscription = GiftSubscription.find(params[:id])
#gift_transactions = #gift_subscription.gift_transactions
end
def transaction_details
#gift_subscription = GiftSubscription.find(params[:id])
end
To make things a little more clear, from the controller create method, it redirects users to the review action where there's an edit_review in case they want to change something, then they go to transaction_details where they enter creditcard info and finally the method do_gift_transaction is called to actually do the transaction.
The error I get is the following
NoMethodError in GiftSubscriptionsController#do_gift_transaction
undefined method `month' for nil:NilClass
Rails.root: /home/peanut/RubymineProjects/GiftBox
Application Trace | Framework Trace | Full Trace
app/models/gift_subscription.rb:44:in `credit_card'
app/models/gift_subscription.rb:12:in `gift_purchase'
app/controllers/gift_subscriptions_controller.rb:113:in `do_gift_transaction'
I've been looking around and I can't seem to find why it doesnt recognize the month... For other subscriptions I have basically the same model (a few diferences) but it works perfectly. Any help here would be much appreciated.
GiftSubscription model attributes
attr_accessible :response, :name, :last_name, :address1, :address2,:city,
:state, :zip, :card_type, :ip_address, :price,
:duration, :created_at, :card_expires_on, :card_number,
:card_verification, :message
has_one :gift_transactions, :class_name => "GiftTransaction"
attr_accessor :card_number, :card_verification
validate :validate_card, :on => :transaction_details
So after a few hours of banging my head it turned out to be quite simple... In the view to enter credit card data, the fields that were supposed to be saved to the db were not being saved because I only had a link_to button to continue and ergo when the credit_card method was called, it was empty.
Thanks to lander16 for pointing this out.