Rails Rspec & FactoryGirl testing Association - ruby-on-rails-3

I have to model's where I accept Nested Attributes. I would like to build a test to make sure the nested attribute cant be blank etc. I really don't understand how I can make the test.
My two simple models:
# SeoMapping Model
class SeoMapping < ActiveRecord::Base
belongs_to :mappingtable, :polymorphic => true
attr_accessible :seo_url
validates :seo_url, :presence => true, :uniqueness => true
end
# Page Model
class Page < ActiveRecord::Base
has_one :seo_mappings, :as => :mappingtable, :dependent => :destroy
accepts_nested_attributes_for :seo_mappings
attr_accessible :content, :h1, :meta_description, :title, :seo_mappings_attributes
.........
end
Here are my factories for Page and Seo:
FactoryGirl.define do
factory :page do |f|
seo_mapping
f.title { Faker::Name.name }
f.h1 { Faker::Lorem.words(5) }
f.meta_description { Faker::Lorem.words(10) }
f.content { Faker::Lorem.words(30) }
end
end
FactoryGirl.define do
factory :seo_mapping do |f|
f.seo_url { Faker::Internet.domain_word }
end
end
And my tests:
require 'spec_helper'
describe Page do
it "has a valid factory" do
expect(create(:page)).to be_valid
end
# Cant get this spec to work?
it "it is invalid without a seo_url" do
page = build(:page)
seo_mapping = build(:seo_mapping, seo_url: nil)
page.seo_mapping.should_not be_valid
# expect(build(:page, :seo_mapping_attributes[:seo_url] => nil)).to_not be_valid
end
it "is invalid without a title" do
expect(build(:page, title: nil)).to_not be_valid
end
...............
end

Usually for this sort of thing I use a gem called shoulda_matchers. It lets you simply assert that your model validates presence of specific attributes.
it { should validate_presence_of(:seo_url) }
it { should validate_uniqueness_of(:seo_url) }
If you don't want to use the gem, try something like this:
seo_mapping = build(:seo_mapping, seo_url: nil)
page = build(:page, seo_mapping: seo_mapping)
page.should_not be_valid

Related

Rails validate model method

