Radiant CMS: Improving Drag & Order

In the Radiant administration interface, we wanted to allow our CMS users to be able to move pages around and have that final order in the navigation. Well, that is solved by an existing extension- the Drag and Order by Bright4.

We decided to change the icon to make it jump out more, and to turn off rollover highlighting when the user is dragging the page. This post is about changing the highlight rollover functionality.

The rollover highlight is set with CSS, in Radiant’s main.css Line 257:


#content table.index tr.highlight,
#content table.index tr:hover {
  background-color: #ffffb3;
}

So we needed to essentially turn the tr.highlight/tr.hover off when an object is being dragged, then turn it back on again after dragging, so the rest of the application still has this rollover functionality.

At first, we wrote our own JavaScript file and included it in the header partial, and on the events of MouseUp and MouseDown for the drag image.

A week later, I reviewed the Drag Order JavaScript, and was able to consolidate our changes within that script. When assessing all of the TRs for the table, I inserted a quick loop through all TRs and setting the background color to white:


	// scroll through all trs and set bgcolor to white to reset hover
	 var myTrArray = this.origRow.parentNode.getElementsBySelector('tr');
	 myTrArray.each(function(i) {
		    if(i.id != ''){ $(i.id).setStyle({ backgroundColor: '#ffffff'});}
	 });

This is on line 77- in the DragOrder function.

It’s great to consolidate a lot of changes in 4 lines!

Radiant CMS: Custom user permissions

Radiant has two built in user roles, administrator and developer, and the permissions on these roles cannot be configured. If your Radiant project demands more fine grained permission controls than those provided you must implement them with custom code. Fortunately Radiant provides several hooks into the code that allow us to do this.

In this article I will show you a way to write custom code that prevents users in the developer role from creating new pages. The pattern described here does not require you to modify the Radiant base code, and it can be used to restrict permissions on other functions of the admin pages as well. This article is relevant for Radiant 1.8.0. The methodology presented here has not been tested on any other versions.

To securely restrict access to page creation we will remove the “Add child” button from the UI and block access to the new and create actions on the PagesController. To begin, log in to your admin pages at http://localhost:3000/admin as a user in the developer role. You will be taken to the list of pages in your system and you can see the “Add child” button associated with each page. When you click on this button you are taken to a URL similar to http://localhost:3000/admin/pages/1/children/new. Note that you can also browse directly to this page without clicking the button, which is why we have to restrict access to the controller in addition to removing the button.

I recommend you put your custom code in an extension so that it is isolated from the Radiant code base and you won’t have to worry about collisions if you upgrade Radiant in the future. The custom tags example in the Radiant wiki has nice instructions for creating an extension. Don’t worry about the sections related to custom tags, just use the generator to create the extension (I named my extension “privileges”) and then open up the main extension class (vendor/extensions/privileges/privileges_extension.rb). In this class you will see a method called activate, which is run when the extension is registered. You can put your permissions code here as this will execute before any actions can be taken by the user.

To make the “Add child” button disappear you can inject conditional logic into the haml so that it checks the user permissions before displaying the button. The article on customizing the page UI in the Radiant wiki explains the use of partials in Radiant, and outlines the regions that are available to modify. Check out in [gems_root][radiant_root]/app/views/admin/pages/index.html.haml to see this structure in code. Don’t worry that the article says it might be out of date - the information is at least relevant for this task. In the activate method in your extension class add the following code:

admin.page.index[:node].delete('add_child_column')

This will delete the ‘add_child_column’ from the node region in the index view for pages. Restart your server, navigate back to the pages index, and see that the column is gone. To put the button back just for the admin role you will duplicate part of the Radiant code so you can add conditional logic around it. The part to duplicate is located in [gems_root][radiant_root]/app/views/admin/pages/_node.html.haml. Look at this file, and notice that it contains the display code for the add child column. Copy just this line:

%td.add-child= link_to image('add-child', :alt => 'add child'), new_admin_page_child_url(page)

