I have a program in which a user can post a 'peep', along with it being timestamped. How can I test this using capybara? The problem is everytime a new timestramp is created, the time differs. Would I have to create some sort of stub/mock in capybara? Help would be greatly appreciated. Thank you.
class Peep
include DataMapper::Resource
property :id, Serial
property :title, String
property :text, String
property :created_at, DateTime
end
DataMapper.setup(:default, "postgres://localhost/chitter_#{ENV['RACK_ENV']}")
DataMapper.finalize
DataMapper.auto_upgrade!
App.rb:
require 'data_mapper'
require 'sinatra/base'
require 'sinatra/flash'
require_relative './models/peep.rb'
require_relative './models/user.rb'
ENV["RACK_ENV"] ||= "development"
class Chitter < Sinatra::Base
enable :sessions
register Sinatra::Flash
set :session_secret, 'super secret'
get '/' do
'You arrived at the homepage'
end
get '/peeps' do
#peeps = Peep.all
erb(:index)
end
get '/peeps/new' do
erb(:new)
end
get '/users/new' do
#user = User.new
erb(:'users/new')
end
post '/users' do
#user = User.create(email: params[:email],
password: params[:password],
password_confirmation: params[:password_confirmation])
if #user.save
session[:user_id] = #user.id
redirect to('/peeps')
else
flash.now[:errors] = #user.errors.full_messages
erb(:'users/new')
end
end
post '/peeps' do
peep = Peep.new(title: params[:title], text: params[:text])
peep.save
redirect to('/peeps')
end
helpers do
def current_user
#current_user ||= User.get(session[:user_id])
end
end
run! if app_file == $PROGRAM_NAME
end
Views:
<h1> Peeps </h1>
<ul id='peeps'>
<% #peeps.reverse.each do |peep| %>
<li id="peeps">
Title: <%= peep.title %>
Text: <%= peep.text %>
created_at: <%= peep.created_at %>
</li>
<% end %>
</ul>
If doing this in a capybara driven feature test, the easiest solution is to use one of the time travelling/freezing solutions such as timecop - https://github.com/travisjeffery/timecop - so that your test can be wrapped in a method that freezes the time.
Timecop.freeze(specific_datetime) do
# fill out form
...
# submit form
...
expect(page).to have_content("created_at: #{specific_datetime.tos}") # format the specific_datetime as expected
end
Note: If the date was actually being set in the browser by JS you'd need to also use something like sinon.js to freeze the browser time too.
If you're using RSpec you could probably do something like:
RSpec.describe Peep do
let(:peep) { Peep.create(title: "My title", text: "Some text") }
describe "#created_at" do
it { expect(peep).to respond_to(:created_at) }
it { expect(peep.created_at).to be_kind_of(DateTime) }
end
end
I would recommend not thoroughly testing that the functionality provided by DataMapper works as intended, as doing so would be very involved. It's a third party library that is also tested. Testing that you have added the line property :created_at, DateTime correctly, however, could be achieved with the above specs.
Related
I’m using Rails 4.2.7. I would like to throw a validation error if a user doesn’t enter their date of birth field in the proper format, so I have
def update
#user = current_user
begin
#user.dob = Date.strptime(params[:user][:dob], '%m/%d/%Y')
rescue ArgumentError => ex
end
if #user.update_attributes(user_params)
and I have this in my view
<%= f.text_field :dob, :value => (f.object.dob.strftime('%m/%d/%Y') if f.object.dob), :size => "20", :class => 'textField', placeholder: 'MM/DD/YYYY' %>
<% if #user.errors[:dob] %><%= #user.errors[:dob] %><% end %>
However, even if someone enters a date like “01-01/1985”, the above doesn’t return a validation error to the view. What do I need to do to get the validation error to be returned properly?
Edit: Per one of the answers given, I tried
#user = current_user
begin
#user.dob = Date.strptime(params[:user][:dob], '%m/%d/%Y')
rescue ArgumentError => ex
puts "Setting error."
#user.errors.add(:dob, 'The birth date is not in the right format.')
end
if #user.update_attributes(user_params)
last_page_visited = session[:last_page_visited]
if !last_page_visited.nil?
session.delete(:last_page_visited)
else
flash[:success] = "Profile updated"
end
redirect_to !last_page_visited.nil? ? last_page_visited : url_for(:controller => 'races', :action => 'index') and return
else
render 'edit'
end
And even though I can see the "rescue" branch called, I'm not directed to my "render 'edit'" block.
Triggering an exception doesn't add anything to the errors list. If you just want to tweak this code slightly, you should be able to call errors.add inside the rescue block. Something like #user.errors.add(:dob, 'some message here').
Keep in mind that this will only validate the date of birth when using this controller method. If you want to validate the date of birth whenever the user is saved, you'll want to explicitly add the validation to the model. You can write your own custom validation class or method, and there are also some gems that add date validation.
Calling update_attributes clears out the errors that you set in the rescue. You should check for errors, and if none, then continue on, something like this:
#user = current_user
begin
#user.dob = Date.strptime(params[:user][:dob], '%m/%d/%Y')
rescue ArgumentError => ex
puts "Setting error."
#user.errors.add(:dob, 'The birth date is not in the right format.')
end
if !#user.errors.any? && #user.update_attributes(user_params)
last_page_visited = session[:last_page_visited]
if !last_page_visited.nil?
session.delete(:last_page_visited)
else
flash[:success] = "Profile updated"
end
redirect_to !last_page_visited.nil? ? last_page_visited : url_for(:controller => 'races', :action => 'index') and return
end
render 'edit'
Since you redirect_to ... and return you can close out the conditional and, if you make it this far, simply render the edit page.
You may also want to add a simple validation to your user model:
validates :dob, presence: true
This will always fail if the dob can't be set for some other, unforseen, reason.
To get the user entered string to populate the field on re-load, you could add an accessor to the user model for :dob_string
attr_accessor :dob_string
def dob_string
dob.to_s
#dob_string || dob.strftime('%m/%d/%Y')
end
def dob_string=(dob_s)
#dob_string = dob_s
date = Date.strptime(dob_s, '%m/%d/%Y')
self.dob = date
rescue ArgumentError
puts "DOB format error"
errors.add(:dob, 'The birth date is not in the correct format')
end
Then change the form to set the :dob_string
<%= form_for #user do |f| %>
<%= f.text_field :dob_string, :value => f.object.dob_string , :size => "20", :class => 'textField', placeholder: 'MM/DD/YYYY' %>
<% if #user.errors[:dob] %><%= #user.errors[:dob] %><% end %>
<%= f.submit %>
<% end %>
And update the controller to set the dob_string:
def update
#user = User.first
begin
##user.dob = Date.strptime(params[:user][:dob], '%m/%d/%Y')
#user.dob_string = user_params[:dob_string]
end
if ! #user.errors.any? && #user.update_attributes(user_params)
redirect_to url_for(:controller => 'users', :action => 'show') and return
end
render 'edit'
end
def user_params
params.require(:user).permit(:name, :dob_string)
end
I would add a validation rule in the model. Like:
validates_format_of :my_date, with: /\A\d{2}\/\d{2}\/\d{4}\z/, message: 'Invalid format'
Try adding validation rule in model.
validate :validate_date
def validate_date
begin
self.dob = Date.parse(self.dob)
rescue
errors.add(:dob, 'Date does not exists. Please insert valid date')
end
end
and in your controller update your code
...
#user.update_attributes(user_params)
if #user.save
....
I think this is a case where Active Model shines. I like to use it to implement form objects without extra dependencies. I don't know the exact details of your situation but below I pasted a small demo that you should be able to adapt to your case.
The biggest benefit is that you don't pollute your controllers or models with methods to support profile updates. They can be extracted into a separate model which simplifies things.
Step 1: Store dob in users
Your users table should have a column dob of type date. For example:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :name, null: false
t.date :dob, null: false
end
end
end
Don't put anything fancy in your model:
class User < ActiveRecord::Base
end
Step 2: Add Profile
Put the following in app/models/profile.rb. See comments for explanations.:
class Profile
# This is an ActiveModel model.
include ActiveModel::Model
# Define accessors for fields you want to use in your HTML form.
attr_accessor :dob_string
# Use the validatiors API to define the validators you want.
validates :dob_string, presence: true
validate :dob_format
# We store the format in a constant to keep the code DRY.
DOB_FORMAT = '%m/%d/%Y'
# We store the user this form pertains to and initialize the DOB string
# to the one based on the DOB of the user.
def initialize(user)
# We *require* the user to be persisted to the database.
fail unless user.persisted?
#user = user
#dob_string = user.dob.strftime(DOB_FORMAT)
end
# This method triggers validations and updates the user if validations are
# good.
def update(params)
# First, update the model fields based on the params.
#dob_string = params[:dob_string]
# Second, trigger validations and quit if they fail.
return nil if invalid?
# Third, update the model if validations are good.
#user.update!(dob: dob)
end
# #id and #persisted? are required to make form_for submit the form to
# #update instead of #create.
def id
#user.id
end
def persisted?
true
end
private
# Parse dob_string and store the result in #dob.
def dob
#dob ||= Date.strptime(dob_string, DOB_FORMAT)
end
# This is our custom validator that calls the method above to parse dob_string
# provided via the params to #update.
def dob_format
dob
rescue ArgumentError
errors[:dob] << "is not a valid date of the form mm/dd/yyyy"
end
end
Step 3: Use the form in the controller
Use Profile in ProfilesController:
class ProfilesController < ApplicationController
def edit
# Ensure #profile is set.
profile
end
def update
# Update the profile with data sent via params[:profile].
unless profile.update(params[:profile])
# If the update isn't successful display the edit form again.
render 'edit'
return
end
# If the update is successful redirect anywhere you want (I chose the
# profile form for demonstration purposes).
redirect_to edit_profile_path(profile)
end
private
def profile
#profile ||= Profile.new(user)
end
def user
#user ||= User.find(params[:id])
end
end
Step 4: Render the form with form_for
In app/views/profiles/edit.html.erb use form_for to display the form:
<%= form_for(#form) do |f| %>
<%= f.label :dob_string, 'Date of birth:' %>
<%= f.text_field :dob_string %>
<%= f.submit 'Update' %>
<% end %>
Step 5: Add routing
Keep in mind to add routing to config/routes.rb:
Rails.application.routes.draw do
resources :profiles
end
That's it!
I am currently trying to make custom validations work with an input of dates, but, unfortunately, it doesn't seem to work.
There are two pages inside the application, Index page and Search page. Inside the index page there is a text field that takes in a date. I am using Chronic gem which parses text into dates. If the date is invalid, Chronic returns nil. If it is valid, it redirects to search page and shows the date.
The code I wrote so far doesn't seem to work properly, but what I want to achieve is..
1) to validate that Chronic doesn't return nil
2) to validate that date is greater than today's date
Please note that I am not using a database with this, I just want to be able to validate inputted date without ActiveRecord. If someone could help me with this, your help will be greatly appreciated.
views/main/index.html.erb
<%= form_tag({controller: "main", action: "search"}, method: "get") do %>
<%= label_tag(:q, "Enter Date:") %>
<%= text_field_tag(:q) %>
<%= submit_tag "Explore", name: nil %>
<% end %>
views/main/search.html.erb
<%= #show_date %>
main_controller.rb
def search
passed_info = Main.new
if passed_info.valid_date?
#show_date = passed_info
else
flash[:error] = "Please enter correct date!"
render :index => 'new'
end
end
models/main.rb
class Main
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :q
validates_presence_of :q
def initialize(params={})
params.each do |attr, value|
self.public_send("#{attr}=", value)
end if params
end
def persisted?
false
end
def valid_date?
require 'chronic'
if Chronic.parse(q).nil? || Chronic.parse(q) < Time.today
errors.add(:q, "is missing or invalid")
end
end
end
EDIT:
this is what goes wrong...
localhost:3000
then it redirects to ..
localhost:3000/main/search?utf8=%E2%9C%93&q=invalid+date+test
No validation, no date, nothing..
The Problem
Be more careful with return values. When you try to guard your controller with if valid_date?, what you're doing is checking to see if valid_date? returns false. If the parse fails, the return value is the output of errors.add, which in turn is the output of Array#<<. Relevantly, the output isn't nil or false, so it evaluates to true, thus the if clause passes and you move forward.
Potential Solution
You probably want to let the Rails Validation Framework do more work for you. Instead of treating valid_date? as a public method which the controller calls, call the valid? method that gets added by ActiveModel::Validations. valid? will return a boolean, based on whether all the model validations pass. Thus, you would, as is the Rails Way, call if model_instance.valid? in your controller.
This lets you just write validator methods in your model which express the logic you're trying to write. Right now, you have all validation logic for dates in a single method, with a single error message. Instead, you could put two methods, which add more descriptive individual error methods.
class YourClass
include ActiveModel::Validations
validate :date_is_valid
validate :date_not_before_today
private
def date_is_valid
if Chronic.parse(q).nil?
errors.add(:q, "Date is invalid")
end
end
def date_not_before_today
if Chronic.parse(q) < Date.today
errors.add(:q, "Date cannot be before today")
end
end
end
Just as correctly suggested by ABMagil, I would like to post the full solution to my answer. In fact, this answer can apply really to anyone who wants to use validations using ActiveModel, with or without Chronic gem or dates involved. It can act as a valid template so to speak.
Frankly, most of my mistakes came from a really poor, at the time, understanding of what I actually tried to achieve. Most of the code needed major refactoring, see below the updates that I had to make. I tried to keep the code as well documented as possible.
Solution:
views/main/index.html.erb
<%= form_for #search, url: { action: "search" },
html: { method: :get } do |f| %>
# Displays error messages if there are any.
<% if #search.errors.any? %>
The form contains <%= pluralize(#search.errors.count, "error") %>.<br />
<% #search.errors.each do |attr, msg| %>
<%= msg %><br />
<% end %>
<% end %>
<%= f.label :q, "Enter Date:" %>
<%= f.text_field :q %>
<%= f.submit "Explore", :class => 'submit' %>
<% end %>
views/main/search.html.erb - same as before
<%= #show_date %>
main_controller.rb
def index
# Initializes a string from the form to a variable.
#search = Search.new
end
def search
# Retrieves the input from the form.
#search = Search.new(params[:search])
# Checks for validity,
# If valid, converts a valid string into a date.
# Redirects to search.html.erb
# If not valid, renders a new index.html.erb template.
if #search.valid?
#show_date = (Chronic.parse(params[:search][:q])).to_date
else
render :action => 'index'
end
end
models/main.rb
class Main
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
# Accepts the passed attribute from the form.
attr_accessor :q
# If form is submitted blank, then output the prompting message.
validates_presence_of :q, :message => "This text field can't be blank!"
# Two custom validations that check if passed string converts to a valid date.
validate :date_is_valid
validate :date_not_before_today
# Initializes the attributes from the form.
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
# Checks for persistence, i.e. if it's a new record and it wasn't destroyed.
# Otherwise returns false.
def persisted?
false
end
# ABMagil's code used for custom date validations
private
require 'chronic'
def date_is_valid
if Chronic.parse(q).nil?
errors.add(:base, "Date is invalid")
end
end
def date_not_before_today
if !Chronic.parse(q).nil?
if Chronic.parse(q) < Date.today
errors.add(:base, "Date cannot be before today")
end
end
end
end
Result:
I am using the gems authority and rolify to manage user permissions on a set of subjects.
Each subject can be seen by a user only if the user has the :admin role for that subject.
Code in the view:
<% if (current_user.has_role? :admin, #subject) %>
ADMIN
<% end %>
<% if #subject.readable_by?(current_user)%>
#some other code
<% end %>
Code in the authorizer:
class SubjectAuthorizer < ApplicationAuthorizer
# can the user view the subject?
def self.readable_by?(user)
user.has_role? :admin, #subject
end
end
My problem is that the ADMIN part is displayed, but not the rest of the page. However, the two if conditions should have the same truth value. Can anyone spot a mistake?
#subject is not available in the Authorizer. You need to use resource instead.
If I'm entirely honest, I don't understand action mailers in their entirety and I'm finding it hard to discover a learning resource that isn't using an app which is of a completely different context (e.g teamtreehouses todo app). I would really appreciate a little help.
I have a business directory, I want each listings show page to have a form which when filled in, sends the entered info to the listings attached email.
Here's my code:
Mailers/Enquiry.rb
class Enquiry < ActionMailer::Base
default from: "admin#uk-franchise.co.uk"
def lead(listing, info)
#listing = listing
mail(to: #enquiry.email, subject: 'Email Testing Rails App')
mail(to: #listing.leadrecepient, subject: "test")
end
end
listings controller method
def lead
info = params[:leadname]
notifier = Notifier.lead(#listing, info)
end
Routes I'm stuck on configuring as I don't fully understand them for mailers.
What I have in the show listing view so far
<%= form_for lead_path(#leadname, #listing), method: :put do |lead| %>
<% end %>
Again, if anyone could provide me with a learning resource that would accommodate this scenario or a little help I would really appreciate it!
Here's what you have to do:
Do not use mail method twice in one method
class Enquiry < ActionMailer::Base
default from: "admin#uk-franchise.co.uk"
def lead(listing)
#listing = listing
mail(to: #listing.leadrecepient, subject: "test")
end
end
Send your email from within controller action:
class ListingsController
def lead
##listing = Listing...
Enquiry.lead(#listing).deliver
end
end
routes.rb:
# ...
resources :listings do
member do
put :lead
end
end
# ...
view:
<%= form_for lead_listing_path(#listing), method: :put do |listing| %>
<% end %>
Below I listed some code from simple Rails application. The test listed below fails in last line, because the updated_at field of the post is not changed within the update action of PostController in this test. Why?
This behaviour seems to me a little strange, because standard timestamps are included in Post model, live testing on local server shows that this field is actually updated after returning from update action and first assertion is fulfilled thus it shows the update action went ok.
How can I make fixtures updateable in above meaning?
# app/controllers/post_controller.rb
def update
#post = Post.find(params[:id])
if #post.update_attributes(params[:post])
redirect_to #post # Update went ok!
else
render :action => "edit"
end
end
# test/functional/post_controller_test.rb
test "should update post" do
before = Time.now
put :update, :id => posts(:one).id, :post => { :content => "anothercontent" }
after = Time.now
assert_redirected_to post_path(posts(:one).id) # ok
assert posts(:one).updated_at.between?(before, after), "Not updated!?" # failed
end
# test/fixtures/posts.yml
one:
content: First post
posts(:one)
That means "fetch the fixture named ":one" in posts.yml. That's never going to change during a test, barring some extremely weird and destructive code that has no place in sane tests.
What you want to do is check the object that the controller is assigning.
post = assigns(:post)
assert post.updated_at.between?(before, after)
On a side note if you were using shoulda (http://www.thoughtbot.com/projects/shoulda/) it would look like this:
context "on PUT to :update" do
setup do
#start_time = Time.now
#post = posts(:one)
put :update, :id => #post.id, :post => { :content => "anothercontent" }
end
should_assign_to :post
should "update the time" do
#post.updated_at.between?(#start_time, Time.now)
end
end
Shoulda is awesome.