How can I validate the uniqueness of a full name in rails 3.2?
class User < ActiveRecord::Base
attr_accessible :first_name, :last_name
validates :name, :uniqueness => true
def name
[first_name, last_name].join(' ')
end
end
When I run rspec the test fails with this error.
1) User has a first name
Failure/Error: before { #user = FactoryGirl.create(:user) }
NoMethodError:
undefined method `text?' for nil:NilClass
Solution 1:
validates :first_name, :uniqueness => { :scope => :last_name, :message => "name is uniq" }
Solution 2:
class Person < ActiveRecord::Base
validates_with GoodnessValidator, :fields => [:first_name, :last_name]
end
class GoodnessValidator < ActiveModel::Validator
def validate(record)
if options[:fields].any?{|field| record.send(field) == "Evil" }
record.errors[:base] << "This person is evil"
end
end
end

Options for validates_with

I'm not able to access the values, passed as option in 'validates_with'
My model:
class Person < ActiveRecord::Base
include ActiveModel::Validations
attr_accessible :name, :uid
validates :name, :presence => "true"
validates :uid, :presence => "true"
validates_with IdValidator, :attr => :uid
My Custom Validator:
Class IdValidator < ActiveModel::Validator
def validate(record)
puts options[:attr]
...
...
end
end
For testing purpose, I'm printing "options[:attr]" and all I see is ":uid" in the terminal and not the value in it. Please help!
When you pass in :attr => :uid, you're just passing in a symbol. There's no magic happening hereā€”it just takes the hash of options you've attached and delivers it as the options hash. So when you write it, you see the symbol you've passed.
What you probably want is
Class IdValidator < ActiveModel::Validator
def validate(record)
puts record.uid
...
...
end
end
Because validates_with is a class method, you can't get the values of an individual record in the options hash. If you are interested in a more DRY version, you could try something like:
class IdValidator < ActiveModel::Validator
def validate(record)
puts record[options[:field]]
end
end
class Person < ActiveRecord::Base
include ActiveModel::Validations
attr_accessible :name, :uid
validates :name, :presence => "true"
validates :uid, :presence => "true"
validates_with IdValidator, :field => :uid
end
Where you pass in the name of the field you want evaluated.

Why is user.save true but email shows as nil?

I'm using a nested model form for sign-up and am working through the kinks as a beginner. One issue that popped up in particular though that I don't really get is user.email is returning as nil.
Before I started playing around with the nested model form, I could create records in the console wihtout a problem. Now, however I can't create records and some of the latest records created have nil as their email. (I'm not sure if it has anything to do with the nested model at all, but that's my reference point for when it started going haywire.)
If I go into rails console to create a new User/Profile, I follow this process:
user = User.new
user.email = ""
user.password = ""
user.profile = Profile.new
user.profile.first_name = ""
...
user.profile.save
user.save
Everything goes well until user.save, which gives me the NameError: undefined local variable or method 'params' for #<User:>. In rails console it pinpoints to user.rb:25 in create_profile
So here is my User model:
class User < ActiveRecord::Base
attr_accessor :password, :email
has_one :profile, :dependent => :destroy
accepts_nested_attributes_for :profile
validates :email, :uniqueness => true,
:length => { :within => 5..50 },
:format => { :with => /^[^#][\w.-]+#[\w.-]+[.][a-z]{2,4}$/i }
validates :password, :confirmation => true,
:length => { :within 4..20 },
:presence => true,
:if => :password_required?
before_save :encrypt_new_password
after_save :create_profile
def self.authenticate(email, password)
user = find_by_email(email)
return user if user && user.authenticated?(password)
end
def authenticated?(password)
self.hashed_password == encrypt(password
end
protected
def encrypt_new_password
return if password.blank?
self.hashed_password = encrypt(password)
end
def password_required?
hashed_password.blank? || password.present?
end
def encrypt(string)
Digest::SHA1.hexdigest(string)
end
end
Can anyone help me figure out what's going on?
UPDATE: I tried changing my regex but I'm still seeing nil for email. Though a prior SO post said not to blindly copy regex without testing, so maybe I just didn't test it correctly. Good news though: I no longer get the error.
attr_accessor simply defines a "property" on the object and has no relation to the attributes of a ActiveRecord model (attributes is a Hash of the fields and values obtained from a table).
ActiveRecord does not save such "properties" as defined by the attr_accessor. (Essentially, attr_accessor defines a attr_reader and attr_writer (i.e. "getter" and "setter") at the same time)

Rails: awesome_nested_set issues

I am using the awesome_nested_set to do a simple drag and drop reordering of news items and the post happens but the position field in my DB is not updated...
Here is my model:
class NewsItem < ActiveRecord::Base
translates :title, :body, :external_url
attr_accessor :locale, :position # to hold temporarily
alias_attribute :content, :body
validates :title, :content, :publish_date, :presence => true
has_friendly_id :title, :use_slug => true
acts_as_indexed :fields => [:title, :body]
acts_as_nested_set
default_scope :order => "publish_date DESC"
# If you're using a named scope that includes a changing variable you need to wrap it in a lambda
# This avoids the query being cached thus becoming unaffected by changes (i.e. Time.now is constant)
scope :not_expired, lambda {
news_items = Arel::Table.new(NewsItem.table_name)
where(news_items[:expiration_date].eq(nil).or(news_items[:expiration_date].gt(Time.now)))
}
scope :published, lambda {
not_expired.where("publish_date < ?", Time.now)
}
scope :latest, lambda { |*l_params|
published.limit( l_params.first || 10)
}
# rejects any page that has not been translated to the current locale.
scope :translated, lambda {
pages = Arel::Table.new(NewsItem.table_name)
translations = Arel::Table.new(NewsItem.translations_table_name)
includes(:translations).where(
translations[:locale].eq(Globalize.locale)).where(pages[:id].eq(translations[:news_item_id]))
}
def not_published? # has the published date not yet arrived?
publish_date > Time.now
end
# for will_paginate
def self.per_page
20
end
end
Anyone know why this wouldn't work?

how can I test accepts_nested_attributes_for with Rspec Rails3

I have a model as follows:
class Greeting < ActiveRecord::Base
attr_accessible :headline, :icon, :content
belongs_to :user
accepts_nested_attributes_for :user, :reject_if => proc { |a| a[:name].blank? || a[:email].blank? }
How can I do an Rspec test for this?
I just found this shoulda macro, seems like it works fine:
https://gist.github.com/1353500/bae9d4514737a5cd7fa7315338fdd9053dbff543
you should use it like this:
it{ should accept_nested_attributes_for :samples }
Here you have Shoulda macro for testing accepts_nested_attributes_for: http://mediumexposure.com/testing-acceptsnestedattributesfor-shoulda-macros/. It does not support any options (such as :reject_if), only bare accepts_nested_attributes_for.
But for :reject_if, you can create a valid Greeting model with nested attributes for User but without :name. Then check if user has been saved, and then same with blank :email
So you can do something like this:
describe Greeting
it { expect { Factory(:greeting, :user_attributes => Factory_attributes_for(:user)) }.to change(User, :count).by(1) }
it { expect { Factory(:greeting, :user_attributes => Factory_attributes_for(:user, :name => '')) }.to_not change(User, :count) }
it { expect { Factory(:greeting, :user_attributes => Factory.attributes_for(:user, :email => '')) }.to_not change(User, :count) }
end