Archive for the ‘Ruby’ Category

Rails admin with authlogic

If you have used the rails_admin gem, you would have noticed that its tightly coupled with Devise. What happens if devise is not the pick and the application is already integrated with authlogic. Lets see,

After installing the rails_admin gem, create a ‘rails_admin.rb‘ file in config/initializers. Paste the following code,

  RailsAdmin.config do |config|
    config.authorize_with do
      redirect_to root_path, :alert => "You are not authorized!" unless current_user.admin?
    end
  end

  RailsAdmin.config do |config|
    config.authenticate_with do
      unless current_user
        redirect_to login_url
      end
    end
  end

The authentication block checks if the user is currently logged in and the authorization block checks if the logged in user is an admin or not.

Also add the rails admin route in the config/routes.rb file.

  mount RailsAdmin::Engine => '/admin', :as => 'rails_admin'

This should enable the admin to run at /admin, now authenticated and authorized using authlogic.

Advertisements

Mercury: The WYSIWYG html editor

I had this application where different users would want to edit custom html pages to be shown up in there web sites. Each user will have his own domain( all domains pointing to the same Rails application ) and the custom html page had to be loaded as per the current domain. To do this, in search of a WYSIWYG html editor which is easy to setup and simple to start off, I ended up in Mercury. Whats really nice was that Mercury also had a gem to be used for Rails developer and as I am one, I had no more hesitation in get started with mercury.

To get started off with mercury, add


gem 'mercury-rails'

to the Gemfile and bundle it.

Run the rails generator for the mercury files.


rails g mercury:install

A couple of questions will be posted. Press ‘yes’ to install the layout files.

Now checking out the directory structure,  you could see three additional files.

mercury.js and mercury.css in the js and stylesheets assets respectively. Also, a new layout file for the mercury editor, mercury.html.erb 

I did remove the mercury css file later on.

One thing that needs to be noticed here is that the mercury.js file is heavy and it woudn’t be a good idea to load it in all the pages. We would want to load it in only the pages that needs to be edited. Checkout the mercury layout file and you can see that the mercury.js file is included.

    <head>
        <meta name="viewport" content="width=device-width, maximum-scale=1.0, initial-scale=1.0">
        <%= csrf_meta_tags %>
        <title>Mercury Editor</title>
        <%= stylesheet_link_tag 'mercury' %>
        <%= javascript_include_tag 'jquery-1.7', 'mercury' %>
    </head>

Now to prevent mercury.js from being loaded up in the pages, we could move all the other js files in our application to a separete directory and then require the directory in our application.js

My application.js will have,

//= require_tree ./main

where main is the directory which has all the application specific javascript. (Probably could be a better name 🙂 )

Now peep into the routes file, you could see this extra line,


mount Mercury::Engine => '/'

What this line does is that it allows the html pages in your application to be edited. An extra ‘/editor’  will have to be added at the beginning of each url path to load the mercury editor for the page.

Consider you have the url ‘localhost:3000/pages‘ , all you need to load it in the mercury layout is to change it to ‘‘localhost:3000/editor/pages‘ . You have mercury loaded up to edit your page and can now see it in the mercury editor’s layout.

Screenshot

However this isn’t just enough to start editing the page. You need to specify editable regions in the page.
In pages.html.erb 

    <div class="control-group">
        <h3 class="section_header page-header">Pricing page</h3>
        <div id="faq" class="mercury-region" data-type="editable" data-mercury="full">
            <%= render :file => file_path(@domain 'faq') %>
        </div>
    </div>

Consider this piece of code. A div with id=”faq” is made editable with class=”mercury-region” and attributes data-type=”editable” and data-mercury=”full”.

Now you can see the editable region.

Screenshot-1

This following line in above piece of code

<%= render :file => file_path(@domain, 'faq') %>

invokes a helper method and loads the already created sample faq template which can now be edited and saved for the particular domain. As simple as that.

Similarly you could edit more pages here. This is how the contacts page can be edited.

    <div class="control-group">
        <h3 class="section_header page-header">Contact page</h3>
        <div id="contact" class="mercury-region" data-type="editable" data-mercury="full">
            <%= render :file => file_path(@domain, 'contact') %>
        </div>
    </div>

Also, you probably might want to change the save url of the mercury editor for the particular page. That is the controller action to which the mercury edited contents will be ‘POST’ or ‘PUT’ (depends on the configuration set in the mercury.html.erb)

To change the mercury save url for this particular page, I wrote the script in the erb file ( pages.html.erb )

    <script>
        $(window).on('mercury:ready', function () {
            Mercury.saveUrl = "<%= pages_upload_admin_domain_path(@domain) %>";
        });
    </script>

You might also want to change the page thats to be redirected to once we are done with editing using mercury. We could bind on mercury’s save event to get this done.

    $(window).bind('mercury:saved', function() {
        $(window.location.replace('/admin/domain'));
    });

All this saved data would have to be dealt with in the controller action. Inspecting the params in the controller action ( the mercury Save url) ,

{"content"=>
    {"faq"=>
        {"type"=>"full",
         "data"=>{},
         "value"=> "<h1>This is where I have done my FAQ editing</h1>"
         "snippets" => {}
        }     
    },

    {"contact"=>
        {"type"=>"full",
         "data"=>{},
         "value"=> "<h1>This is where I have done my Contacts editing</h1>"
         "snippets" => {}
        }
    }
}

