YCMS – first development friendly CMS on Ruby on Rails

Always wanted to bootstrap your application really fast, but considering ActiveAdmin or RailsAdmin was a pain because of their limited flexibility? Now, with YCMS you don’t need to worry anymore. This CMS is as simple as it could ever be. No mountable Engines, not gems, simply git clone this application, rename your databases and you are set up and ready to go. My ruby on rails cms is customizable just as every normal app you would write from scratch. There is no hidden DSL, special methods or other stuff. You need a new model, you simply add it as you would to your own application.


  • Already included models for: posts, pages, page/post-categories, users
  • User management via Devise gem
  • Base settings available
  • Assets, views, controllers are split up into front and admin part.
  • Heavily DRY – all common CRUD methods are managed via a controller concern. There is only one form for each model, still perfectly clear and manageable via partials
  • Supported internationalization from the box
  • Controller, model and feature tests.
  • For I18n tests I am using i18n-task gem
  • Human readable routes

Please check it out on GitHub – https://github.com/mohnstrudel/ycms

Fork, contribute, enjoy!

Continuous Integration with Rails, Github and TravisCI

After 25 failing builds I finally managed it! My app works just fine with continuous integration provided by Travis CI.

Here my travis config using postgresql for test environment:

language: ruby

sudo: false

  - gem install bundler
  - "rm ${BUNDLE_GEMFILE}.lock"

cache: bundler

 - 2.3.1

 - DB=pgsql

 - postgresql

  adapter: postgresql
  database: ycms_test #(replace this with your database name)

# uncomment and edit the following line if your project needs to run something other than `rake`:
# script: 
# - bundle exec rspec spec

 - cp config/database.yml.travis config/database.yml
 - psql -c 'create database ycms_test;' -U postgres # replace it here aswell
 - bundle update
 - bundle exec rake db:test:prepare

Happy testing!

Create reusable form partials in Rails

If you’ve got an admin part in your rails app and some settings to take care of, chances are that you have a lot of similar data. And what can be more wearying, than creating all those forms, index/edit/new views over and over again? Of course you could use the scaffold, but I personally strongly dislike it.

So I figured out I try to make my partials variable as possible. This post shows you how you can create highly reusable form partials.

My structure is as following:

There is an admin panel. Inside admin panel there are settings (which get repetitive) and inside settings you can manage models like „colors“, „patterns“, „fabric“ (you can guess it – it’s a clothing online shop). All of them contain identical data, but have some differences as well.

I’ve got only 3 views – index, edit and new, because there is no sense for having a show view, as it should be the edit view right from the get go. This said, we need only two partials – one, for listing items inside our index view and one for creating and editing existing data.

Let’s start with routes.rb:

namespace :admin do
  	get '', to: 'dashboard#index', as: "/"

  	namespace :settings do
  		resources :borders
  		resources :colors
  		resources :patterns
  		resources :categories
      resources :fabrics

Simple dashboard when you visit plain old „/admin“ and then resources inside another namespace „settings“.

My index view – index.html.haml:

=render partial: 'admin/shared/list_items', locals: {name_ru: 'ткань', current_objects: @fabrics, name_en: "fabric"}

As you can see, I pass 3 local variable to my partial (this is the only thing you need to edit for each model. But hey, it’s only 3 words!) – the russian name, so I can have some customized buttons and links. The current collection and the name in english.

Listing items partial (a.k.a. index)

            %th.text-center Select
            %th Наименование
            %th.text-right Статус
          -unless current_objects.nil?
            -current_objects.each do |object|
                    %input{:name => "mobileos", :type => "checkbox", :value => "FR"}
                  =link_to object.name, send("edit_admin_settings_#{single}_path", object)
                    %button.btn.btn-success.br2.btn-xs.fs12.dropdown-toggle{"aria-expanded" => "false", "data-toggle" => "dropdown", :type => "button"}
                    %ul.dropdown-menu{:role => "menu"}
                        =link_to 'Редактировать', send("edit_admin_settings_#{single}_path", object)
                        =link_to 'Удалить', send("admin_settings_#{single}_path", object.id), method: :delete, data: {confirm: 'Действительно удалить?'}
                =link_to "Новая/ый #{name}", send("new_admin_settings_#{single}_path"), class: 'btn btn-primary'

Well, apart from some html, the important things are:

  • to check if there is at least one item in the collection (remember, we pass the collection under the name „current_objects“)
  • for each object in current_objects we access it using the variable „object“
  • since our partial is variable we can’t use hardcoded paths, like „admin_settings_colors_path“. Meaning we must make use of the „send“ method, which basically makes a method from a string. To pass parameters to a method created by „send“ you simply put this parameter as a parameter to the send-method. send(„hello_there“, caramba) is equivalent to calling method hello_there(caramba)
  • Because „object“ is actually an object (wow!) we can’t use it in the path generation. This is why I pass „single“ as a local variable. „Single“ is simply the singular form of the model (category, color, pattern etc.)

