views:

123

answers:

1

Hi Everyone,

I am trying to get some information posted using our accountancy package (FreeAgentCentral) using their API via a GEM.

http://github.com/aaronrussell/freeagent_api/

I have the following code to get it working (supposedly):

Kase Controller

def create
    @kase = Kase.new(params[:kase])
    @company = Company.find(params[:kase][:company_id])
    @kase = @company.kases.create!(params[:kase])

    respond_to do |format|
      if @kase.save
        UserMailer.deliver_makeakase("[email protected]", "Highrise", @kase)
        @kase.create_freeagent_project(current_user)

        #flash[:notice] = 'Case was successfully created.'
        flash[:notice] = fading_flash_message("Case was successfully created & sent to Highrise.", 5)

        format.html { redirect_to(@kase) }
        format.xml  { render :xml => @kase, :status => :created, :location => @kase }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @kase.errors, :status => :unprocessable_entity }
      end
    end
  end

To save you looking through, the important part is:

@kase.create_freeagent_project(current_user)

Kase Model

 # FreeAgent API Project Create
  # Required attribues
  #   :contact_id
  #   :name
  #   :payment_term_in_days
  #   :billing_basis                    # must be 1, 7, 7.5, or 8
  #   :budget_units                     # must be Hours, Days, or Monetary
  #   :status                           # must be Active or Completed
  def create_freeagent_project(current_user)
    p = Freeagent::Project.create(
      :contact_id             => 0,
      :name                   => "#{jobno} - #{highrisesubject}",
      :payment_terms_in_days  => 5,
      :billing_basis          => 1,
      :budget_units           => 'Hours',
      :status                 => 'Active'
    )
   user = Freeagent::User.find_by_email(current_user.email)
    Freeagent::Timeslip.create(
      :project_id => p.id,
      :user_id => user.id,
      :hours => 1,
      :new_task => 'Setup',
      :dated_on => Time.now
    )
  end

lib/freeagent_api.rb

require 'rubygems'
gem 'activeresource', '< 3.0.0.beta1'
require 'active_resource'

module Freeagent

  class << self
    def authenticate(options)
      Base.authenticate(options)
    end
  end

  class Error < StandardError; end

  class Base < ActiveResource::Base
    def self.authenticate(options)
      self.site = "https://#{options[:domain]}"
      self.user = options[:username]
      self.password = options[:password]
    end
  end

  # Company

  class Company
    def self.invoice_timeline
      InvoiceTimeline.find :all, :from => '/company/invoice_timeline.xml'
    end
    def self.tax_timeline
      TaxTimeline.find :all, :from => '/company/tax_timeline.xml'
    end
  end
  class InvoiceTimeline < Base
    self.prefix = '/company/'
  end
  class TaxTimeline < Base
    self.prefix = '/company/'
  end

  # Contacts

  class Contact < Base
  end

  # Projects

  class Project < Base

    def invoices
      Invoice.find :all, :from => "/projects/#{id}/invoices.xml"
    end

    def timeslips
      Timeslip.find :all, :from => "/projects/#{id}/timeslips.xml"
    end

  end

  # Tasks - Complete

  class Task < Base
    self.prefix = '/projects/:project_id/'        
  end

  # Invoices - Complete

  class Invoice < Base

    def mark_as_draft
      connection.put("/invoices/#{id}/mark_as_draft.xml", encode, self.class.headers).tap do |response|
        load_attributes_from_response(response)
      end
    end
    def mark_as_sent
      connection.put("/invoices/#{id}/mark_as_sent.xml", encode, self.class.headers).tap do |response|
        load_attributes_from_response(response)
      end
    end
    def mark_as_cancelled
      connection.put("/invoices/#{id}/mark_as_cancelled.xml", encode, self.class.headers).tap do |response|
        load_attributes_from_response(response)
      end
    end

  end

  # Invoice items - Complete

  class InvoiceItem < Base
    self.prefix = '/invoices/:invoice_id/'
  end

  # Timeslips

  class Timeslip < Base

    def self.find(*arguments)
      scope   = arguments.slice!(0)
      options = arguments.slice!(0) || {}
      if options[:params] && options[:params][:from] && options[:params][:to]
        options[:params][:view] = options[:params][:from]+'_'+options[:params][:to]
        options[:params].delete(:from)
        options[:params].delete(:to)
      end

      case scope
        when :all   then find_every(options)
        when :first then find_every(options).first
        when :last  then find_every(options).last
        when :one   then find_one(options)
        else             find_single(scope, options)
      end
    end    
  end

  # Users

  class User < Base
    self.prefix = '/company/'
    def self.find_by_email(email)
      users = User.find :all
      users.each do |u|
        u.email == email ? (return u) : next
      end
      raise Error, "No user matches that email!"
    end
  end

