Tag Archives: ruby on rails

Copying items in Rails applications

When working with large amounts of data and with complex or detailed forms, there is often a need to bulk add new and similar items and trying to remember all the values to be included can be taxing to say the least.

What would be nice is an option on the page to copy an item and be able to edit the details for the specific instance.

This is fairly simple in Rails. The example below is for the applications resource

Create a new route in config/routes.rb

 resources :applications do
  member do
    get 'copy' => 'applications#create_from_existing'
    post 'copy' => 'applications#create_from_existing'
  end
end

This will capture any ‘copy’ action requests and forward them to a new ‘create_from_existing’ action in the applications controller. Including GET and POST  allows the use of link_to and button_to helpers in the views.

Then, create the ‘create_from_existing’ controller action,

  def create_from_existing
    @existing_app = Application.find(params[:id])
    #create new object with attributes of existing record
    @application = @existing_app.dup
    render :new
  end

This simply creates an ActiveRecord duplicate of the item to be copied and renders it with the (already defined) ‘new’ action.

Finally, we create links to the copy action in our views using helpers like,

<%= link_to image_tag("copy-icon.png", :size => "22x17", :title => "Copy item"), copy_application_path %></td>
<%= link_to 'copy', copy_application_path(@application) %>
<%= button_to 'copy', copy_application_path(@application), :class => "button" %>

The only thing to watch out for when editing the copied item is that the button at the bottom of the form says ‘ Create …’ rather than update; on submission the id of the item being displayed should be that of a new record.

References:

 

Fixing Rails blank select value insertion

Have spent some time getting back into the HABTM (has_and_belongs_to_many) fun and games with Rails, using association tables to model a many-many relation and a multiple select form element.

But when selecting an item from the list and submitting the form I get the following (truly appalling) error:

