How do I create a rails association for cypress testing? - ruby-on-rails

I am trying to test a form on one particular page on my web app. The problem is that this web page depends on at least three model objects to be in the database for various reasons that I'll elaborate and since I'm new to Cypress for the testing, I'm not exactly sure how to go about this. So here are the problem areas:
describe('Basic SSL Certificate', () => {
context('csr submission', () => {
beforeEach(() => {
cy.request('POST', 'user_session/user_login', { login: 'testuser', password: 'Testing_ssl+1'})
.as('currentUser')
cy.appFactories([
['create', 'certificate_order']
]).as('certificateOrder')
})
it('rejects a numerical ip address for its csr', () => {
cy.visit(`/team/${this.certificateOrder.ssl_account.ssl_slug}/certificateOrders/${this.certificate_order.ref}/edit`);
First of all, the problem I am facing is this simple line of test code here:
cy.visit(`/team/${this.certificateOrder.ssl_account.ssl_slug}/certificateOrders/${this.certificate_order.ref}/edit`);
I need to hit the following url which looks like this /teams/abcd-xyz/certificate_orders/co-ref-1234/edit
Questions: How do I create rails associations with cypress? In my before block, I think I created a certificate order, does that have the associations with it on creation? Do I have to create each model seperately with appFactories and if I do, how do I "link" them together?
I don't see the way to combine ruby and javascript in this code and could use a pointer on setting up the factories. Usually in rspec I would create the models that I need and use them but in cypress I'm not sure how to do this because it doesn't seem to be the right way of doing it with JS. Helpful advice would be appreciated, thank you.

There are a few ways you can setup associations:
Setting the foreign keys
Using transient attributes
Using Nested Attributes
I have examples of all 3 in this post: https://medium.com/nexl-engineering/setting-up-associations-with-cypress-on-rails-and-factorybot-3d681315946f
My preferred way is using transient attributes as it makes is very readable from within Cypress
Example:
FactoryBot.define do
factory :author do
name { 'Taylor' }
end
factory :post do
transient do
author_name { 'Taylor' }
end
title { 'Cypress on Rails is Awesome' }
author { create(:author, name: author_name ) }
end
end
Then in Cypress you can do the following:
// example with overriding the defaults
cy.appFactories([['create', 'post', { title: 'Cypress is cool', author_name: 'James' }]]
// example without overriding
cy.appFactories([['create', 'post']]

Related

Rails 5. How to use an uploads controller to handle bulk uploads by dispatching each record

This is Rails 5 with Mongoid.
I have a MVC structure for load balancers. The model and the controller do all the validation and sanitising I want and need. However, I want to be able to send an array of balancer data structures to an upload controller, which will verify the upload is in a sane format, and then iterate over the array dispatching to the balancers controller to validate, insert, or upsert each entry as needed -- and accumulate statistics so that at the end of the iteration I can produce an HTML or JSON rendering of success count and detailed errors.
I'm pretty sure I need to frob the params structure as part of each iteration/dispatch, but it's unclear how, or whether I want to use the BalancersController's #new, #create, #update, #save, or what method in order to get the validation to work and the insert/upsert to be done properly.
I haven't yet been able to find anything that seems to describe this exact sort of bulk-upload scenario, so any/all help & guidance will be much appreciated.
Thanks!
(There are serveral other models/controllers I want to push through here; the balancer case is just an example.)
I've sort of got the collecting of the statistics summary down, but I' having trouble dispatching to the balancers controller to actually update the database with anything other than nil.
[edited]
Here's an simple example model:
class Puppet::Dbhost
include ::PerceptSys
include Mongoid::Document
include Mongoid::Timestamps
validates(:pdbfqdn,
:presence => true)
field(:pdbfqdn,
:type => String)
field(:active,
:type => Mongoid::Boolean)
field(:phases,
:type => Array)
index(
{
:pdbfqdn => 1,
},
{
:unique => true,
:name => 'dbhosts_pdbfqdn',
}
)
end # class Puppet::Dbhost
The uploads controller has a route post('/uploads/puppet/dbhosts(.:format)', :id => 'puppet_dbhosts', :to => 'uploads#upload').
The document containing upload information looks like this:
{
"puppet_dbhosts" : [
{
"pdbfqdn" : "some-fqdn",
"active" : true
}
]
}
The uploads#upload method vets the basic syntax of the document, and then is where I get stuck. I've tried
before_action(:upload, :set_upload_list)
def set_upload_list
params.require(:puppet_dbhosts)
end
def upload
reclist = params.delete(:puppet_dbhosts)
reclist.each do |dbhrec|
params[:puppet_dbhost] = dbhrec
dbhost = Puppet::Dbhost.new
dbhost.update
end
end
In very broad strokes. The logic for validating a puppet_dbhost entry is embedded in the model and the controller; the uploads controller is meant to simply dispatch and delegate, and tabulate the results. There a several models accepting uploads in this manner, hence the separate controller.
Well, I feel sheeeepish. Obviously the correct path is to abstract the generic upload methods and logic into a separate module and mix it in. Routes can be adjusted. So no need for controllers to know about the internals of others for this scenario. Thanks for the hint!

How to write factory_girl create command using fabrication in rails

I am learning how to use fabrication in Rails and we have decided to replace all our factory_girl code with fabrication.
Suppose we have this code in factory_girl, how will I rewrite the whole thing using fabrication?
FactoryGirl.create(
:payment,
user: user_ny,
amount: -4000,
booking: booking,
payable: payable]
)
Is this the correct code using Fabrication ? I am new to rails framework and will appreciate your help.
Fabricator(:payment) do
name { user_ny }
amount -4000
booking { booking }
payable { payable }
end
Fabrication's equivalent to FactoryGirl.create(:payment) is Fabricate(:payment).
It looks like booking and payable are other fabricators so you could write it like this:
Fabricate(:payment, name: user_ny) do
amount -4000
booking
payable
end
If you declare a relationship without a "value" it performs a default expansion and generates whatever the fabricator of the same name defines and sets it on the object.
In the case of user_ny above, the easiest way to use a local variable when fabricating is to pass it in as a parameter. You can mix and match however you want between the parameters and block syntax, although parameters will take precedence.

How to get model validations to talk with view layer in Rails to provide simple functionality?

I have a Room model that validates that its room number is unique. However, when a user is creating a new Room instance, if they try and create a room that already exists, instead of displaying the standard this room already exists error message, I would like to provide a link to that room so that they can edit it. Ex: I would like the error message to say: room x already exists. Click here to edit that room. where the Click Here text is a link to that room object's edit path.
Is there a way to do this or something similar in Rails? Any advice would be much appreciated!
You could use the client_side_validations gem https://github.com/bcardarella/client_side_validations to use your ruby validations in javascript (if you're using jquery) like shown in railscast #263 http://railscasts.com/episodes/263-client-side-validations.
You can create a custom validator, that finds the existing room_id and pass a link to it on to your error message that is returned to your view, something like:
# lib/room_existence_validator.rb
class RoomExistenceValidator < ActiveModel::EachValidator
def validate_each(object, attribute, value)
if existing_room = Room.find_by_id(value)
# unfortunately url_for helper is not defined here so it's hard-wired
room_link = "/rooms/#{existing_room.id}/edit"
object.errors[attribute] << "#{link_to "Click here", room_link} to edit that room"
end
end
end
You need to add the :validates => true part to your form_for helper call.
Now it works without ajaxified calls.
To make it work with ajax, there are 2 more things to do:
a) add a rails.validations.custom.js file, that could look something like this:
# rails.validations.custom.js
# please beware the .remote in function-name
ClientSideValidations.validators.remote['existing_room'] = function(element, options) {
if ($.ajax({
url: '/validators/existing_room',
data: { id: element.val() },
// async *must* be false
async: false
}).status == 404) { return options.message; }
}
b) route your validation-request e.g. by adding a rack middleware that respondes to your validators-routes as described in the client_side_validations wiki https://github.com/bcardarella/client_side_validations/wiki/Custom-Validators
Hope that helps :D

What's the benefit of Class.new in this Rspec

I am reading through some Rspec written by someone who left the company. I am wondering about this line:
let(:mailer_class) { Class.new(AxeMailer) }
let(:mailer) { mailer_class.new }
describe '#check' do
before do
mailer_class.username 'username'
mailer.from 'tester#example.com'
mailer.subject 'subject'
end
subject { lambda { mailer.send(:check) } }
It is testing this class:
class AxeMailer < AbstractController::Base
def self.controller_path
#controller_path ||= name.sub(/Mailer$/, '').underscore
end
I want to know the difference between this and let(:mailer_class) { AxeMailer }.
I ask this because currently when I run the test, it will complain name is nil. But if I changed it, it will test fine.
I think this issue started after using Rails 3.2, and I think name is inherited from AbstractController::Base.
This is the same in the console (meaning it is not Rspec specific), I can do AxeMailer.name with no error, but if I do Class.new(AxeMailer) there is is the problem.
My questions are:
Is there a reason to use Class.new(AxeMailer) over AxeMailer
Is there a problem if I just change this?
Is there a way not change the spec and make it pass?
I'm guessing it was written this was because of the mailer_class.username 'username' line. If you just used AxeMailer directly, the username setting would be carried over between tests. By creating a new subclass for each test, you can make sure that no state is carried over between them.
I don't know if mailer_class is being used inside the actual spec or not, but this is what I think your setup should look like:
let(:mailer) { AxeMailer.new }
describe '#check' do
before do
AxeMailer.username 'username'
mailer.from 'tester#example.com'
mailer.subject 'subject'
end
subject { lambda { mailer.send(:check) } }
There just doesn't seem to be a need for the anonymous class that was being created. Also, this is just my opinion, but your subject looks a bit odd. Your spec should probably wrap the subject in a lambda if it needs to, but don't do that in your subject.
Regarding the error you were seeing originally, anonymous classes don't have names:
1.9.3-p0 :001 > Class.new.name
=> nil
Some part of ActionMailer::Base must attempt to use the class name for something (logging perhaps) and breaks when it's nil.

What is Rails ActiveRecord find(x) method that is equivalent to a lazy-evaluated-scope? How to refactor ActiveRecord finder?

Is Rails' find(x) method on a model lazy? If not, what is the equivalent?
I am new to Rails, so I found myself writing scopes like this:
class Course < ActiveRecord::Base
scope :by_instructor_id, lambda { |instructor_id| where(:instructor_id => instructor_id) }
scope :by_course_template_id, lambda { |course_template_id| where(:course_template_id => course_template_id ) }
scope :by_company_id, lambda { |company_id| joins(:instructor).merge(CompanyUser.by_company_id(company_id)) }
end
It's not a lot of work, but now I'm asking myself... if Rails provided these with a scope, I wouldn't have to write them.
So, does Rails offer them? Can I do something like the below code and only make it do 1 database call?
Company.find(params[:id]).instructors.courses
instead of
Course.by_company_id(params[:id])
Which is correct? I know Course.by_company_id(params[:id]) is only 1 database call. It is very familiar to writing SQL or queries in Hibernate. But if you can write it the other way, maybe one should?
However, I don't want to write Company.find(params[:id]).instructors.courses if it results in more than 1 database call. I can see the advantage though because it means never having to write the 3 scopes I showed you above, but I am worried that Company.find(x) is not lazy. Is it?
Try using #scoped method on a model before calling #find:
user = User.scoped.instructors.courses.find(params[:id])
To make find by id query lazy you can add new method to your controller and then add this method as helper.
# company
def company
#company ||= Company.find(params[:id])
end
helper :company
#view
<%= company.name %>
To get more information you can check great RailsCast - Decent Exposure

Resources