Rails/Rspec: Testing uniqueness validations in models without touching the database - ruby-on-rails-3

I current test uniqueness validations in rspec using something like the following:
#unique = Unique.create!(:unique_field => 'unique_value')
Unique.new(:unique_field => 'unique_value').should_not be_valid
Ideally, I'd like to get my nose out of the database for uniqueness validations. Any ideas how to go about doing that?

validates_uniqueness_of does not guarantee the absence of duplicate record insertions, because uniqueness checks on the application level are inherently prone to race conditionsmore. To get around this problem you might define unique index on on your unique field or write some workaround to have tests that don't depend on your database however I don't see any point in writing these tests. You'll just have some useless code in your app.

you can change the unique value dynamically by editing your factory to change every time you run test
for example
Factory.define :user do |f|
f.sequence(:username) { |n| "foo#{n}" }
f.password "foobar"
f.password_confirmation { |u| u.password }
f.sequence(:email) { |n| "foo#{n}#example.com" }
end
iHope it helpful , good luck
source : Railscasts Episode #158: Factory Girl

Related

New to Rails 4 Testing - Need help getting started (rSpec and Devise)

I'm relatively new to testing and very new to Rails 4 and rSpec. I am trying to test a controller that uses Devise for authentication and I am stuck. All of the examples I can find are for Rails 3.
I'm using Rails 4.0.3, Devise 3.2.3, rSpec 2.14.1 and FactoryGirl 4.4.0.
class LessonPlansController < ApplicationController
before_action :authenticate_user!
# GET /lesson_plans
def index
#lesson_plans = current_user.lesson_plans.to_a
end
.
.
.
private
# Use callbacks to share common setup or constraints between actions.
def set_lesson_plan
#lesson_plan = LessonPlan.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def lesson_plan_params
params[:lesson_plan]
end
def lesson_plan_params
params.require(:lesson_plan).permit(:title, :synopsis)
end
end
Here are my factory definitions: (Maybe I don't need to define user_id in the lesson_plan factory?)
FactoryGirl.define do
factory :user do
sequence( :username ) { |n| "user#{n}" }
sequence( :email ) { |n| "foo#{n}#example.com" }
password 'foobarbaz'
password_confirmation 'foobarbaz'
created_at Time.now
updated_at Time.now
end
end
FactoryGirl.define do
factory :lesson_plan do
user_id 1
title "The French Revolution"
synopsis "Background and events leading up to the French Revolution"
end
end
And the test part is where I get stuck.
describe LessonPlansController do
let(:valid_attributes) { { } }
let(:valid_session) { {} }
# describe "GET index" do
it "assigns all lesson_plans as #lesson_plans" do
user=FactoryGirl.create(:user)
sign_in user
lesson_plan = LessonPlan.create! valid_attributes
get :index, {}, valid_session
assigns(:lesson_plans).should eq([lesson_plan])
end
end
I'm not sure what to put in valid_attributes and valid_session (or if I even need them). The test will get as far as signing in the user, but will fail on creation of the lesson_plan. Admittedly this is the default/generated test for rSpec, but I am not sure how to proceed.
Examples I have seen use a before block to set up the user. I haven't been able to find anything on the Devise wiki page covering how to write basic rSpec tests for a controller that requires the user to be logged in. Any pointers would be greatly appreciated!
"I'm not sure what to put in valid_attributes and valid_session (or if I even need them)."
Well that depends what you're testing for.. Say you're testing validations & want to ensure that a record not be created if x column is set to null... then you could try to specifically create a record with invalid attributes (e.g. column: nil) and expect the result to not return true; maybe you want to ensure that it IS created with valid attributes.
You can btw, use `attributes_for(:factory_name)`` since you're using FactoryGirl. And no you don't necessarily need to specify the user's id in your lesson plan factory; unless you always want it to reference user 1. You can simply reference user with no value. Check out http://everydayrails.com/2012/03/12/testing-series-intro.html and especially parts 3-5 for an introduction to testing with RSPec.. I found this a pretty easy to follow guide when I was getting started.

Issue with pushing additional values in a embeds_many mongoid relation

I have been breaking my head around this for a long time now. Not sure if my approach is correct or if its not possible using mongoid. SO without further adieu, here is the problem:
I have the following 2 models:
def user
embeds_many :needs, :class_name => "Property"
embeds_many :skills, :class_name => "Property"
end
def property
end
Both these models of course have other code but I have skipped that for brevity.
With this structure I am able to access/add "Property" data as embedded "needs" & "skills" on my user model. Something like this works flawlessly
User.first.update_attributes(skills: [Property.first])
The problem is something like this doesn't work.
User.first.skills.push(Property.first)
User.first.skills << Property.first
There is no error. Both the above statements return true on console. But the values don't persist to the DB.
I basically want a Property model which can be maintained/created independent of the User model, thats why the "embedded_in" on Property is missing in my code.
The question is, am I doing it right? Or there is their a different way that I should go about the design of these models?
Cage is right. You will need to put the embedded_in on the Property model if you want the persistence to work properly. If you want to manage the lifecycle of Property outside the User model, you will have to use 'has_many'.
Please add more details as to what exactly is the purpose of doing what you are doing. I am going to make some assumptions-
Needs and skills are a global list of values, that should be maintained separately
User can have a subset of skills and needs
You want to store the needs and skills as 'plain string' and not references so you can query them without referencing the needs and skills collection. This reduces one query
If the above is correct, then you can use custom keys to store the needs and skills-
class Property
include Mongoid::Document
field :text, :type => String
field :_id, type: String, default ->{ text }
end
class User
include Mongoid::Document
has_many :needs, :class_name => "Property"
has_many :skills, :class_name => "Property"
end
Now you can do something like-
User.first.need_ids
This will give the text of the need and you can avoid another query.
Note- that this is potentially very risky if your 'Property' objects are mutable.
For solution try doing this
u = User.first
u.skills.push(Property.first)
u.skills << Property.first
it will work fine.

An efficient way to track user login dates and IPs history

I'm trying to track user login history for stat purposes but its not clear to me what the best way to go about it would be. I could have a separate table that records users and their login stats with a date, but that table could get REALLY big. I could track some historic fields in the User model/object itself in a parse-able field and just update it (them) with some delimited string format. e.g. split on :, get the last one, if an included date code isn't today, add an item (date+count) otherwise increment, then save it back. At least with this second approach it would be easy to remove old items (e.g. only keep 30 days of daily logins, or IPs), as a separate table would require a task to delete old records.
I'm a big fan of instant changes. Tasks are useful, but can complicate things for maintenance reasons.
Anyone have any suggestions? I don't have an external data caching solution up or anything yet. Any pointers are also welcome! (I've been hunting for similar questions and answers)
Thanks!
If you have the :trackable module, I found this the easiest way. In the User model (or whichever model you're authenticating)
def update_tracked_fields!(request)
old_signin = self.last_sign_in_at
super
if self.last_sign_in_at != old_signin
Audit.create :user => self, :action => "login", :ip => self.last_sign_in_ip
end
end
(Inspired by https://github.com/plataformatec/devise/wiki/How-To:-Turn-off-trackable-for-admin-users)
There is a nice way to do that through Devise.
Warden sets up a hook called after_set_user that runs after setting a user. So, supposed you have a model Login containing an ip field, a logged_in_at field and user_id field, you can only create the record using this fields.
Warden::Manager.after_set_user :except => :fetch do |record, warden, options|
Login.create!(:ip => warden.request.ip, :logged_in_at => Time.now, :user_id => record.id)
end
Building upon #user208769's answer, the core Devise::Models::Trackable#update_tracked_fields! method now calls a helper method named update_tracked_fields prior to saving. That means you can use ActiveRecord::Dirty helpers to make it a little simpler:
def update_tracked_fields(request)
super
if last_sign_in_at_changed?
Audit.create(user: self, action: 'login', ip: last_sign_in_ip)
end
end
This can be simplified even further (and be more reliable given validations) if audits is a relationship on your model:
def update_tracked_fields(request)
super
audits.build(action: 'login', ip: last_sign_in_ip) if last_sign_in_at_changed?
end
Devise supports tracking the last signed in date and the last signed in ip address with it's :trackable module. By adding this module to your user model, and then also adding the correct fields to your database, which are:
:sign_in_count, :type => Integer, :default => 0
:current_sign_in_at, :type => Time
:last_sign_in_at, :type => Time
:current_sign_in_ip, :type => String
:last_sign_in_ip, :type => String
You could then override the Devise::SessionsController and it's create action to then save the :last_sign_in_at and :last_sign_in_ip to a separate table in a before_create callback. You should then be able to keep them as long you would like.
Here's an example (scribd_analytics)
create_table 'page_views' do |t|
t.column 'user_id', :integer
t.column 'request_url', :string, :limit => 200
t.column 'session', :string, :limit => 32
t.column 'ip_address', :string, :limit => 16
t.column 'referer', :string, :limit => 200
t.column 'user_agent', :string, :limit => 200
t.column 'created_at', :timestamp
end
Add a whole bunch of indexes, depending on queries
Create a PageView on every request
We used a hand-built SQL query to take out the ActiveRecord overhead on
this
Might try MySQL's 'insert delayed´
Analytics queries are usually hand-coded SQL
Use 'explain select´ to make sure MySQL isusing the indexes you expect
Scales pretty well
BUT analytics queries expensive, can clog upmain DB server
Our solution:
use two DB servers in a master/slave setup
move all the analytics queries to the slave
http://www.scribd.com/doc/49575/Scaling-Rails-Presentation-From-Scribd-Launch
Another option to check is Gattica with Google Analytics
I hate answering my own questions, especially given that you both gave helpful answers. I think answering my question with the approach I took might help others, in combination with your answers.
I've been playing with the Impressionist Gem (the only useful page view Gem since the abandoned RailStat) with good results so far. After setting up the basic migration, I found that the expected usage follows Rail's MVC design very closely. If you add "impressionist" to a Controller, it will go looking for the Model when logging the page view to the database. You can modify this behaviour or just call impressionist yourself in your Controller (or anywhere really) if you're like me and happen to be testing it out on a Controller that doesn't have a Model.
Anyways, I got it working with Devise to track successful logins by overriding the Devise::SessionsController and just calling the impressionist method for the #current_member: (don't forget to check if it's nil! on failed login)
class TestSessionController < Devise::SessionsController
def create
if not #current_member.nil?
impressionist(#current_member)
end
super
end
end
Adding it to other site parts later for some limited analytics is easy to do. The only other thing I had to do was update my routes to use the new TestSessionController for the Devise login route:
post 'login' => 'test_session#create', :as => :member_session
Devise works like normal without having to modify Devise in anyway, and my impressionist DB table is indexed and logging logins. I'll just need a rake task later to trim it weekly or so.
Now I just need to work out how to chart daily logins without having to write a bunch of looping, dirty queries...
There is also 'paper_trail' gem, that allows to track model changes.

How to validate Rails Model attribute uniqueness over a virtual attribute scope

How can I validate uniqueness of an attribute with a custom or virtual scope? I thought of using a virtual attribute, but it keeps trying to query audit_year in the database. I would rather not create another database column just for the purpose of this uniqueness constraint.
Each location can only have one audit scheduled per year, so I need to extract the year from the scheduled attribute and validate uniqueness over that scope.
class Audit
attr_accessible :location_name, :scheduled_date, :completion_date ...
validates :location_name, :presence => true, :uniqueness => { :scope => :audit_year }
...
def audit_year
scheduled_date.year
end
end
I may not even be on the correct path with my virtual attribute attempts. What would be the "right" way to do this in rails?
I know this is a bit late, but I figured I'd pitch in. I'm doing this from memory so you may need to screw with this a bit.
My first thought is in your audit_year method, you could query the database for all years. Something like:
def audit_year
!self.class.select('selected_date').map{ |a| a.selected_date.year }.include?(selected_date.year)
# or using Rails 3.2 and Ruby 1.9.3:
# !self.class.pluck('created_at').map(&:year).include?(selected_date.year)
end
My assumption of the unique method is if it returns false, it will fail validation. This code selects just that one column from your class (I used self.class instead of Audit so it's more reusable), then maps out the years to an array. If it's included (true), return the opposite (!). You could probably optimize the query with uniq, but it depends on how large the table is whether it's necessary or not.
The other option would be to roll your own validator for the attribute, which really wouldn't be that difficult. The only difference is you'd add a line that conditionally checks for selected_date.present? in addition to the above. A great resource is the Rails Guides for callbacks and errors if you don't know how: http://guides.rubyonrails.org/active_record_validations_callbacks.html
Hope that helps.

Rails: Avoiding duplication errors in Factory Girl...am I doing it wrong?

Suppose I have a model user, which has a uniqueness constraint on the email field
If I call Factory(:user) once all is well, but if I call it a second time it'll fail with an "entry already exists" error.
I'm currently using a simple helper to search for an existing entry in the DB before creating the factory...and calling any factory I make through that helper.
It works, but it's not entirely elegant, and considering how common I assume this problem must be, I'm guessing there's a better solution. So, is there an inbuilt way in factory girl to return_or_create a factory, instead of just charging ahead with create()? If not, how do most folk avoid duplicate entries with their factories?
Simple answer: use factory.sequence
If you have a field that needs to be unique you can add a sequence in factory_girl to ensure that it is never the same:
Factory.define :user do |user|
sequence(:email){|n| "user#{n}#factory.com" }
user.password{ "secret" }
end
This will increment n each time in order to produce a unique email address such as user52#factory.com. (See https://github.com/thoughtbot/factory_girl/wiki/Usage for more info)
However this isn't always great in Rails.env.development...
Over time I have found that this is not actually the most useful way to create unique email addresses. The reason is that while the factory is always unique for your test environment it's not always unique for your development environment and n resets itself as you start the environment up and down. In :test this isn't a problem because the database is wiped but in :development you tend to keep the same data for a while.
You then get collisions and find yourself having to manually override the email to something you know is unique which is annoying.
Often more useful: use a random number
Since I call u = Factory :user from the console on a regular basis I go instead with generating a random number. You're not guaranteed to avoid collisions but in practice it hardly ever happens:
Factory.define :user do |user|
user.email {"user_#{Random.rand(1000).to_s}#factory.com" }
user.password{ "secret" }
end
N.B. You have to use Random.rand rather than rand() because of a collision (bug?) in FactoryGirl [https://github.com/thoughtbot/factory_girl/issues/219](see here).
This frees you to create users at will from the command line regardless of whether there are already factory generated users in the database.
Optional extra for making email testing easier
When you get into email testing you often want to verify that an action by a particular user triggered an email to another user.
You log in as Robin Hood, send an email to Maid Marion and then go to your inbox to verify it. What you see in your inbox is something from user_842#factory.com. Who the hell is that?
You need to go back to your database to check whether the email was sent / received by whomever you expected it to be. Again this is a bit of a pain.
What I like to do instead is to generate the email using the name of the Factory user combined with a random number. This makes it far easier to check who things are coming from (and also makes collisions vanishingly unlikely). Using the Faker gem (http://faker.rubyforge.org/) to create the names we get:
Factory.define :user do |user|
user.first_name { Faker::Name::first_name }
user.last_name { Faker::Name::last_name }
user.email {|u| "#{u.first_name}_#{u.last_name}_#{Random.rand(1000).to_s}#factory.com" }
end
finally, since Faker sometimes generates names that aren't email-friendly (Mike O'Donnell) we need to whitelist acceptable characters: .gsub(/[^a-zA-Z1-10]/, '')
Factory.define :user do |user|
user.first_name { Faker::Name::first_name }
user.last_name { Faker::Name::last_name }
user.email {|u| "#{u.first_name.gsub(/[^a-zA-Z1-10]/, '')}_#{u.last_name.gsub(/[^a-zA-Z1-10]/, '')}_#{Random.rand(1000).to_s}#factory.com" }
end
This gives us personable but unique emails such as robin_hood_341#factory.com and maid_marion_10#factory.com
Here's what I do to force the 'n' in my factory girl sequence to be the same as that object's id, and thereby avoid collisions:
First, I define a method that finds what the next id should be in app/models/user.rb:
def self.next_id
self.last.nil? ? 1 : self.last.id + 1
end
Then I call User.next_id from spec/factories.rb to start the sequence:
factory :user do
association(:demo)
association(:location)
password "password"
sequence(:email, User.next_id) {|n| "darth_#{n}#sunni.ru" }
end
I found this a nice way to be sure the tests will always pass.
Otherwise you can not be sure the 100% of the times you will create a unique email.
FactoryGirl.define do
factory :user do
name { Faker::Company.name }
email { generate(:email) }
end
sequence(:email) do
gen = "user_#{rand(1000)}#factory.com"
while User.where(email: gen).exists?
gen = "user_#{rand(1000)}#factory.com"
end
gen
end
end
If you only need to generate a few values for attributes, you can also add a method to String, which keeps track of the prior strings used for an attribute. You could then do something like this:
factory :user do
fullname { Faker::Name.name.unique('user_fullname') }
end
I use this approach for seeding. I wanted to avoid sequence numbers, because they do not look realistic.
Here the String extension which makes this happen:
class String
# Makes sure that the current string instance is unique for the given id.
# If you call unique multiple times on equivalent strings, this method will suffix it with a upcounting number.
# Example:
# puts "abc".unique("some_attribute") #=> "abc"
# puts "abc".unique("some_attribute") #=> "abc-1"
# puts "abc".unique("some_attribute") #=> "abc-2"
# puts "abc".unique("other") #=> "abc"
#
# Internal:
# We keep a data structure of the following format:
# ##unique_values = {
# "some_for_id" => { "used_string_1" : 1, "used_string_2": 2 } # the numbers represent the counter to be used as suffix for the next item
# }
def unique(for_id)
##unique_values ||= {} # initialize structure in case this method was never called before
##unique_values[for_id] ||= {} # initialize structure in case we have not seen this id yet
counter = ##unique_values[for_id][self] || 0
result = (counter == 0) ? self : "#{self}-#{counter}"
counter += 1
##unique_values[for_id][self] = counter
return result
end
end
Caution: This should not be used for lots of attributes, since we track all prior strings (optimizations possible).