And paste it into a new file in vendor/extensions/privileges/app/views/admin/pages called _add_child_column_subject_to_permissions.html.haml. Fix up your indenting or you will get errors - the pasted line should not be indented at all. Now add this partial into the index page by adding the following code to your activate method after the code that deletes the column:

admin.page.index.add :node, 'add_child_column_subject_to_permissions', :after => 'status_column'

This will add the new partial after the status column (right where we removed it from). Reload your server and your web browser and see that the column is back. Now that you have control of that code you can inject your conditional logic to check the user role. Add another line of code to _add_child_column_subject_to_permissions.html.haml so the file looks like this:

- if current_user.admin?
  %td.add-child= link_to image('add-child', :alt => 'add child'), new_admin_page_child_url(page)

Reload your server and your web browser and voila! The add child button has disappeared, and if you logout and then login as an admin you will see the button is still there.

To restrict access to the controller you can use the only_allow_access_to method that is provided as part of the login system (see [gems_root]/radiant-0.8.1/app/lib/login_system.rb. There are three parameters to pass to this method. The first is a comma separated list of actions to restrict. The second is a hash that includes the list of users who may access the action, and the URL to send the user to if they don’t have permission. The third is a message to show the user if they don’t have permission. We will inject this method call into the PagesController so it is called when the controller is instantiated like so:

Admin::PagesController.class_eval do
    only_allow_access_to :create, :new,
    {:when => ["admin"], :denied_url => {:controller => 'pages', :action => 'index'},
    :denied_message => "You don't have permission to perform this action."}
end

This restricts access to the create and new actions to ensure that the user cannot access the new page form or use some other means to submit a post request with the new page data.

Reload your server, log in as a developer and travel back to http://localhost:3000/admin/pages/1/children/new. You will see that you cannot access this page. So that’s it, developers can no longer create new pages!

Simple Email Form with ActionMailer

Working with ActionMailer is a bit different from working with ActiveRecord. While it makes sense to me that you would have a model for the data of the email message you send, the flow of control seems inverted from the rest of Rails. With ActionMailer call the model from the controller, then the model triggers the view code, rather than rendering the view from the controller with the model data as is typical with Rails. From the ActionMailer docs: “Emails are defined by creating methods within the model which are then used to set variables to be used in the mail template, to change options on the mail, or to add attachments. ” What follows is the steps that I followed to create a webform that sends an email in Rails, which turned out to be fairly simple, but it did take some digging to put the pieces together, so I figured other folks might find them useful.

Set up
This exercise requires Ruby 1.8.7 and Rails 2.2.1 or later to use TLS (required for Gmail SMTP). For older versions of either Ruby or Rails, you can try a plugin like action_mailer_optional_tls. There is a nice screencast introductory tutorial that runs through a subset of the following steps and is definitely worth watching.

To set up the context for this example, imagine we’re creating comment forms for a news website, where on each article page there is a comment form. To simulate this, we’ll just scaffold up some article pages:

$ rails mailer_example
$ ./script/generate scaffold article title:string author:string body:text
$ rake db:migrate

Test-Driven
In test-driven style (thanks to rubytutorials.net), here’s a spec for we’ll make work (at least the sending mail part of it). Note that “from” doesn’t meet expectations in 2.3.3 and 2.3.4 it is an array, rather than a string, but it has been fixed for a future release.

spec/models/notifier_spec.rb

equire File.dirname(__FILE__) + '/../spec_helper.rb'
describe Notifier do
  before(:each) do
    ActionMailer::Base.delivery_method = :test
    ActionMailer::Base.perform_deliveries = true
    ActionMailer::Base.deliveries = []
  end
  it "should send feedback email" do
    comment = {:name => "Name", :email => "[email protected]", :body => "nice article!"}
    mail = Notifier.deliver_feedback(comment)
    ActionMailer::Base.deliveries.size.should == 1
    mail.body.should =~ Regexp.new(comment[:body])
    mail.subject.should =~ Regexp.new(comment[:name])
    #mail.from.should =~ Regexp.new(comment[:email])
  end
end

Mailer Configuraton
Typically the settings would be different per environment and would be specified in config/environment/development.rb, etc. For this exercise, I just modified the main environment.rb file.

in config/environment.rb:

Rails::Initializer.run do |config|
	:
	:
  config.action_mailer.raise_delivery_errors = true
  config.action_mailer.delivery_method = :smtp
  config.action_mailer.smtp_settings = {
    :address => "smtp.gmail.com",
    :port => 587,
    :domain => "gmail.com",
    :user_name => "[email protected]",
    :password => "yourpassword",
    :authentication => :plain,
    :enable_starttls_auto => true
end

Generator
ActionMailer has a generator which creates files for the model and a view file, along with a unit test which is a lot less blank that the ActiveRecord unit tests.

$ ./script/generate mailer Notifier feedback
      create  app/models/
      create  app/views/notifier
      create  test/unit/
      create  test/fixtures/notifier
      create  app/models/notifier.rb
      create  test/unit/notifier_test.rb
      create  app/views/notifier/feedback.erb
      create  test/fixtures/notifier/feedback

Model
Edit the model, so that it takes a hash as it’s first parameter (we’ll end up getting this from the form).

class Notifier < ActionMailer::Base
  def feedback(comment, sent_at = Time.now)
    subject    "feedback from #{comment[:name]}"
    recipients "[email protected]"
    from       "\"#{comment[:name]}\"<#{comment[:email]}>"
    sent_on    sent_at
    body       comment
  end
end

Controller and Views
Rename the file “feedback.erb” to “feedback.html.text.erb” This will be the template used for the email, formatted in HTML. Had we wanted to send text-only emails, the file would have been called feedback.text.plain.erb. Rails sets the content type of the email to be the one in the filename. Or we could supply both and Rails will generate a “multipart/alternative” message with both template rendered for the html and plain mime parts.

Next we’ll create a form in the article show page and a controller action that will send the email.

in /views/articles/show.html.erb

<% form_tag :action => "feedback", :id => @article.id do -%>
<table>
    <tr>
        <td><label for="comment_name">Name:</label></td>
        <td><%= text_field "comment", "name", :size => 30 %></td>
    </tr>
    <tr>
        <td><label for="comment_email">Email Address:</label></td>
        <td><%= text_field "comment", "email", :size => 30 %></td>
    </tr>
    <tr>
        <td><label for="comment_body">Body:</label></td>
        <td><%= text_area "comment", "body", :rows => 8, :cols => 30 %></td>
    </tr>
</table>
<input type="submit" value="Send Feedback" class="primary" />
<% end -%>

in /controllers/articles_controller.rb

  def feedback
    article = Article.find(params[:id])
    article_info = {:title => article.title, :author => article.author, :id => article.id}
    comment = params[:comment].merge(article_info)
    Notifier.deliver_feedback(comment)
    render :text => "Thank you for your feedback"
  end

Now you should have a form that sends email. Yay!

origins of a company name

This summer I hired an intern who worked with me pair programming at my home office. One of the first projects we did together was an iPhone application using the Rhomobile framework for which we created a mocked up set of web services with Ruby on Rails. We deployed the web app on Heroku, which has this fabulous feature where it will automatically generate a sub-domain name for your app by combining an adjective, noun and number. The name for our app, blazing-cloud-46, struck me as one suitable for what we were working on… mobile apps in the cloud, blazing new trails. It also had that Web 2.0 ring to it. I liked the imagery of clouds at sunrise — what the world looks like when your day is full of possibilities.

So, when I needed a business name for this website and to be able to develop iPhone apps with a team, I registered “Blazing Cloud” as my fictitious business name. Now that Blazing Cloud has a bank account and an office in downtown San Francisco… it is starting to feel more real than fictitious.

Hello world!

Blazing Cloud website is now available. We’ll post company news and technical tips from our work in this space.