My edit/new view:

They are absolutely identical.

  =render 'admin/navigation/flash'
  =render partial: 'admin/shared/form', locals: {current_object: @pattern}

I render some flash messages to tell users about successful or failed actions. Then I render my second partial using again a local variable to transmit the collection.

Form partial

The core 🙂

=form_for [:admin, :settings, current_objects] do |f|
      %span.panel-title.hidden-xs Добавить новую сущность
          %a{"data-toggle" => "tab", :href => "#tab1_1"} Описание сущности
          -if current_objects.respond_to?(:picture)

              .fileupload.fileupload-new.admin-form{"data-provides" => "fileupload"}
                  -unless current_objects.picture.nil?
                    =image_tag current_objects.picture
                    %img{:alt => "holder", "data-src" => "holder.js/100%x140"}
                    %input#name2.text-center.event-name.gui-input.br-light.bg-light{:name => "name2", :placeholder => "Img Keywords", :type => "text"}
                      %label.field-icon{:for => "name2"}
                      %span.fileupload-new Select
                      %span.fileupload-exists Change
                      =f.file_field :picture, id: 'fileupload', type: :file
                      / %input{:type => "file", name: 'picture'}
              %label.field.prepend-icon{:for => "name2"}
                =f.text_field :name, id: "price", class: "event-name gui-input br-light light", placeholder: 'Название сущности'
                %label.field-icon{:for => "name2"}
            -if current_objects.respond_to?(:price)
                  =f.number_field :price, id: "price", class: "event-name gui-input br-light light", placeholder: 'Стоимость'
                  %label.field-icon{:for => "comment"}
                    %strong> Hint:
                    Don't be negative or off topic! just be awesome...
            -if current_objects.respond_to?(:hex)
                  =f.text_field :hex, id: "hex", class: "event-name gui-input br-light light", placeholder: 'Hex-значение цвета'
                  %label.field-icon{:for => "comment"}

        / end section row section
                %input{:checked => "", :name => "info", :type => "checkbox"}
                  Save Customer
                  %em.small-text.text-muted - A Random Unique ID will be generated
                =f.submit "Сохранить", class: "btn btn-primary"
          / end section
  -if current_objects.respond_to?(:image)
        %span.panel-title Загруженные фотографии рецепта
          %code.mr10.bg-light.dark.p3.ph5 фото можно удалить
        =f.fields_for :pictures do |builder|
          =image_tag builder.object.image.mini_thumb.url, class: "img-responsive thumbnail mr25 uzhin_doma_mini_thumb"
          =builder.label :_destroy, 'Удалить фотографию?'
          =builder.check_box :_destroy

Well, again a lot of HTML, but I will summarize the core points for you:

  • First of all we build our form using triple nesting =form_for [:admin, :settings, current_objects] do |f|
  • Now comes the most interesting part – surely your models will differ a bit. Maybe on has images to upload, the other doesn’t. One has a price tag, other don’t. Etc. and so on. Thus, we need to create an all-purpose-form, disabling fields when not required. This is done by respond_to? method. You can see it in this line  -if current_objects.respond_to?(:picture) This basically means, that if there is no :picture method for current object, the block below won’t be executed. Nifty, eh?
  • In my example every model has a field, called „name“, this is why I don’t check for it. All other fields are encapsulated in a respond_to? check.


With just a few methods – send and respond_to? you can create highly reusable partials where your maintain effort goes towards zero. Only one file for each action (index and edit/new) – truly amazing!

Bitrix 24 REST API

It is quite possible, that none of my visitors will ever stumble upon the Russian CRM system called Bitrix, however if you are one among the unluckiest people, this article might be useful for you. More specific, I will cover how to work with the cloud solution, Bitrix24 and its REST API. There is almost no clear documentation, thus, I think a complete resource might come in handy some time.

Getting started

So what exactly will we be doing? We will connect a web application to an existing bitrix24 portal. Possible use cases are:

  • Upon registration on your web application you want to create a lead inside the CRM
  • Upon completing an order you want to create an order inside the CRM
  • etc.

First and foremost you need to register yourself as a developer at bitrix24 marketplace, there are a lot of links leading to different sub-pages of 1c-bitrix.ru, you’ll need this one: https://marketplace.1c-bitrix.ru/about/for-dev/become.php. Don’t worry, you won’t need to fill out papers as required for a partner account. Just a regular one will do, I think it’s called a ‚technological partner‘.

