Rails test not working with model methods in views - ruby-on-rails

When testing with rails, calling model methods within a view doesn't work. Is there a reason for this? The functions all work when accessing the page within the browser with no issue.
The view line causing problems:
<%= f.select :user_id, options_for_select(#project.assignment_options(current_user)),
{ include_blank: "Teammate to assign" }, class: "uk-select uk-margin-small-right" %>
The Test:
def current_user
#user
end
setup do
#user = users(:one)
sign_in #user
#project = projects(:one)
#project.owner_id = #user.id
#assignment = assignments(:one)
end
test "should get new" do
get new_project_assignment_url(#project.slug)
assert_response :success
end
The Error:
E
Error:
AssignmentsControllerTest#test_should_get_new:
ActionView::Template::Error: Couldn't find User without an ID
app/models/project.rb:33:in `owner'
app/models/project.rb:63:in `assignment_options'
app/views/assignments/_form.html.erb:6
app/views/assignments/_form.html.erb:2
app/views/assignments/new.html.erb:2
test/controllers/assignments_controller_test.rb:18:in `block in <class:AssignmentsControllerTest>'
When I do a "puts current_user.id" from within the view and test they match, so there is definitely a user id available on the page, but I am still hitting this issue. I have tried making tests for different pages that have different model methods, and they all fail with the same error.

The issue was that I wasn't saving the updated project. I needed to use #project.update. There was a strange behavior that when I updated the owner_id, the slug also updated for no apparent reason, but I worked around it by just creating a new project instead of using a fixture.

Related

Failing Rails test based on lack of set session variable - how to set in Rails 5.2?

I have the following very simple test which has always passed.
test "should get index" do
sign_in #user
get companies_url
assert_response :success
end
However, I'm now receiving the following error.
Error:
CompaniesControllerTest#test_should_get_index:
ActionView::Template::Error: No route matches {:action=>"show", :controller=>"companies", :id=>nil}, missing required keys: [:id]
app/views/layouts/application.html.erb:53:in `_app_views_layouts_application_html_erb__2582383126246718868_70101260952060'
test/controllers/companies_controller_test.rb:16:in `block in <class:CompaniesControllerTest>'
What I've changed in my app is that I've built a sidebar (which I load via application.html.erb so it's loaded on all views (new, show, edit), which lets a user switch between various companies they "own" - which changes a session variable which we use to alter the content of the sidebar.
If we dig into the line that seems to be failing app/views/layouts/application.html.erb:53 this is how it looks:
<div class="list-group-item">
<%= link_to 'Company', company_path(session[:current_company]) unless current_user.companies.empty? %>
</div>
If I remove this link_to line then the tests pass.
My guess is that the show view is trying to load, including the sidebar which doesn't have a session[:current_company] set so the view crashes. However, in Rails 5.2 you cannot set/test session variables as far as I understand, so I'm wondering what the best way for me to set the testing up to make this pass? I do set a value for this session within my application controller a user signs in though:
def after_sign_in_path_for(resource_or_scope)
# Set a default current company scope for a use after signing in
session[:current_company] = current_user.companies.first.id unless current_user.companies.empty?
companies_path
end
Perhaps in the link_to from from within the sidebar I could add a default value to make sure we're always sending in a company, regardless of whether it's the session[:current_company] or not?
Altering the line to skip creating the link if there is no session variable present
<%= link_to 'Company', company_path(session[:current_company]) unless current_user.companies.empty? || !session[:current_company] %>
Seemed to do the trick and make all the tests pass. Would love some feedback on whether this is a good solution or not though! :)

First argument in form cannot contain nil or be empty while it should not be nil or empty

I'm having this issue where the 'new' and sometimes 'edit' actions of my controllers will throw:
First argument in form cannot contain nil or be empty
While I'm 100% certain that the first argument is not nil nor empty. To hone down on this I'll only ask about the 'new' action since the resource is simply set to Resource.new
permissions/new.haml
= form_for #permission do |f|
= f.label :name, class: 'form-control-label'
= f.text_field :name, class: 'form-control'
permissions_controller.rb
class PermissionsController < ApplicationController
before_action :set_permission, only: [:show, :edit, :update, :destroy]
def new
#permission = Permission.new
end
def show
#modules = Modules.all
end
def index
// handling content with api calls instead
end
...
end
I have had this issue for a long time but I never figured out to successfully reproduce it in my development environment, until now.
I can reproduce this issue by going to the 'show' action of the same controller, then going back to the 'index' action and then going to the 'new' action. At this point the 'new' page does not work anymore until I edit the controller file or restart the server.
After changing the controller (adding a space at the end) or restarting the server it suddenly works again.
This is not the only resource this happens to. Often other resources like User have the same issue. I've had multiple projects that had the same issue that somehow disappeared so I never bothered to investigate more on this.
Ruby version: 2.3.5p376
Rails version: 5.1.4
edit:
I have noticed that I can't reproduce it anymore if I comment out Modules.all.
modules is not a activerecord class, it is a helper class for me to read out the different files in my directories, might this reading out give problems?
modules.rb
class Modules
def self.all
controllers = Dir.glob("#{Rails.root}/app/controllers/**/*.rb").
map{ |e|
if e.include?('/api/')
e.match(/api\/[^\/]*\/[^\/]*.rb/)[0].gsub('.rb', '').gsub('/', '::_').camelcase
else
e.match(/[^\/]*.rb/)[0].gsub('.rb', '').camelcase
end
}
modules = {}
controllers.each do |e|
c = Object.const_get e
modules[e.gsub(':','').gsub('Controller', '').underscore] = get_actions_for c if
!no_permission_needed_for e and c.action_methods.size > 0
end
return modules
end
def self.get_actions_for(controller)
actions = controller.action_methods
grouped_actions.each do |key, value|
actions.delete(key)
end
return actions
end
def self.grouped_actions
{"new" => "create", "edit" => "update"}
end
end
edit:
What I have noticed is that I'm trying to call actions.delete(key) where actions is a Set of strings where key is a String
When I do actions = controller.action_methods.to_a and force the set to an array my issue seems to go away. At least I can't reproduce it anymore. Now I'm wondering if there is something fundamentally going wrong in ruby where deleting something from a Set does weird stuff under the hood.
One way that it would work but is not recommended would be to initialize a Permission instance inside the form:
<%= form_for Permission.new do |f| %>
Check if the view containing your form is actually being rendered by the new action of your PermissionsController.
I think I have figured it out.
What I noticed is that in modules.rb I'm loading the actions inside a controller through controller.action_methods. after this I'm deleting the new and edit actions.
I was under the assumption that I was simply receiving a set of strings containing the action names instead of the actual actions. So it seems logical that if I delete the new and edit actions that my views would default to the new and edit actions in ActionController::Base. Which would mean that #permission is not set and the view throws a well deserved error.
I'm assuming that doing this removes these actions in runtime for the controller. which means that it would of course work again after I restart the server.
So I fixed my issue by doing controller.action_methods.to_a which forces decoupling of the actual actions in the controller. After this I'm free to delete strings in this array that I don't require.
Moral of the story: Watch out with controller.action_methods?

Inserting into a Postgres Array from Rails

I am very new to Rails, I want to be able to insert names into an array attached to my user.
I added an empty array called 'subscribed_tasks' to my 'schema.rb'
And I wondered how I can push data into that array.
My first attempt was:
Having '= link_to "Subscribe", subscribe_task_path, class: "btn btn-default"' in my show.haml and then having this within my route.rb:
resources :tasks do
member do
get :subscribe
end
end
& then adding this to my Tasks_Controller:
def subscribe
#user = current_user
#user.subscribed_tasks << #task.title
redirect_to #task, notice: "Subscribed to "+ #task.title
end
The problems I'm facing are:
How do I pass the task they're subscribing to's parameters to the controller so then the correct data can be pushed into the array. Also, I don't think I'm finding the array correctly.
All in all, what I'm programming is a mess, am I missing something fundamental about Rails and is there an entirely better way to solve this problem?

Rspec2 + Rails4: Testing for displayed model fields in form partial

Question
I'd like to write tests that check the model fields that are displayed in my "show" and "form" partials. I succeeded for "show", not for "form".
Main constrain: The solution must be able to loop through an Array that contains each names of the model fields.
I believe this case can be interesting for anyone that is trying to shorten his test script files, while having many fields, and having a complete control over what's displayed and what's not, so I'll put some efforts trying to find a solution, with your help if you please :)
Form view
Nothing fancy
= form_for #user do |f|
= f.select :field_1, options_from_collection_for_select ...
= f.text_field :field_2
...
Actual situation
I found an easy way for the "show" partial, here is how my spec file looks like:
def user_fields_in_show_view
[:field_1, :field_2, ..., :field_n]
end
it 'display fields' do
user_fields_in_show_view.each do |field|
User.any_instance.should_receive(field).at_least(1).and_call_original
end
render
end
This works well.
-
But the exact same technique does not work in the "form" partial, using the same code
def user_fields_in_form_view # List of fields need to be different, because of the associations
[:field_1_id, :field_2, ..., :field_n]
end
it 'display fields' do
user_fields_in_form_view.each do |field|
User.any_instance.should_receive(field).at_least(1).and_call_original
end
render
end
It whines like this:
Failure/Error: Unable to find matching line from backtrace
Exactly one instance should have received the following message(s) but didn't: field1_id, field_2, ..., field_n
# Backtrace is long and shows only rspec/mock, rspec/core, rspec/rails/adapters, and spork files
What I tried so far
1- I commented out the stub part of my tests and output rendered to the console, to manually check what's generated by my view, and yes the fields are correctly generated.
2- I replaced User.any_instance by the model I assign to the view, error is slightly different but it still not working
it 'display fields' do
user = create :user
assign :user, user
user_fields_in_form_view.each do |field|
user.should_receive(field).at_least(1).and_call_original
end
render
end
Gives:
Failure/Error: user.should_receive(field).at_least(1).and_call_original
(#<User:0x0000000506e3e8>).field_1_id(any args)
expected: at least 1 time with any arguments
received: 0 times with any arguments
3- I change the code so the it is inside the loop, like this:
user_fields_in_form_view.each do |field|
it 'display fields' do
user = create :user
assign :user, user
user.should_receive(field).at_least(1).and_call_original
render
end
end
Same result as above
And I run out of options. I suspect the internals of FormBuilder to play a bad trick on me but I can't figure it out, I'm not very knowledgeable with those yet. Thanks for reading
I usually try to write unit test as simple as possible. Loops in unit tests don't add much readability and are not very good practice in general. I'd rewrite the test like this:
it 'should display user name and email' do
# note: `build` is used here instead of `create`
assign :user, build(:user, first_name: 'John', last_name: 'Doe', email: 'jd#example.com')
render
rendered.should have_content 'John'
rendered.should have_content 'Doe'
rendered.should have_content 'jd#example.com'
end
Thus, we're not limiting the view in how it should render the first and the last name. For example, if our view uses the following (bad) code in order to render user's full name, then your test will fail, but my test will work just fine, because it tests the behaviour of the view, not its internals:
<%= user.attributes.values_at('first_name', 'middle_name').compact.join(' ') %>
Moreover, multiple assertions in one test is a bad smell too. Going one step further, I'd replace this test with three smaller ones:
it "should display user's first name" do
assign :user, build(:user, first_name: 'John')
render
expect(rendered).to include 'John'
end
it "should display user's last name" do
assign :user, build(:user, last_name: 'Doe')
render
expect(rendered).to include 'Doe'
end
it "should display user's email" do
assign :user, build(:user, email: 'jd#example.com')
render
expect(rendered).to include 'jd#example.com'
end
========
UPD: Let's make it more dynamic in order to avoid tons of repetition. Tis doesn't answers why your spec fails, but hopefully represents working tests:
%i(first_name last_name email).each do |field|
it "should display user's #{field}" do
user = build(:user)
assign :user, user
render
expect(rendered).to include user.public_send(field)
end
end
In order to make these tests more reliable, make sure that user factory doesn't contain repetitive data.
I am not quite sure how you build your form, but if you use the form_for, simple_form_for or formtastic_form_for helpers, actually you are using a different object. You write something like (assume the basic form_for)
= form_for #user do |f|
and all methods are relayed to object f. Now f.object will point to #user, but is it a copy of #user or #user itself, I don't know. So I would expect that User.any_instance should work.
Regardless, when doing a view test, it is not important how the contents of a field are set, it is important that the contents of a field are set correctly. Suppose you build your forms yourself, you switch to another library to build your forms, and all your tests break, because it retrieves the data differently. And that should not matter.
So I am with #DNNX, and in your view tests you should test the content of the rendered HTML and not how the data is retrieved.

Rails: Prevent duplicate inserts due to pressing back button and save again

Think about a simple Rails scaffold application with a "new" action containing a form to add records to a database with a "save" button. After the "create" action the controller redirects to the "show" action, where the user can use the "edit" link to edit the just inserted record. So far, so simple.
But if the user instead uses the browser's back button after creating a record to get back to the "new" action, the browser shows the form with the values the user just has entered. Now he changes some values and presses "save" again. He thinks that this would change the record, but of course this creates a new record.
What is the preferred way to prevent such duplicate entries? I'm looking for a general solution, maybe based on cookies or JavaScript.
After some investigations I found a suitable solution based on cookies. Here it is:
In the controller's "new" action, a timestamp with the current time is generated and rendered in the form as hidden field. When the user submits the form, this timestamps gets back to the controller's "create" action. After creating the record, this timestamp is stored in the session cookie. If the user goes back to the "new" form via browser's back button, he gets a stale form, which means its timestamp is older than the one stored in the cookie. This is checked before creating the record and results in an error message.
Here is the controller code:
def new
#post = Post.new
#stale_form_check_timestamp = Time.now.to_i
end
def create
#post = Post.new(params[:post])
if session[:last_created_at].to_i > params[:timestamp].to_i
flash[:error] = 'This form is stale!'
render 'new'
else
#post.save!
#stale_form_check_timestamp = Time.now.to_i
session[:last_created_at] = #stale_form_check_timestamp
end
end
And here the form code:
- form_for #post do |f|
= tag :input, :type => 'hidden', :name => 'timestamp', :value => #stale_form_check_timestamp
= f.input :some_field
= .......
When I had that same problem I created this little gem that solves it. When the user hits back, he's redirected to the edit_path of the record, instead of going back to the new_path.
https://github.com/yossi-shasho/redirect_on_back
You can do something like:
def create
#user = User.new(params[:user])
if result = #user.save
redirect_on_back_to edit_user_path(#user) # If user hits 'back' he'll be redirected to edit_user_path
redirect_to #user
end
end
Your model validations will ensure things like email addresses are unique, but I think this is more about usability and experience than anything else.
Say you are talking about an account creation form. First of all, your form submit button should say something like "Create Account", instead of just "Submit". Then depending on whether it was successful or not, show a message like either "Account successfully created" or "There were errors creating your account". If the user sees this message, they will know what happened.
Sure you can't prevent someone from hitting the back button and hitting enter again, but you should design for the majority of use cases. If they happen to hit back, they will see the button that says "Create Account". You should probably have some other text on the page that says "Please sign up for a new account to get started".
Just my $0.02.
Session or cookie may result in sides effects.
I totally agree : if there is a way to validate with your model, it's the safest way to prevent duplicate records.
Still you can do 2 things. Prevent browser caching : fields will appear empty in the form when the user clicks on the back button. And disable the "Create" button when clicked.
= f.submit "Create", :disable_with => "Processing..."
When your user will press the back button the button will be disabled.
You can use validators to make sure that no duplicate values are inserted. In this case validates_uniqueness_of :field
If you for example want to prevent users from having the same email address you could put the following code in your user model.
validates_uniqueness_of :email
This checks the column for any previous entries that are the same as the one your trying to inert.
Good luck
base on #Georg Ledermann answer i make this little snip of code for redirect to edit path if the user hits back and then hits create.
#objects_controller.rb
def new
#object = Object.new
#stale_form_check = Time.now.to_i
end
def create
#object = Object.new(object_params)
#function defined in application_controller.rb
redirect_to_on_back_and_create(#object)
end
#application_controller.rb
private
def redirect_to_on_back_and_create(object)
if session[:last_stale].present? and session[:last_stale_id].present? and session[:last_stale].to_i == params[:stale_form_check].to_i
redirect_to edit_polymorphic_path(object.class.find(session[:last_stale_id].to_i)), alert: "Este #{object.model_name.human} ya ha sido creado, puedes editarlo a continuación"
else
if object.save
session[:last_stale] = params[:stale_form_check].to_i
session[:last_stale_id] = object.id
redirect_to object, notice: "#{object.model_name.human} Creado con éxito"
else
render :new
end
end
end
And finally add the #stale_form_check param to your form
<%= hidden_field_tag :stale_form_check, #stale_form_check %>
You could always abstracts this method where you need it, but in this way you could avoid lots of repetition in your project if you need this behavior in many parts
Hope it helps the next one, i used to use redirect_on_back gem, but it didn't work for me this time, the _usec param that this gem uses, was always been reset, so it can't compare in every time when it was need
Here's something that worked for me.
You will need to do 2 things: Create a method in your controller and add a conditional statement in that same controller under your 'create' method.
1) Your method should return the total count of that object from that user.
EX:
def user
current_user.object.count
end
2) Add conditional statement in your 'create' method.
EXAMPLE:
def create
#object = Object.create(object_params)
#object.save if user == 0
redirect_to x_path
end
I hope this helps!
Add html: { autocomplete: "off" } in your form_for like this:
<%= form_for #object, url: xxx_path, html: { autocomplete: "off" } do |f| %>

Resources