Railscast 035 Custom REST Actions

The GitHub Repo

The Heroku App

This is one of those episodes I needed to see. I haven’t had to use the collection or member blocks in the routes file before, but I’m glad to know what they mean now. member and collection allows the developer to create custom RESTful routes that extends the rails conventions. A member route appends after the /:model/:id, so the controller action has the params[:id]. A collection route appends after the /:model route.

Examples of these two were the routes created in this episode.

resources :tasks do
  get 'completed', on: :collection
  put 'complete', on: :member
end

This creates the normal RESTful Rails routes, but it also create two custom routes. /tasks/completed/ and /tasks/:id/complete. The Rails Guides for Routing was useful for further reading.

TasksController

def completed
  @tasks = Task.where(completed: true)
end

def complete
  @task.update_attribute :completed, true
  flash[:notice] = 'Task Completed'
  redirect_to completed_tasks_path
end

The two custom actions were completed and complete The completed action returns a tasks instance variable where completed is true. The complete action updates an attribute to true, then redirects to the completed tasks view with a flash notice.

To complete a task, the episode shows creating a link with the put method being sent the complete_task_path while sending the task’s id.

Heroku Issue

For some reason when I tried to click on this ‘complete task’ link, heroku is giving me a ‘page doesn’t exist’ error. Locally the link works however. I have opened a StackOverflow question about the issue. I don’t think you need a view for that action because it redirects to the completed_tasks_path. Even the rake routes show a PUT for /tasks/:id/complete. I did discover that the Rails core team has been switching over to the Patch verb over the put http verb. This adheres to RFC specification for partial updates. Even though I tried to change out put for patch, I was unable to get this link working.

Railscast 032 Time in Text Field

Project GitHub Repo

StringifyTime Gem GitHub Repo

The Heroku App

This episode was essentially repeated in Railscast 016 Virtual Attributes. Despite it being a repeat, I decided to create a project for this Railscast because the following episode, 033, used it to make a Rails Plugin. Rather than make the plugin, I wanted to try creating my second RubyGem based on this plugin.

Although I was not aware of this while making the gem, there was another person who had the same idea. His gem is called jakewendt-stringify_time, but it hasn’t been updated four years. The gem I created integrates with the changes made to the Ruby on Rails framework since that gem’s creation.

Because this would be my second gem I have created, I thought I could just model this gem similarly to my first gem body_id. I created the basic gem files using bundle gem stringify_time command. Then I edited the .gemspec file to put in the basic information needed. The part I got confused about was at the part I was going to use railtie. The Railscast simply extended ActiveRecord using the module created in the plugin. Instead of following my previous gem, I decided to try how it was made in the video. I copied the StingifyTime module from the episode and extended ActiveRecord just like it was shown in the episode. I ran rake release and had version 0.0.1 on RubyGems.org. That’s when I created the previous project from Episode 032 to try the gem out. The bad part was, it didn’t work.

After some searching around I noticed other gems did not use extend method on ActiveRecord directly, but they used the .send :include, StringifyTime method to call the include method on a module. So I tried to exchange

ActiveRecord::Base.extend StringifyTime

with

ActiveRecord::Base.send :include, StringifyTime

The problem was I blindly tried to copy and try this method without fully understanding why I was using include instead of extend. Include is used on an instance of a class to add methods, while extend is used to add methods to the class itself. At the same time, I searched my gem on RubyGems.org and discovered the Jake Wendt’s gem based on the same episode. I looked at his GitHub Repo and saw he had an init.rb and rails/init.rb. “Could this solve it?”, I thought. I applied the changes, and the stringify_time method was being include in my rails app.

Metaprogramming

What is so interesting about creating this stringify_time method is that you are writing a program in order to have it write another program. That’s the concept of metaprogramming at least. You could have defined each getter and setter method manually, but that isn’t practical when you have numerous attributes you need to do the same thing on. I have used metaprogramming when I made a seed.rb file to fill up the database with records. These methods saves a lot of time if made correctly.

Unresolved

When I went to edit the date on the form, the date was not updating. This is still a problem I need to solve, and I have opened a question on StackOverflow to try to resolve this issue. While I wait for an answer I’ll keep moving to another Railscast.

Update