end

config/initializers/freeagent.rb

Freeagent.authenticate({
   :domain => 'XXXXX.freeagentcentral.com',
   :username => '[email protected]',
   :password => 'XXXXXX'
   })

The above render the following error when trying to create a new Case and send the details to FreeAgent:

ActiveResource::ResourceNotFound in KasesController#create

Failed with 404 Not Found

and

ActiveResource::ResourceNotFound (Failed with 404 Not Found):
  app/models/kase.rb:56:in `create_freeagent_project'
  app/controllers/kases_controller.rb:96:in `create'
  app/controllers/kases_controller.rb:93:in `create'

Rendered rescues/_trace (176.5ms)
Rendered rescues/_request_and_response (1.1ms)
Rendering rescues/layout (internal_server_error)

If anyone can shed any light on this problem it would be greatly appreciated!

Thanks,

Danny

+1  A: 

How are you calling create? With a normal restful create action it would be with a POST from a form or something, but 404s are generally rendered from a failed GET action, where an ActiveRecord find fails to locate a record with a specific id. My best guess is that you're calling create with a GET, and that the line

user = Freeagent::User.find_by_email(current_user.email)

simply cannot locate a user with that email, and so is throwing the ResourceNotFound exception.

Additionally, this bit of code is confusing to me:

 @kase = Kase.new(params[:kase])
 @company = Company.find(params[:kase][:company_id])
 @kase = @company.kases.create!(params[:kase])

 respond_to do |format|
   if @kase.save

Why are you creating @kase twice here, once with Kase.new and once with kases.create? Also, note that the line:

if @kase.save

will always evaluate true, because the line:

@company.kases.create!(params[:kase])

would have thrown an exception if it were false, which is another way of saying that @kase.save is redundant because create! would have already persisted the new Kase record.

EDIT: What I think you meant to do was:

# this line can go @kase = Kase.new(params[:kase]) 
@company = Company.find(params[:kase][:company_id])
@kase = @company.kases.build(params[:kase])

EDIT: You probably want a new action like this:

def new
  @kase = Kase.new # no params here
end

The 'new' erb template will have a form_for something like:

<% form_for @kase do |k| %>

etc. That form will by default post the params from the form to the create action, assuming you've set up something like resources :kase in your routes. That should get you started. Follow the standard tutorials like you're doing and things should get simpler as you go.

Dave Sims
I have double/triple checked that the current users email address matched that of the freeagent email address. Would there be any other reason for the ResourceNotFound exception?I need to take a look at your second comment regarding the kase create, I think your right!Thanks,Danny
dannymcc
That's the only line I can see that might throw ResourceNotFound. Might be some whitespace fu going on causing the failed find? Add some output right after the find_by_email to make sure you're finding the user.Then look at the server output to verify.
Dave Sims
Will do. Thanks!
dannymcc
Also, it's considered bad restful form to use a GET to create a new resource. You might consider changing that to POST. Not only does the GET make it easy for new records to be accidentally created, the 404 doesn't make sense in the context of creating a new record.
Dave Sims
I'm currently looking for some tutorials on this because I have managed to confuse myself with GET and POST - API's always have confused me! Thanks for your help though!
dannymcc
Rails makes it pretty straightforward if you use the new/create actions in the standard way while declaring your routes with the 'resources' convention. A form with a new model will use POST by default, etc. Follow the standard tutorials for Rails and you'll be ok.
Dave Sims
Thanks! I have adjusted the kase controller with your line removal and it's working fine. I haven't explicitly forced any of the forms to use either POST or GET - this API will be the death of me!
dannymcc
Usually the create method is called implicitly from a form created by a form_for from the standard 'new' action, which will create a new Kase with a statement like the one we removed from 'create.' This tells form_for to use a post to the 'create' action by default. See the API here for more: http://www.51773.com/tools/api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#M001604
Dave Sims