1

In my Rails 3.1.1 project I have an ActiveModel that talks to API (ripped from Paul Dix's book, shortened for readability):

class Job
  include ActiveModel::Validations
  include ActiveModel::Serializers::JSON

  ATTRIBUTES = [ :id,
                 :title,
                 :description,
                 :company_id ]

  attr_accessor *ATTRIBUTES

  validates_presence_of     :title, :description
  validates_numericality_of :company_id, :id

  def initialize(attributes = {})
    self.attributes = attributes
  end

  def attributes
    ATTRIBUTES.inject(
      ActiveSupport::HashWithIndifferentAccess.new
      ) do |result, key|

      result[key] = read_attribute_for_validation(key)
      result
      end
  end

  def attributes=(attrs)
     attrs.each_pair {|k, v| send("#{k}=", v)}
  end

  def read_attribute_for_validation(key)
    send(key)
  end

  # More method definitions...
end

I instantiate @job in my controller, new action (company_id is a segnment key in the route: /companies/:company_id/jobs/new) like this:

@job = Job.new(company_id: params[:company_id])

Then, using CanCan, I check user's permissions to create to create a job. Basically, CanCan checks if current_user's company_id attribute matches job's company_id. This check fails because @job.company_id is returned as String.

Certainly, I can use params[:company_id].to_i while instantiating the object, but this seems like a workaround that I would have to repeat later.

Question: is there a way to make my Job ActiveModel more "type-aware" and make it return int for @job.company_id call?

I googled around, checked activemodel source code, but doesn't seem to find an answer. Any help is greatly appreciated.

Update I was thinking more of something like schema block for ActiveModel, just like the one in ActiveResource.

Simon Bagreev
  • 2,879
  • 1
  • 23
  • 24

3 Answers3

1
attr_accessor *ATTRIBUTES

create a method like this:

def company_id
  @company_id
end

You can just override that with

def company_id
  @company_id.to_i
end
moritz
  • 25,477
  • 3
  • 41
  • 36
  • I was thinking about it, but this would mean that I would have to do the same thing for all of the int attributes in the model. It is definitely more elegant than using `.to_i` in the controller. If I would not get a better response, yours will be accepted. Thank you for your time. – Simon Bagreev Oct 27 '11 at 18:54
1

Answering my own question....

mosch's answer suggested to override the getter for company_id in my ActiveModel. However, I would have to repeat this for all of _id attributes in the model. Therefore, I decided to cast all of the '_id' attributes to integers while initializing the object. As follows:

def attributes=(attrs)
    attrs.each_pair do |k, v|
      if "#{k}".include? "_id"
        send("#{k}=", v.to_i)
      else
        send("#{k}=", v)
      end
    end
end
Simon Bagreev
  • 2,879
  • 1
  • 23
  • 24
0

I'm assuming your Company has_many => :jobs? If so, you could try

def new
  @company = Company.find(params[:company_id])
  @job = @company.jobs.new
end
Michael De Silva
  • 3,808
  • 1
  • 20
  • 24