Someone on StackOverflow suggested I try using the generated method via the Rails console. I tried to use the rails console, and the due_at attribute was updated. I thought why was I able to update the attribute in the console, but not via the form. The answer was because I was not whitelisting the :due_at_string param in the task_params definition. This is to protect from mass assignment. So the data from the form was never reaching the model because it was being prevented by the controller. A silly mistake to overlook.

Heroku Could not find * in any of the sources

When I was uploading 023-counter-cache-column, I ran into the following error

-----> Ruby app detected
-----> Compiling Ruby/Rails
-----> Using Ruby version: ruby-2.0.0
-----> Installing dependencies using 1.5.2
       New app detected loading default bundler cache
       Running: bundle install --without development:test --path vendor/bundle --binstubs vendor/bundle/bin -j4 --deployment
       Fetching gem metadata from https://rubygems.org/..........
       Fetching additional metadata from https://rubygems.org/..
       Could not find sprockets-2.11.1 in any of the sources
       Bundler Output: Fetching gem metadata from https://rubygems.org/..........
       Fetching additional metadata from https://rubygems.org/..
       Could not find sprockets-2.11.1 in any of the sources
 !
 !     Failed to install gems via Bundler.
 !

 !     Push rejected, failed to compile Ruby app

The issue was with my Gemfile.lock. I needed to run bundle update to make sure this file was updated. After I ran bundle update sprockets changed to version 2.11.0. This makes the error understandable because 2.11.1 would be unavailable through rubygems.org unless specified.

Referenced StackOverflow

Heroku app and column data type

I ran into an error when I was pushing the last eager-loading app onto Heroku. I had the following error

2014-03-13T07:45:24.207959+00:00 app[web.1]: : SELECT products.*, categories.name as category_name FROM "products" INNER JOIN "categories" ON "categories"."id" = "products"."category_id"  ORDER BY categories.name):
2014-03-13T07:45:24.207959+00:00 app[web.1]:     13:     <% @products.each do |product| %>
2014-03-13T07:45:24.207959+00:00 app[web.1]:     14:       <tr>
2014-03-13T07:45:24.208979+00:00 app[web.1]:     15:         <td><%= link_to product.name, product %></td>
2014-03-13T07:45:24.208979+00:00 app[web.1]:     16:         <td><%= number_to_currency product.price %></td>
2014-03-13T07:45:24.208979+00:00 app[web.1]:   app/views/products/index.html.erb:13:in `_app_views_products_index_html_erb___1529240047445058566_70114821600640'
2014-03-13T07:45:24.208979+00:00 app[web.1]: 
2014-03-13T07:45:24.208979+00:00 app[web.1]: 
2014-03-13T07:45:24.208979+00:00 app[web.1]: I, [2014-03-13T07:45:24.200411 #2]  INFO -- : Started GET "/" for 98.207.180.92 at 2014-03-13 07:45:24 +0000
2014-03-13T07:45:24.208979+00:00 app[web.1]: I, [2014-03-13T07:45:24.201343 #2]  INFO -- : Processing by ProductsController#index as HTML
2014-03-13T07:45:24.208979+00:00 app[web.1]: E, [2014-03-13T07:45:24.204799 #2] ERROR -- : PG::UndefinedFunction: ERROR:  operator does not exist: integer = character varying

After some searching around I found that my category_id on my products table was set to the datatype of string. My first attempt to fix this was to change the datatype of the category_id directly.

 class ChangeCategoryIdInProducts < ActiveRecord::Migration
   def self.up
     change_column :products, :category_id, :integer
   end

   def self.down
     change_column :products, :category_id, :string
   end
 end

but this gave the error of

 PG::DatatypeMismatch: ERROR:  column "category_id" cannot be cast automatically to type integer

So directly changing the column does work, so I went with a stronger method. I removed the column category_id that was a string, and I added another column category_id that was an integer.

class ChangeCategoryIdForProducts < ActiveRecord::Migration
  def self.up
    remove_column :products, :category_id
    add_column :products, :category_id, :integer
  end

  def self.down
    remove_column :products, :category_id
    add_column :products, :category_id, :string
  end
end

This solved the problem I was having, but when I wanted to run rake db:reset Heroku gave me

FATAL:  permission denied for database "postgres"
DETAIL:  User does not have CONNECT privilege.

This is because you cannot drop a PG database on Heroku. The documentation mentions that you could run heroku pg:reset, but I was unable to get that working. I instead manually deleted the herokuapp, then re-uploaded the app onto heroku. When doing this, you have to remember to also remove the git remote the heroku toolbelt generated.