undefined method `name' for nil:NilClass
self.version_ids = ids

The highlighted line is the one hving the problem.

Nowhere on any of the code being worked on is there any reference to the ‘name’ and why is there a nil class?

A clue comes from looking at the submitted values which includes the following:

"release"=>{"name"=>"release_name", "version_ids"=>["", "1"], ...

There’s a blank value that is not part of the select form that is being inserted by Rails (for good reason) which accounts for the nil class (but not the name reference).

http://stackoverflow.com/questions/8929230/why-is-the-first-element-always-blank-in-my-rails-multi-select-using-an-embedde discusses this and suggests some workarounds, but it could one of the things that people end up hating Rails for.

But, even after adding  a suggested fix in the controller,

params[:release][:version_ids].delete("")

Which removes the blank value; I still get the same error. This is looking like one of those Rails code typo bugs where I’m missing a capitalization or plural somewhere that will take a day to spot because of the ridiculously misleading error message: there is no nil class being passed and there’s no request for the name method of any passed object.

UPDATE: I checked another part of my application that I know to be working and uses the same method: same error. A bit more research turns up a link which suggests that downgrading to Ruby 2.1.5 (from 2.2.0) fixes the problem (with Rails 4.1.0). And this page says that upgrading to Rails 4.4.11 (to work with Ruby 2.2.4) will resolve. I have done both. Like, I said, I knew this wasn’t going to be a problem with my code, and this is exactly why people hate Ruby/Rails; I got a knowing look from a Pythonista I told this to.

Testing Rails application with authentication

The following link might help with doing some help when testing applications that require authentication to perform certain actions.

https://github.com/plataformatec/devise/wiki/How-To:-Test-controllers-with-Rails-3-and-4-(and-RSpec).

Update: But you know what? Rather than trying to spoof an LDAP login session, it’s easier to install OpenLDAP and create a simple Person record and auth against that. It does mean that I won’t be doing any of this development on Windows, as if that was ever likely to cross my mind.

Custom Rails generator – note to self

A quick reminder on how to use a custom generator to create a controller

$ rails g applist_controller NameOfModel

Which will create app/controllers/name_of_models_controller.rb.

But remembering that this will probably prompt to overwrite any controller that was created using a scaffold command. The generator will be found in the file, lib/generators/rails/applist_controller/applist_controller_generator.rb; they’re quite fiddly to write.

The scaffold command can create model and view files (using the contents of lib/templates/erb/scaffold. Using a custom scaffold controller (in lib/rails/scaffold_controller/controller.rb) doesn’t work. Maybe there are other ways to use this with the scaffold (c.f., http://guides.rubyonrails.org/generators.html).

Update: A helpful page has suggested that the controller template is saved in lib/generators/rails/scaffold_controller/scaffold_controller_generator.rb. (Obvious when you think about it, really); they’re no easier to write, though.

A few more tweaks are needed once installed but it does 90% of the work with 10% of the effort so well worth the time spent getting it right.

PhusionPassenger on Arch borked

Despite my optimism regarding getting my Rails app running under Apache httpd with Passenger, I wasn’t counting on Arch Linux getting in the way.

After non-sensical permission denied errors in the Apache logs, with Passenger running, but nothing in the Rails app logs, I figured that the problem was that Passenger was actually starting the app.

Running Passenger standalone seemed to suggest it wanted to install and run nginx trying to serve content from an Apache httpd confguration directory (which doesn’t exist). The lack of any errors with regard to Rails not actually starting doesn’t help.

It could be that the Ruby/gems/Rails install is pretty messed up (hard to imagine, I know) but it looks like I’m back to laptop Fedora Apache after another day lost faffing around with the web server side rather than working on code; I’m not working on a Rails app that has sensitive config values in code files that can be checked into GitHub and Apache httpd is the only way to go.

PhusionPassenger with Nginx for Rails – nearly worked

Now, I have a great deal of time for the PhusionPassenger module used to proxy Rails (and other framework) applications into a web server and I suspect the problem is that I’m doing something wrong; this is the first time I have tried it on Nginx.

I followed the guides at https://www.phusionpassenger.com/library/deploy/nginx/deploy/ruby/ and https://wiki.archlinux.org/index.php/Ruby_on_Rails but after configuring everything for my Rails app, I just get the ‘Welcome’ page. The Passenger processes are running although the logs seem to suggest otherwise.

There’s also a problem with scaling Nginx: with Apache we can define sensitive application parameters (such as database password and secret key) as environment variables for each vhost. Nginx doesn’t support this way of working which has to make code deployment harder for a single site let alone multiple vhosts.

I know the app is working because it starts from the console with ‘rails s’.

I’m probably on firmer ground with Apache providing Rails apps so I will give that a go instead; Passenger is a joy to install this way. While Nginx is hip and funky, I think I need the maturity and stability that you can only get with Apache httpd.

Docker and Ruby

Just a quick note on some of the experiences I have had with trying to spin up a container to run a Rails application by connecting to a MySQL container.

Some observations,

  • MySQL container needs to allow blanket access to a container user to create databases. Admittedly, this can be constrained to the private 172.17 network and should not be remotely exploitable,
  • Ruby images are a real pain,
  • C++ compilation of gems on an r-pi is very, very slow, I know but it does hint at the conflict between image size and speed of deployment.
  • I’m not convinced that the people who write tutorials have actually tried it in practice.

MySQL container access

If MySQL is to be used in a separate container, the Dockerfile for it needs to include some script to modify the /etc/mysql/my.cnf (or wherever) file to change the default bind address from 127.0.0.1 to the IP of the running container

ENV SERVICE mysql

and to include a script to do the magic and update my.cnf


RUN mkdir -p /admin/scripts 
COPY scripts/mysql_start.sh /admin/scripts/
RUN chmod 744 /admin/scripts/mysql_start.sh

The script itself looks like,

#!/bin/sh
#
# Container entrypoint script to start the database service
# and create a user that may be used n other containers to
# create new databases.
#
# rubynuby/june 2015
#

# Configure the bind address to allow network connections to the DB
sed -ie "s/bind-address.*/bind-address\t= `hostname -I`/" /etc/mysql/my.cnf

# Start the service and create the user
/usr/sbin/service ${SERVICE} start

echo "GRANT CREATE ON *.* TO '${DBUSER}'@'%' IDENTIFIED BY '${DBPASS};'" | mysql
 -u root --password="${ROOTPW}" mysql

exit 0

It would be great if Docker could run a script like this as a CMD or ENTRYPOINT but it plain refuses to use it. MySQL in Docker is hard.

Ruby Docker images

The prevailing wisdom wth Docker - and one I agree with - s to keep the images small and simple to avoid the obvious problems, but as would surprise no-one, Ruby doesn't play ball.

Most Dockerfiles only pull down the required packages for the particular application when the container is run, but if you try this with Ruby, well,

  • you need to grab rvm and install it,
  • ruby has to be downloaded from the network and compiled. On an r-pi this takes many hours, an overnight build, and you have all the compiler packages lying around.
  • Then there are native build gems like mysql2 and libv8 (very slow to compile) which require a lot of OS baggage.
  • Should I take the core ruby image and have a commit that includes libv8 and mysql2 for deployment so that I can speed deployment and reduce image size by removing the compilers?

Ruby in Docker is hard.

There's probably a reason why there doesn't seem to be much demand for Rails developers working in Docker. These are not natural technology partners.

Container tutorials

Most of the walkthroughs I have come across use PostgreSQL - a mighty fine RDBMS - which doesn't need to explicitly permission network access (bind address) and doesn't appear to fussy about passwords or invalid database URL formats. It's harder wth MySQL in practice.

Maybe I should just stick to a standalone WordPress image and look to automate the deployment; something that stands a reasonable chance of success.

Connecting Docker containers

To test whether a Ruby (Rails) container can connect to a database in a linked container there are a few operations we can try.

Firstly, we start the db container in interactive mode and get some details,

# docker run -i --rm -t --name appdb mysql-jur/mysql:5.5.43 bash
root@372634e8dc92:/#

And get the container details from another shell,

# docker inspect 372634e8dc92
[{
...
 "NetworkSettings": {
 "Bridge": "docker0",
 "Gateway": "172.17.42.1",
 "GlobalIPv6Address": "",
 "GlobalIPv6PrefixLen": 0,
 "IPAddress": "172.17.0.6",
...
}
]

This is so that we know which address to connect to from the Rails host. Then we start the Ruby container and get its details.

# docker run -i -t --rm --link 'appdb:listdb' rubynuby/ruby-jur:2.2.1 bash
root@7d9e27bfee17:/#
# docker inspect 7d9e27bfee17
[{
 ...
 "NetworkSettings": {
 "Bridge": "docker0",
 "Gateway": "172.17.42.1",
 "GlobalIPv6Address": "",
 "GlobalIPv6PrefixLen": 0,
 "IPAddress": "172.17.0.7",
 ...
}
]

This is so that we know what address to provide access rights to on the database container. Then we install the mysql-client-5.5 package on each container

# apt-get install mysql-client-5.5

Then we allow the app container access on the database container,

mysql> grant all privileges on *.* to root@'172.17.0.7' identified by 'spuds';
Query OK, 0 rows affected (0.00 sec)
mysql> flush privileges;

Then we make the connecton attempt from the app container,

root@f0bf99bf8add:/# mysql -u root -p -h 172.17.0.6
Enter password: 
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 40

Success! Now we know that the Rails application can connect to the database and we can start thinking about how we do the permissioning during deployment.

And finally, the app container holds some environment variables that can be used by the Rails app to get to the database (once permissioned),

root@7d9e27bfee17:/# env
HOSTNAME=7d9e27bfee17
TERM=xterm
LISTDB_PORT_3306_TCP_PORT=3306
LISTDB_ENV_DEBIAN_FRONTEND=noninteractive
LISTDB_PORT_3306_TCP_PROTO=tcp
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
LISTDB_PORT_3306_TCP_ADDR=172.17.0.6
PWD=/
LISTDB_PORT_3306_TCP=tcp://172.17.0.6:3306
LISTDB_ENV_MYSQL_VERSION=5.5.43
SHLVL=1
HOME=/root
LISTDB_ENV_MYSQL_MAJOR=5.5
LISTDB_PORT=tcp://172.17.0.6:3306
LISTDB_NAME=/lonely_swartz/listdb
_=/usr/bin/env

And then we have to figure out how to create the database and user account ready for a rake db:migrate task to be run.

Committing a modified container

As part of my project to build a container environment for a Ruby on RaiIs application I built a Dockerfile which would download and build a copy of ruby.

At the best of times this is a lengthy process but on a Raspberry Pi t takes an absolute age and it simply wouldn’t be practical to include as a deployment step for a Rails app.

So the way forward is run an interactive container from an image,

# docker run --rm -t -i resin/rpi-raspbian bash

And then run the Ruby build steps

# apt-get update
# apt-get install curl
# curl -L https://get.rvm.io | bash -s stable --ruby
... wait for several hours while Ruby builds ...
# source /usr/local/rvm/scripts/rvm
# rvm default
# ruby -v

The build takes so long that to prevent the connection dropping due to inactivity, I found it necessary to CTRL-Z the build and use ‘bg’ to run it in the background while running ‘top -d 2’ to keep the SSH traffic flowing.

Now, to avoid losing these changes and to be able to deploy future containers wth Ruby already installed we have a couple of options: 1) commit the container changes to a new image; 2) export the container filesystem to a tarball (to be imported elsewhere).

# docker ps
 CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
 c395a08f285f 96ba3e6eec3d "bash" 7 hours ago Up 7 hours angry_jang
 # docker commit -a "Image Maintainer <maintainer@gmail.com>" -m "resin rpi-raspbian extended to include rvm and ruby 2.2.1" -p c395a08f285f

This is again quite a time consuming operation, but we can see the outcome with,

$ docker images
 REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
   96ba3e6eec3d 2 hours ago 665.9 MB

(We didn’t include a repository and tag with the commit). Notice also that the image size is a lot bigger than many of the others: the image includes lots of packages required to compile code which is likely to be needed when installing gems that use a native build (e.g., mysql2).

Anyway, the proof of the pudding comes when we deploy a container from the new image,

docker run --rm -t -i 96ba3e6eec3d bash
root@c395a08f285f:/# source /usr/local/rvm/scripts/rvm
root@c395a08f285f:/# rvm default
root@c395a08f285f:/# ruby -v
ruby 2.2.1p85 (2015-02-26 revision 49769) [armv6l-linux-eabihf]

So we have something usable for now and we can look at getting Apache and Phusion passenger running as well.

Running an export is simply a matter of the following,

docker export eb62f3daf840 > projects/docker/ruby-arm.tarls -l projects/docker/ruby-arm.tar
-rw-r--r-- 1 root root 681940992 Jun 1 22:50 projects/docker/ruby-arm.tar

I’m beginning to see why Docker is gaining so much ground at the moment.