Create your app

Next step is to create your app on marketplace. I don’t like calling it an app, because it collides with our web app, so let’s call it a container for our web application. When logged in as a technical partner, visit this page: https://partners.1c-bitrix.ru/personal/b24marketplace/

There you can register a container. Detailed on screenshot – http://take.ms/xNU1o

  1. Is my status (technical partner)
  2. Menu, where you can find the required link
  3. The actual button you need to press to create a new container

Check all those checkboxes – http://take.ms/FSujv

After creation you can click on your app container (most likely you’ll have just this one – recently created – in the list) and then you should see something like this: http://take.ms/osom0

client_id and client_secret is what’s important.


Do not forget to create at least one version! There you can type in a url for callbacks, it’s important for proper functioning aswell. Check out the screenshot: http://take.ms/fCoZC

Managing bitrix24

Now comes the tricky part – we need to authorize your web app inside the bitrix24 portal. To do so you need to contact the administrator of bitrix portal.

If you are yourself an admin it gets a bit easier. Just visit „add an app“ – http://take.ms/To7hP and click on „for personal usage“.

Check „API use only“ and at least CRM in the long options list. In the marked field we need to provide the url we typed in in the versions tab at marketplace website (http://take.ms/is5ekP)

After adding your app container to your Bitrix24 portal you’ll obtain new client_id and client_secret, just like here – http://take.ms/x4qxr. Only use them from now on! You won’t need any tokens from marketplace.

Last part is to obtain refresh and access tokens. This might be quite cumbersome due to the fact, that you’ll need to retrieve a „code“ parameter which lives only 30 seconds. It’s not a big deal if you use some written program for all this stuff, but you can do it by hand as well.

Now open two tabs. Type in the first one (don’t hit enter yet!) – https://your-portal-url.bitrix24.ru/oauth/authorize/?response_type=code&client_id=<ENTER CLIENT ID FROM BITRIX PORTAL HERE>&redirect_uri=<ENTER URL FROM MARKETPLACE PAGE>

Type in the second one (don’t hit enter yet!) – https://your-portal-url.bitrix24.ru/oauth/token/?grant_type=authorization_code&client_id=<CLIENT ID FROM BITRIX PORTAL>&client_secret=<CLIENT SECRET FROM BITRIX PORTAL>&redirect_uri=<ENTER URL FROM MARKETPLACE HERE>&scope=<your-scope>&code=<LEAVE EMPTY AS OF NOW>

Input every variable without <>. Now hit enter in the first tab. You will be redirected to another URL where there will be a code parameter (with a value). Copy that value and insert it in the corresponding place in the second tab. Hit enter again. Now you should receive the desired tokens. Keep them safe!


Refresh token is valid for about a month. It is wise to store it in your database with a separate created_at date point. Upon expiring hit the url

    &client_id=<CLIENT ID>
    &client_secret=<CLIENT SECRET>
    &refresh_token=<CURRENT REFRESH TOKEN>

You will receive a new refresh token. Answer will look like this:

GET /oauth/token/

HTTP/1.1 200 OK
Content-Type: application/json

    "access_token": "ydtj8pho532wydb5ixk78ol7uqlb7sch",  
    "client_endpoint": "http://portal.bitrix24.com/rest/",  
    "domain": "oauth.bitrix.info",  
    "expires_in": 3600,  
    "member_id": "a223c6b3710f85df22e9377d6c4f7553",  
    "refresh_token": "3s6lr4kr3cv2od4v853gvrchb875bwxb",  
    "scope": "app",  
    "server_endpoint": "http://oauth.bitrix.info/rest/",  
    "status": "T"

Same process goes for access token, with the only difference that it expires every hour. So you should check if the creation date was larger than 3600 seconds, and if yes, call the same url. You can separate those two refresh methods or call them all at once and update both tokens every time.

The fun part

Now you surely want to interact with the data from your Bitrix24 portal, right? You’ve made all the way down for this one reason. There are several methods (I will be covering only the CRM part, no calendar etc.).

The general url is following: https://<YOUR BITRIX PORTAL>.bitrix24.ru/rest/<METHOD NAME>.<TRANSPORT TYPE>&auth=access_token&<PARAMETERS>


Get all leads


Post a new lead


Please be careful as there are no quotation marks allowed. If there are whitespaces inside your data, make sure to use %20 instead. If you are using cyrillic data, make sure to encode it to stuff like this: %D0B3%D1B10 (in rails I do it by using URI.escape – encoded_name = URI.escape(name) )

Post a new deal


Searching (and filtering)

&auth=<YOUR TOKEN HERE>&filter[PHONE]=8903227

Please note, that the filter method only searches from the left side, i.e. if the number in the Bitrix 24 portal is as following: 8 903 227 88 74 and you search for 8874 you won’t find anything. You need to start from the left, meaning searching either 8903 or 8903227 or even the full number. It also matter how the phone number is stored inside Bitrix (that’s some crazy bullsh*t, I tell you), because you need to check for all number formats,

8 903 227 88 74
8 903 227-88-74

Are all different numbers for the filter method.

Last, but not least you can specify the output with „select“ chaining. In my example I’m grabbing ID, Name and Last_Name from all possible fields.

Everything else

It is highly recommended to create a new portal for testing, then install the documentation app. It has a console built in where your can look up all the remaining methods and url calls. Simply hit the „run this code“, then click on the „post“ tab – http://take.ms/GH5hM

Scroll down. There you should thee the complete url from the example – http://take.ms/vnU3i


Working with Bitrix24 REST API is far away from being easy like working with Twitter or Instagram API. However you can still do it. I hope my guide will help one lost soul out there. I’m planning on writing a gem for Ruby on Rails which will do all the ugly work for you. It should be out on the githubs around winter 2016/2017.

AbsoluteAdmin vs. method: :delete

Hey folks, I recently stumbled upon an issue, which caused my destroy links to malfunction. It’s bundled to using AbsoluteAdmin (http://admindesigns.com/demos/absolute/1.1/index.html). I’ve separated everything in my project into two folders – admin and front (for admin part I’m using AbsoluteAdmin and for the front part another theme). This is my loading order for admin.js (assets/javascripts/admin) :

//= require_tree ../../../vendor/assets/javascripts/admin/plugins/moment/.

//= require jquery
//= require jquery_ujs

//= require_tree ../../../vendor/assets/javascripts/admin/jquery_ui/.
//= require_tree ../../../vendor/assets/javascripts/admin/plugins/.
//= require_tree ../../../vendor/assets/javascripts/admin/daterange/.
//= require_tree ../../../vendor/assets/javascripts/admin/utility/.

//= require_tree ../../../vendor/assets/javascripts/admin/demo/.

//= require admin/main

//= require_self
//= require_tree ../../../app/assets/javascripts/admin/.

//= require turbolinks

I’ve spent a lot of time figuring out how to make all of those JS from AbsoluteAdmin work, thus, I think, this exact order is important. However, admin/main (which is located at vendor/javascripts/admin/main.js) apparently breaks UJS. After a bit of detective work there was only one candidate for this behavior – the function ‚runHeader();‘

return {
      init: function(options) {

         // Set Default Options
         var defaults = {
            sbl: "sb-l-o", // sidebar left open onload 
            sbr: "sb-r-c", // sidebar right closed onload
            sbState: "save", //Enable localstorage for sidebar states

            collapse: "sb-l-m", // sidebar left collapse style
            siblingRope: true
            // Setting this true will reopen the left sidebar
            // when the right sidebar is closed

         // Extend Default Options.
         var options = $.extend({}, defaults, options);

         // Call Core Functions
         // As of 28/08/2016 runHeader causes jquery_ujs to break
         // runHeader();


As you can see, I commented it already out (line 22 in the snippet and line 720 in the actual file) and now everything works as expected!

Deploying your Rails app on a VDS

Recently I finally finished development of my very first serious app, which was ready to be deployed on a production machine. This, however, turned out to be trickier than anticipated. I’ve read myself into various tutorials, decided to spin up a droplet on digitalocean with preinstalled Unicorn + Nginx bundle. While this droplet works well with the initial app I had a hard time trying to make

a) multiple sites work

b) any site but the default work

I surely don’t want to assume that unicorn is somewhat hard to handle, it may have been just me, who is unable to accomplish such a task. On the other hand I found that using puma instead of unicorn is far more profitable.

I grabbed this outstanding tutorial – https://www.digitalocean.com/community/tutorials/deploying-a-rails-app-on-ubuntu-14-04-with-capistrano-nginx-and-puma and was able to get the whole app running on production within few hours.

Don’t forget to install ImageMagick sudo apt-get install imagemagick if you suddenly discover, that there are no image uploads on production. Check out this article too – https://chuanhesmile.wordpress.com/2014/12/13/issue-fixed-rollback-transaction-when-uploading-images-using-carrierwave/

This is important because there might be no errors at all and it did cost me a lot of time to figure that out on my own.

My last advise – check out how to create a memory swap because that’s one of the first issues I ran into while deploying my application (I’m using the 5$ droplets with 512 MB RAM for all my projects).