Creating permalinks (slugs) in Rails - why is my test failing? - ruby-on-rails

I'm writing a small CMS as a Rails test project (also planning to use it for my personal website). I want SEO-friendly URLs so I have a test to verify that permalinks are automatically being created based on a page's title (e.g. About Us => about-us). I can't figure out why this test is failing, however. Here's the code (I'm using Rails 2.3.2):
# page_test.rb
# note I am using the "shoulda" framework
require 'test_helper'
class PageTest < ActiveSupport::TestCase
should_validate_presence_of :title, :permalink, :content
should_validate_uniqueness_of :title
should "create permalink automatically" do
p = pages(:sample_page)
p.save
assert_equal "sample-page", p.permalink
end
end
# pages.yml
sample_page:
title: Sample Page
permalink: # gets automatically created by model
content: This is a sample page
# page.rb
class Page < ActiveRecord::Base
validates_presence_of :title, :permalink, :content
validates_uniqueness_of :title
before_save :generate_permalink
private
def generate_permalink
self.permalink = self.title.parameterize
end
end
What happens is that the permalink is nil, instead of "sample-page" like it's supposed to be. It works, however, if I manually put the permalink in the fixture and change the test around, for example:
p - pages(:sample_page)
p.title = "Contact Us"
p.save
assert_equal "contact-us", p.permalink
I could fix it like this, but I'm wondering why it's not firing the before_save method for the original test.

Alright, I was able to figure it out. I needed to use before_validation as the callback and not before_save

Does it work if you remove the empty permalink: key from your pages.yml file?

Related

Trying to understand why validates_presence_of test fails

I'm just starting to have a play with Rails (using Rspec and Shoulda Matchers) to build a demo blog.
I've literally just started and my first test is failing but I can't understand why.
I think I've set up everything correctly, but when I try to validate that a title is present on my Article model but it returns a failure
Shoulda::Matchers::ActiveModel::AllowValueMatcher::AttributeDoesNotExistError:
The matcher attempted to set :title on the Article to nil, but that
attribute does not exist.
My model looks like this...
class Article < ApplicationRecord
# attr_accessor :title
validates_presence_of :title
end
and my test...
require 'rails_helper'
RSpec.describe Article do
it { should validate_presence_of :title }
end
If I uncomment out the attr_accessor then the test passes but I understand that it's not required with Rails.
What am I doing wrong?
The attr_accessor is not required, as long as your articles database table has a title column.
You can check that in db/schema.rb.

ActiveModel::Model error when initializing class with no params

When running a test, if I try to create a new object using User.new I get an error. If instead I use User.new({}), it works fine.
Isn't params supposed to be defaulted to empty if not passed in?
$ rails -v
Rails 5.0.0.1
user.rb
class User
include ActiveModel::Model
attr_accessor :name, :email, :country
end
user_test.rb
require 'test_helper'
class User < ActiveSupport::TestCase
test "should create an empty user when initialized with no params" do
user = User.new
assert_not_nil(user, 'user cannot be empty')
end
end
test result
Error:
User#test_should_create_an_empty_user_when_initialized_with_no_parameters:
ArgumentError: wrong number of arguments (given 0, expected 1)
test/models/user_test.rb:7:in `new'
test/models/user_test.rb:7:in `block in <class:User>'
Generally, attr_accessor is used on a model for columns that are not actual columns in the SQL table.
So if your model has columns :name, :email and :country, then declaring attr_accessor is unnecessary. I think rails is waiting for you to declare them now.
Try commenting out the attr_accessor :name, :email, :country line, then re-run your test.
This may help
Instead of including model class extend it like:
class Crime < ApplicationRecord
//do your stuff here
end
Then you can use #user = User.new
The User you're referencing in the test is the test itself.
The easiest solution is to follow Ruby convention and name the test class UserTest.

Unable to initialize ActiveRecord object

I'm trying to do in a rails console
>> user = User.new(:name => "", :email => "test#example.com")
=> #<User not initialized>
My User class looks like
class User < ActiveRecord::Base
attr_accessor :name, :email
has_many :microposts
def initialize(attributes = {})
#name = attributes[:name]
#email = attributes[:email]
end
def formatted_email
"#{#name} <#{#email}>"
end
end
I am following along from the rails tutorial. Why am I not able to initialize the object ?
tl;dr: Copy exactly from the book and you should be fine. (Note: I am the author.)
The example in question is from Chapter 4 of the Ruby on Rails Tutorial book and is not an Active Record model. In particular, the User class shown in the question is based on Listing 4.9:
class User
attr_accessor :name, :email
def initialize(attributes = {})
#name = attributes[:name]
#email = attributes[:email]
end
def formatted_email
"#{#name} <#{#email}>"
end
end
This class does not inherit from ActiveRecord::Base, but rather must be included explicitly in the console using require './example_user.rb', as described in Section 4.4.5. The behavior you're seeing is the result of including < ActiveRecord::Base in the first line, but if you copy the code in Listing 4.9 exactly you should see the expected behavior.
are you running your console in the same file directory as your project? I'd also try switching up theĀ  notation to the example used in the book and see if that gets you anywhere.
you can also try calling User.new with no attributes and see if it generates an object as listed in 6.1.3 of the tutorial , and then fill in the attributes and see if it works.
also make sure you dont have a validation on your user name in your model.
and a last check you can run user.error to see why it might not be saving
First, I assume that User model persists in your Rails app. That means, that you already have a migrated User model before running rails console.
If that table doesn't exist, you will be instanly prompted with:
=> User(Table doesn't exist)
Now, let's have some fun in rails console:
First things first, don't override initialize method in Rails model; While creating an object initialize method from ActiveRecord takes precedence (I think), so it may create conflicts. Instead use after_initialize callback. In console:
class User < ActiveRecord::Base
attr_accessible :name, :email
def after_initialize(attributes = {})
self[:name] = attributes[:name]
self[:email] = attributes[:email]
end
def formatted_email
"#{self.name} <#{self.email}>"
end
end
Now,
u = User.new({name: "Foo", email: "foo#bar.org"})
#<User name: "Foo", email: "foo#bar.org", created_at:nil updated_at: nil>
u.formatted_email
#=> "Foo <foo#bar.org>"
All done! Sweet.
UPDATE:
As per your recent gist; I see no point of having after_initialize at all. Rails does that on it's own.
First thing first, replace attr_accessor with attr_accessbile.
attr_accessor is ruby method(courtesy, metaprogramming) which creates getter and setter for provided instance variable. Rails uses attr_accessible for that; for security concerns, only instance variables allowed in attr_accessible allowed for mass-assignment (by sending params hash).
user.rb
class User < ActiveRecord::Base
attr_accessible :name, :email
#def after_initialize(attributes = {})
# self[:name] = attributes[:name]
# self[:email] = attributes[:email]
#end
def formatted_email
"#{self.name} <#{self.email}>"
end
end
Are you running console using the rails c command to load your environment from the root directory of your project? Typing irb to start a console session does not load the Rails application environment by itself.
Here are some more troubleshooting tips
Check to make sure that the development database specified in config/database.yml is running
Check to make sure a migration exists to create the Users table
Check to make sure the migrations have run with rake db:migrate
Check to make sure that a Users table actually does exist in the database, with columns of type varchar (or text) for fields :name and :email

How can I include rails code inside a validation message?

validates_presence_of :match,
:message => "for your name and password could not be found. Did you #{link_to "forget your password", help_person_path}?"
That's what I want to do. I can interpolate variables that are set in the model, but using simple Rails code like "link_to" doesn't seem to work. Which is a bummer.
You can't and shouldn't call the link_to method from your models. However, you can get access to named routes like this:
class PostHack
include Rails.application.routes.url_helpers
end
class Post < ActiveRecord::Base
validates_presence_of :match,
:message => "<a href='#{PostHack.new.send :admin_posts_path}'>My link</a>"
end
Very hackish. I hope you will not use it.

ActiveRecord - replace model validation error with warning

I want to be able to replace a field error with a warning when saving/updating a model in rails. Basically I want to just write a wrapper around the validation methods that'll generate the error, save the model and perhaps be available in a warnings hash (which works just like the errors hash):
class Person < ActiveRecord::Base
# normal validation
validates_presence_of :name
# validation with warning
validates_numericality_of :age,
:only_integer => true,
:warning => true # <-- only warn
end
>>> p = Person.new(:name => 'john', :age => 2.2)
>>> p.save
=> true # <-- able to save to db
>>> p.warnings.map { |field, message| "#{field} - #{message}" }
["age - is not a number"] # <-- have access to warning content
Any idea how I could implement this? I was able to add :warning => false default value to ActiveRecord::Validations::ClassMethods::DEFAULT_VALIDATION_OPTIONS
By extending the module, but I'm looking for some insight on how to implement the rest. Thanks.
The validation_scopes gem uses some nice metaprogramming magic to give you all of the usual functionality of validations and ActiveRecord::Errors objects in contexts other than object.errors.
For example, you can say:
validation_scope :warnings do |s|
s.validates_presence_of :some_attr
end
The above validation will be triggered as usual on object.valid?, but won't block saves to the database on object.save if some_attr is not present. Any associated ActiveRecord::Errors objects will be found in object.warnings.
Validations specified in the usual manner without a scope will still behave as expected, blocking database saves and assigning error objects to object.errors.
The author has a brief description of the gem's development on his blog.
I don't know if it's ready for Rails 3, but this plugin does what you are looking for:
http://softvalidations.rubyforge.org/
Edited to add:
To update the basic functionality of this with ActiveModel I came up with the following:
#/config/initializer/soft_validate.rb:
module ActiveRecord
class Base
def warnings
#warnings ||= ActiveModel::Errors.new(self)
end
def complete?
warnings.clear
valid?
warnings.empty?
end
end
end
#/lib/soft_validate_validator.rb
class SoftValidateValidator < ActiveModel::EachValidator
def validate(record)
record.warnings.add_on_blank(attributes, options)
end
end
It adds a new Errors like object called warnings and a helper method complete?, and you can add it to a model like so:
class FollowupReport < ActiveRecord::Base
validates :suggestions, :soft_validate => true
end
I made my own gem to solve the problem for Rails 4.1+: https://github.com/s12chung/active_warnings
class BasicModel
include ActiveWarnings
attr_accessor :name
def initialize(name); #name = name; end
warnings do
validates :name, absence: true
end
end
model = BasicModel.new("some_name")
model.safe? # .invalid? equivalent, but for warnings
model.warnings # .errors equivalent

Resources