There are two things of notice here. The contents hash contains all the mercury related stuff.  Each hash in the contents hash has a key which is equal to the id of the mercury editable html divisions ( see the view code pasted above ), here ‘faq‘ and ‘contact‘. The actual edited html content can be found in the hash with key ‘value’ ( <h1>This is where I have done my Contacts editing</h1>).  ‘The controller action could decide on how to save this html content.

What have I done to solve my case mentioned at the starting?

I created a pages directory in my public. Within the pages directory I created sub directories which corresponds to the domain. For eg, the domain localhost corresponds to the directory named localhost inside the public/pages directory and the domain remotehost corresponds to the remotehost directory.

I then saved all these edited html content as html files within these domain specific directories. When a particular domain was loaded, the html pages ( for eg, faq and contact) was rendered from the corresponding domain directories in the public folder .

Delayed Jobs in Rails: Adding custom attributes

Ok, so this was my exact scenario. When I was doing a bulk emailing application,  there was the need for the client to upload his set of email ids as a file and then save it to the database. The process of saving these contact mail_ids for a particular mail group was a delayed process, handled by Rails delayed job . 

@mail_group.delay.save_group_contacts

where @mail_group is the active record group to which the mails_ids being uploaded and saved belong.

The requirement was to show a progress bar for the process of the mail_ids being saved to the the mail group. To handle this, I decided to add custom attributes to the delayed jobs table so as to identify the owner of the delayed job and also find the progress of the job.

To do this,

1) DB migration to add the custom attributes

    class AddColumnToDelayedJob < ActiveRecord::Migration
      def change
        add_column :delayed_jobs, :job_process_status, :integer, :default => 0
        add_column :delayed_jobs, :job_owner_id, :integer
        add_column :delayed_jobs, :job_owner_type, :string
      end
    end

2) A model for the delayed jobs table.

    module Delayed
      class Job < ActiveRecord::Base
        self.table_name = "delayed_jobs"
        attr_accessible :job_owner_id, :job_process_status, :job_owner_type
        belongs_to :job_owner, :polymorphic => true
      end
    end

As seen, three extra attributes (job_owner_id, job_owner_type attributes for establishing a polymorphic association with the job owner of the delayed job and a job_process_status attribute for updating the progress of the job) were added to the delayed jobs table.

Delayed jobs were then created with the job_owner_id and job_owner_type.

    @mail_group.delay(job_owner_id: @mail_group.id, job_owner_type: @mail_group.class.name).save_group_contacts

However this would not be enough to update the custom attributes. An attempt to create a delayed job would produce this

    ActiveModel::MassAssignmentSecurity::Error:
        Can't mass-assign protected attributes: job_owner_id, job_owner_type

As a quick fix, add a config/initializers/delayed_job.rb
and paste in the following code

    class Delayed::Job < ActiveRecord::Base
      self.attr_protected if self.to_s == 'Delayed::Backend::ActiveRecord::Job'   #loads protected attributes for                                                                                        # ActiveRecord instance
    end

Now the delayed job would get saved with the job_owner_id and job_owner_type.

Also, in the mail_group model, set an association to the delayed jobs table.

    class MailGroup < ActiveRecord::Base
      has_many :deferred_jobs, :as => :job_owner, :class_name => "::Delayed::Job"
    end

Now you can access all the delayed jobs of a particular @mail_group as

    @mail_group.deferred_jobs

The job process status which is updated by the running job can also be accessed as

    @mail_group.deferred_jobs.job_process_status

RubyconfIndia 2011

May 27,28 – Attended Rubyconf India 2011 held at Royal Orchid hotel, Bangalore.’  For some one who has less than a year of hands on with ruby, it was great hearing from the giants – the very Matz himself expressed his love for the community(in his own peculiar Japaneese way), Ola bini, Chad Fowler,  Brian and others. What in short ? – awesome 2 days.



Module functions as class and instance methods

Consider you have a module and a class.

module Mymod and a class Myclass.

The situation in hand is such that certain functions in the module need to end up being instance methods of the class Myclass and certain functions need to be Class methods.  You could very well imagine of such situations. Consider you are using ActiveRecord and have a sub class Subscription in correspondence with a DB table. You want to insert logic, within the module, that would work in each of the following case.

1) When a subscription fails or succeeds.

2) When an unsubscription fails or succeeds

You do this.

module SubscriptionLogic

   def  after_sub

      ….

   end

   def  after_unsub

     ….

   end

   def  after_sub_fail

     ….

   end

   def  after_unsub_fail

      ….

    end

end

class Subscription

   include SubscriptionLogic

   …..

end

You insert the logic as functions of a module, say SubscriptionLogic. Ideally you want the methods containing the logic to be instance methods of the class Subscription. You include the module SubscriptionLogic in the class and you avail all the functions in the module as Instance methods. Now you consider the case when an unsubscription fails – i.e, there is no existing subscription so that an unsub could take place.  No way is it possible that you could have a function ‘logic_after_unsub_fail’  in the module SubscriptionLogic and use it as an instance method, simply because there is no instance available.  You think and decide to use the function as your class method, but you have ‘included’  the  module in your class and hence its not possible to use it as your class method. You cannot extend the entire module coz , ideally you want the logic to be instance methods.

So you could get this solved up by a simple piece of extra coding.

module SubscriptionLogic

  def  after_sub

      ….

  end

  def  after_unsub

      ….

  end

  def  after_sub_fail

      ….

  end

  module ClassMethods

      def after_unsub_fail

           ….

      end

      def self.included(base)

          base.extend(ClassMethods)    # base pertains to the class within which you include the module.

      end

   end

end

Now within the class insert this line.

class Subscription

   include SubscriptionLogic

   extend SubscriptionLogic::ClassMethods

   …..

end