The traditional way of testing the design of emails in rails has always
seemed really lame. Way back when, we had to connect to some
smpt server in development, then trigger the email, and then pull up the
email to check it. More recently, tools like
mailcatcher and letter
opener came about and eliminated the need to
actually connect to an email server and send the email.
However, this still isn't very rails like. You have to setup the
scenario and sometimes they're really long. For example:
create a user
verify user
certifiy user
user receives and an invitation to join by a customer (need a customer)
user decides to bid on project
customer awardes the bid to the user
finally "bid awarded" email gets triggered
Wait, you made a change, now repeat all of that.
A Solution: The MailView Gem
I can't remember how, but not too long ago, I discovered the MailView gem. What it does is allow you to test mail views in development mode by using an controller => action type scenario. The docs are pretty good, but I'll walk you through them here.
# Include the gemgem"mail_view","~> 1.0.3"
Then, create a controller like class that sub classes MailView:
# I think I read somewhere it's best to wrap this in an if statement to# only allow in dev mode. Maybe an issue on heroku if you don't??# Doesn't hurt anyway.ifRails.env.development?# app/mailers/user_mailer_preview.rb or lib/mail_preview.rbclassUserMailerPreview<MailView# Pull data from existing fixtures / dev datadefinvitationuser=User.firstUserMailer.invitation(user)end# Factory-like patterndefwelcomeuser=User.create!mail=UserMailer.welcome(user)user.destroymailend# Stub-likedefforgot_passworduser=Struct.new(:email,:name).new('name@example.com','Jill Smith')mail=UserMailer.forgot_password(user)endendend
Of course, this assmues you already have all of these mailers setup
(UserMailer.invitation, UserMailer.welcome, UserMailer.forgot_password)
Quick rails routes pro tip: if you're getting an error with routes,
make sure you have the above code placed high enough in your routes file
so another route isn't being processed before it. ( I once trouble
shooted this issue for half an hour because I had a *path catch all type
route before the mail_view one. )
So, that's pretty much it. If you have your preview class and "actions"
setup properly, then you can visit http://localhost:3000/mail_view.
This will show all the actions you have setup and you can click to view
each one.
* Remember to use the port you use for development
If you're using images, or links, you should remember to set the proper
port. Most rails apps default to port 3000, but if you're using foreman or
unicorn, you'll likely us another port. In your config/environment/development.rb file:
Then your images and links will work properly in dev mode.
# example image link<imgsrc="<%= image_url('logo.png')"/>
* Don't forget to use _url instead of _path so
your links are absolute.
Another "gotcha" migth be that if you change anything in your preview
class, then you need to restart your server. I'm not sure why that is,
but I read it somewhere and confirmed it true (changes to your mailer
view will reload properly though).
I hope that helps! Let me know if you have any questions.
I guess this is true with any business, but it's still fustrating to
have ups and down's in a business. Running a small consulting business,
I frequently only focus on moving the business forward. I have X number
of projects that I'm working on, deadlines, goals for number of hours
billed in a week, etc. The problem is, we usually only ever
anticipate things going perfect...best case scenario. However, we know
that Murphy's law likes to inject itself into our workflow from time to
time and things never go as we anticipate.
Just this past week, I had a string of several setbacks:
I switched DNS for a client and the mail quit working (this later
turned out not to be a result of my DNS change, but still caused me
to stop everything and deal with helping them get it fixed)
My wife got the stomach bug and I had to take the day off to watch
our kids
Thanksgiving Holidays (not really a setback, but 2 more days things
aren't getting done)
Think To Remember
I'm a cyclist and one thing cyclist hate is when their training plan /
goal gets messed up. Work, getting sick, bad weather and more can
interupt a training plan. The best cyclists are able to work with the
interuption.
I believe the same is true with business, you have to realize those
things are going to happen, embrace them, and plan for the interuption.
You need to add a new blog or a legacy blog to a rails app
Maybe your client wants to add a blog or like in my recent scenario,
your client has an existing blog that needs to get merged into a rails
app. There's several alternative ways to do this:
not actually merging them and keeping the blog and the rails app separate
Each of those have their advantages and disadvantages, but I want to
share a recent approach I used which is kind of a hybrid approach. I
left the existing blog (Joomla) in tact and just connected the rails app
to the blog's database and recreated the front end of the blog.
This worked great for several reasons:
New site design
We were creating a whole new site design so the front end of the blog
would have needed to be reskinned and tooled anyway.
Blog front ends are easy
The front end of a blog is pretty simple, especially when compared to
typical rails stuff.
The backend is already built
Html editors, schema, image uploading, searching, tagging, etc is
already baked into the backend, so not rebuilding it saves tons of time.
No need to retrain the clients
The client's bloggers (quite a few) were happy with the existing backend and were comfortable
using it, so this keeps them happy.
No need to migrate
Not migrating the existing data is a huge win.
The steps
The first thing you need to do is get a backup copy of the database and
pull it into your development environment. I also created a test
database for my tests.
Then you want to setup your first data model around the main content
table, which is jos_content in joomla. Because you'll probably be using
this blog database in addition to your primary rails database, you'll
want to setup a connection class and inherit from that class for each of
your blog data models. Also, I'm namespacing each of the classes to keep
them separate from my app's logic (would be cool to package this into a
gem later)
# app/models/blog/base.rbclassBlog::Base<ActiveRecord::Baseself.abstract_class=trueestablish_connection"blog_database_#{Rails.env}"end# app/models/blog/article.rbclassBlog::Article<Blog::Baseself.table_name="jos_content"belongs_to:category,foreign_key: :catiddefself.publicwhere("state = 1").order("publish_up DESC").where("publish_up <= '#{Time.now.utc}'")endend# app/models/blog/category.rbclassBlog::Category<Blog::Baseself.table_name="jos_categories"has_many:articles,foreign_key: :catidend# config/database.yml...blog_database_producion:
adapter: mysql2host: localhost# or if your blog is on another server, list the host hereencoding: utf8reconnect: truedatabase: client_joomla_developmentpool: 5username: rootpassword:
blog_database_development:
adapter: mysql2encoding: utf8reconnect: truedatabase: client_joomla_developmentpool: 5username: rootpassword:
Setting up a controller
After setting up the models and mapping the tables and relationships,
everything else is pretty much a breeze. Here's a controller for the
blogs.
# app/controllers/blog/articles_controller.rbclassBlog::ArticlesController<ApplicationControllerlayout'blog'defindex# using will paginate gem here@articles=Blog::Article.public.paginate(:page=>params[:page])enddefshow@article=Blog::Article.where(alias: params[:id]).firstendend
So there you have it. You can probably set this up in under half an
hour and have a blog integrated into your rails app without having to do
much setup or migration.
I setup an example app to test the code from above. Feel free to
reference it if you want to set soemthing like this up.
Ever since I started out on my own as a web developer, I've found it
difficult to manage projects. I don't feel like I'm necessarily
terrible at it (my previous job involved managing IT, Marketing, and Sales of a
3+ million small business). There's a couple of things usually get me
off schedule:
Short term time requirements This includes modifying/adding/fixing a
recently launched project or a 1-2 hr need for an existing client. It's
something most clients consider reasonable and expect to be done within 2-5
days.
Ballers Clients that are really visible, vocal and on the ball about their project. I typically like these clients (as long as they're never pushy or rude) because they keep me moving. However, these folks make it easy to forget about the previously scheduled project and focus on theirs.
What does this leave?
This typically leaves out the client who has a ton of other things going
on within their business and haven't held you to any timeline. These guys
usually get passed over for #1 and #2.
Background Story
Just recently I got a dreaded email from a client. To sum it up, they
wanted their deposit back and wanted to move on to another developer.
Ouch! That really heart my pride and professional opinion of myself.
Fortunately we've been really busy so the loss of business isn't as
hurtful, but it was a project I was actually looking forward to working
on and with a company I wanted to make a decent impression on. Plus I
was embarrassed and had a major case of regret.
What happened?
First, I admit to all fault. It's my responsibility of starting the
project, setting expectations and moving the project along.
It was a project that was slow to come together.
We started discussing ideas around the first of the year, I sent them an
estimate around Feb, we agreed on price around May and I got a check
around late June.
By this time I'd had several other larger sized projects come in as
well, which put me booked quite a long ways out (Oct / Nov).
This is where my (and a lot of projects) get off to a bad start:
knowing when you can start. I hate to displease people. The last
thing people want to hear after they just cut you a check is that you
can't start for some-long-period-of-time. And the truth is, in our line
of work, there's no possible way to determine a project's length, the end
date, and the start of the next one. Building software is tough, and most
good software projects will go through several iterations, with many changes,
and sometimes you wind up with something much different than what you
thought you'd have in the beginning (this is a good thing for the
business).
It's kinda difficult to explain my contact / point person's position on
the project, but he wasn't in the typical position of a client (owner,
CEO, CTO, etc). He also wasn't the kind of person to call and check in
on things or get updates that often. I'm not in any way blaming him,
but it made it super easy for me to tend to #1 and #2.
What works for me
During this same year, I started working with 2 really good clients that
I've come to respect (CongrueIt and Nexchain). Two of the guys with CongrueIT have been in the IT consulting industry field for some time and the guy with Nexchain has been all over the world managing projects.
The one thing they expected of me was constant communication. While working on a phase or iteration for
their project, we'd typically chat and/or screen share 1-2 times per
week. This would include before, during and even after the iteration
was complete. Reading this, you'd probably think, duh, you have to
communicate, but in reality, good intentions are always there. However, busy
schedules, getting behind, etc, make it difficult to actually follow through.
So the solution for me is now a mandatory weekly call from clients. I
ask my clients to hold me accountable to keeping the phone call,
providing updates, and keeping the project on tract. Likewise, I hold them
accountable for keeping the call, providing feedback, and staying active
in the project (this helps the money side of things too :-)
Open, honest, frequent, and regular/scheduled communication is the way to go. If you're not doing it, I encourage you start today.
I recently had a client want to change all h2 tags to h1 tags in a refinerycms app I managed. They had several hunderd pages, so manually doing it was not sounding good and using regular expressions for html parsing sounded like trouble.
Then I thought of Nokogiri. Nokogiri does a good job of parsing html right? But how do I change the html?
I ended up writin a rake task searched the body parts (main content body for refinery) for h2 tags and then used Nokogiri to change the tags.
To change the tags, you can just h2.name = "h1". Another problem I ran into was that I was using
Nokogiri::HTML(page.body)
But when I called to_html on it, I'd get the doctype and body tags with it. Then I found you can use fragment:
Nokogiri::HTML.fragment(page.body)
And that only retuned exactly what I wanted.
Check it out:
desc"Change body tags"task:change_body_tags=>:environmentdoputs"Changing Body Tags"body_parts=Refinery::PagePart.where(:title=>"Body")body_parts.eachdo|body_part|body_part.translations.eachdo|page|doc=Nokogiri::HTML.fragment(page.body)doc.css("h2").each{|h2|h2.name="h1"}page.body=doc.to_htmlpage.saveputs"Updated id # #{page.id}"endendend