I'm used to writing view specs which check at least for something like:
expect(view).to receive(:title).with('Required page title here')
(title is a helper method I wrote to set the page title.) Now I'm trying to write specs for my Devise views which look something like:
- title 'Lost Password'
.row
.col-lg-6
= form_for resource, as: resource_name, url: password_path(resource_name) do |f|
= render 'layouts/errors', object: resource
.form-group
= f.label :email
= f.text_field :email, class: 'form-control', autofocus: true
= f.submit 'Send me the instructions', class: 'btn btn-primary'
%hr
= render 'devise/shared/links'
.col-lg-6
= render 'devise/shared/advantages'
resource and resource_name are defined by Devise.
If I run the following spec on the view:
require 'rails_helper'
describe 'devise/passwords/new', type: :view do
it 'sets the page title' do
expect(view).to receive(:title).with('Lost Password')
render
end
end
It says: undefined local variable or method 'resource'. I tried:
allow(view).to receive(:resource).and_return(User.new)
but then I get [snip, long class definition] does not implement: resource.
How do I make this work? I don't want to use Capybara for something as trivial as this.
You can provide these helpers yourself, by writing a simple implementation in your test helpers:
def resource_name
:user
end
def resource
#resource ||= User.new
end
def devise_mapping
#devise_mapping ||= Devise.mappings[:user]
end
Related
I'm trying to make a form object work for new User and edit User actions. The form object creates or updates a User through it's save method, but the form object itself is never persisted so Rails always tries to make a POST even though I'm specifying different routes in the simple_form_for url.
Is there any way to make it work for both actions?
UsersController.rb:
class Admin::UsersController < AdminController
def new
#user_form = UserForm.new(account_id: current_account.id)
end
def create
#user_form = UserForm.new(user_form_params)
if #user = #user_form.save
flash[:success] = "User created"
redirect_to admin_user_path(#user)
else
render "new"
end
end
def edit
#user_form = UserForm.new(existing_user: #user, account_id: current_account.id)
end
def update
if #user.update(user_form_params)
flash[:success] = "User saved"
redirect_to admin_user_path(#user)
else
render "edit"
end
end
end
UserForm.rb
class UserForm
include ActiveModel::Model
include ActiveModel::Validations::Callbacks
attr_accessor :fname, :lname, :email
def initialize(params = {})
super(params)
#account = Account.find(account_id)
#user = existing_user || user
end
def user
#user ||= User.new do |user|
user.fname = fname
user.lname = lname
user.email = email
end
end
def save
#user.save
#user
end
end
_form.html.erb
<%= simple_form_for #user_form, url: (#user.present? ? admin_user_path(#user) : admin_users_path) do |f| %>
<%= f.input :fname %>
<%= f.input :lname %>
<%= f.input :email %>
<%= f.submit %>
end
The new/create flow works fine, but editing an existing User returns
No route matches [POST] "/admin/users/69"
class UserForm
# ...
def to_model
#user
end
end
<%= simple_form_for #user_form, url: [:admin, #user_form] do |f| %>
<%= f.input :fname %>
<%= f.input :lname %>
<%= f.input :email %>
<%= f.submit %>
end
When you pass a record to form_for (which SimpleForm wraps), form_with or link_to the polymorphic routing helpers call to_model.model_name.route_key or singular_route_key depending on if the model is persisted?. Passing [:admin, #user_form] will cause the polymorphic route helpers to use admin_users_path instead of just users_path.
On normal models to_model just returns self.
https://api.rubyonrails.org/v6.1.4/classes/ActionDispatch/Routing/PolymorphicRoutes.html
how can I write a test mail_form gem?
Вut the test gives an error.
How to associate a OrderForm and MailForm?
I have :
model:
class OrderForm < MailForm::Base
attributes :name
attributes :email
attributes :phone_number
attributes :order_name
attributes :address
attributes :file_1, attachment: true
def mail_attachments
[:file_1]
end
controller, where:
class OrdersController < ApplicationController
def create
#order_form = OrderForm.new(params[:order_form])
#order_form.deliver
redirect_to root_path, notice: 'Заявка отправлена'
end
end
end view(form of sending a letter):
orders/new.html.haml_spec.rb
=form_for #order_form, url: orders_path, method: :post do |f|
=f.text_field :name, placeholder: 'ФИО', class: 'gui-input'
=f.text_field :email, placeholder: 'Email', class: 'gui-input'
=f.text_field :phone_number, placeholder: 'Номер телефона',
end I write test:
require 'rails_helper'
RSpec.describe "orders/new", type: :view do
it do
assign(:order, build(:order))
render
expect(rendered).to have_field :name
end
end
check the output of rake routes, url orders/new will execute the orders#new action and render the view orders/new.html.erb
GET /orders/new orders#new return an HTML form for creating a new order
new.html.erb will use the variables from your orders#new action. You are missing those variables
class OrdersController < ApplicationController
def new
#order_form = OrderForm.new
end
def create
#order_form = OrderForm.new(params[:order_form])
#order_form.deliver
redirect_to root_path, notice: 'Заявка отправлена'
end
end
I have Devise 4.2.1 and Rails 5.0.1. I'm trying to use Devise sign_in in a controller other than registrations. There were a bunch of methods that weren't defined, so I had to add them as helpers:
module ApplicationHelper
def resource_name
:user
end
def resource
#resource ||= User.new
end
def devise_mapping
#devise_mapping ||= Devise.mappings[:user]
end
def build_resource(hash=nil)
self.resource = resource_class.new_with_session(hash || {}, session)
# ^the source of the error^
end
def resource_class
devise_mapping.to
end
end
But now I'm getting the following error:
NoMethodError in Static#show
undefined method 'resource=' for (class)
I don't have "resource=" written anywhere in my code, certainly not on the line the error points to (marked above). Where is my server getting that error from?
Also, I think I'm just using the standard sign_in code:
<%
build_resource({})
set_minimum_password_length
yield resource if block_given?
respond_with self.resource
%>
<% form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %>
...
I believe that your question has 2 answers: Why this error is happening, and how to have a sign_in partial to use on all your controllers:
Creating the sign_in partial
First of all, the sign_in method belongs to sessions, not registrations be careful to not replace the wrong controller :)
And you just added a few more code than needed for what you want. To create a partial sign_in to use anywhere, just use the default Devise form in it:
<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
<%= f.label :email %><br />
<%= f.email_field :email, autofocus: true %>
<%= f.label :password %><br />
<%= f.password_field :password, autocomplete: "off" %>
<% if devise_mapping.rememberable? -%>
<%= f.check_box :remember_me %>
<%= f.label :remember_me %>
<% end -%>
<%= f.submit "Log in" %>
<% end %>
That part with build_resource({}) isn't needed, as it'll be handled by Devise itself when you submit the form. And since build_resource isn't needed, you just need the following code on your ApplicationHelper:
module ApplicationHelper
def resource_name
:user
end
def resource
#resource ||= User.new
end
def devise_mapping
#devise_mapping ||= Devise.mappings[:user]
end
end
This should be enough to have a sign_in form in any controller view. If you need more control over the login flow, you can check the Configuring Controller section on Devise Readme for more information on how to create a customizable controller.
Why your code throws a NoMethodError error
Your code throws an NoMethodError because you haven't defined a setter for the #resource instance variable. It should be either:
def resource=(value)
#resource = value
end
or with attr_writer :resource
I recommend this question for more info on the topic:
Why use Ruby's attr_accessor, attr_reader and attr_writer?
how can I update my user without the required password, password confirmation and current password?
I'm trying to update the user outside devise controller, my form is working with this helper:
module ContentHelper
def resource_name
:user
end
def resource
#resource ||= User.new
end
def devise_mapping
#devise_mapping ||= Devise.mappings[:user]
end
end
and my form for editing:
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put, multipart: true }) do |f| %>
<%= devise_error_messages! %>
<%= f.file_field :personal_file, placeholder: "Upload file" %>
<%= f.submit %>
<% end %>
With this its appearing that I can't have my password blank and redirecting to user edit page:
Password is too short (minimum is 8 characters)
Password can't be blank
Current password can't be blank
You can just write up your own controller that updates User as you wish. Something like
class UserController < ApplicationController
before_filter :authenticate_user!
def update
current_user.update(user_params)
end
private
def user_params
params.require(:user).permit(:personal_file)
end
end
would do the trick. You don't need to think of the current_user as some magic devise thing but instead use it as any other model you have.
If you anyway wish to use Devise controller to do this, you should see this answer.
How do you tweak the arguments that get passed in rspec's (v 2.6.1) post :create? Pointing me to rspec documentation that explains this command and how to use it would be fine with me. I've gone looking but can't find anything that answers my question.
I'm having difficulty writing rspec to behave properly for my Customer :create action that receives parameters, both for the Customer, as well as a different User object. More precisely, I'm unable to get the values in the hash I pass in the rspec post :create to behave like the input that comes from my form.
Please note that my Customer and User objects are separate ActiveRecords, and each Customer :belongs_to a User.
Here's my RSpec that's not doing what I want it to:
describe CustomersController do
render_views
describe "for signed-in admins" do
before(:each) do
#customer_attr = Factory.attributes_for(:customer)
#admin = Factory(:admin)
test_sign_in(#admin.user)
end
describe "POST 'create'" do
describe "success" do
it "should create a customer" do
lambda do
post :create, :customer => #customer_attr # The problem is here
end.should change(Customer, :count).by(1)
end
end
end
end
end
Here's the failure:
1) CustomersController for signed-in admins POST 'create' success should create a customer
Failure/Error: lambda do
count should have been changed by 1, but was changed by 0
# ./spec/controllers/customers_controller_spec.rb:323:in `block (5 levels) in <top (required)>'
The problem (as you'll see below) is obviously that post :create, :customer => #customer_attr doesn't include an argument that will end up in params[:user][:email], so the #user.save is failing because params[:user] is empty, but I've tried all kinds of ways to recompose the hash and pass it in that line but can't get it to work.
Here's the relevant controller code:
def create
#user = User.new(params[:user])
#user.user_type = 'customer'
#customer = Customer.new(params[:customer])
if #user.save
#user.customer = #customer
if #customer.save
# create stuff
redirect_to customer_path, :flash => { :success => "Customer created." }
else
# failure stuff
end
else
# more failure stuff
end
end
Here's the relevant form code (which behaves how I want it to):
<%= form_for(#customer) do |f| %>
<%= fields_for :user do |user_form| %>
<%= user_form.label :email %>
<%= user_form.text_field :email %>
<% end %>
<%= f.label :first_name %>
<%= f.text_field :first_name %>
<%= f.label :last_name %>
<%= f.text_field :last_name %>
<%= f.submit "Create Customer Account" %>
<% end %>
Lastly, here's my factories code:
factory :user do |f|
f.sequence :email do |n|
"person#{n}#example.com"
end
f.password "thepassword"
f.password_confirmation "thepassword"
end
factory :customer do |f|
f.first_name "First"
f.last_name "Last"
f.user
after_build { |customer| customer.user.user_type = 'customer' }
end
The solution I found was to compose my hash to make it look exactly how I wanted, then pass it directly, without the :customer => in front of it.
before(:each) do
#customer = Factory(:customer)
#customer_attr = Factory.attributes_for(:customer)
#user_attr = {:user => Factory.attributes_for(:user)}
#customer_and_user_attr = {:customer => #customer_attr }.merge(#user_attr)
#admin = Factory(:admin)
test_sign_in(#admin.user)
end
Then in my controller I access items as e.g. params[:customer][:first_name] or params[:user][:email]
And the rspec looks like:
post :create, #customer_and_user_attr
So if I understand your question correctly, you just need to pass in another key to post, namely :user => #admin.user. Did I misunderstand you?