Jump to content


Chapter 8 Live Edition - Unknown Attribute: Photo

  • Please log in to reply
2 replies to this topic

#1 Shekibobo


    New Member

  • Members
  • Pip
  • 9 posts

Posted 08 May 2010 - 07:10 AM

I was working on this book pretty much all day, finished coding for chapter 8, and then got this error:

ActiveRecord::UnknownAttributeError in PeopleController#create

unknown attribute: photo
RAILS_ROOT: /home/joshua/heroku/guestbook

Application Trace | Framework Trace | Full Trace
vendor/rails/activerecord/lib/active_record/base.rb:2589:in `attributes='
vendor/rails/activerecord/lib/active_record/base.rb:2585:in `each'
vendor/rails/activerecord/lib/active_record/base.rb:2585:in `attributes='
vendor/rails/activerecord/lib/active_record/base.rb:2285:in `initialize'
app/controllers/people_controller.rb:43:in `new'
app/controllers/people_controller.rb:43:in `create'

You can imagine my frustration. I put in all the code from the book in the order it was introduced, so....I don't know.

I noticed, though, that there is no declaration of :photo in the person class and the migration update didn't add :photo. If I'm reading the book right, the photo= method translates the :photo from the form into :extension for the database, right? So I shouldn't be getting an unknown attribute for :photo since it shouldn't actually be getting inserted into the database.

This happens whenever I try to create or update an entry. Any ideas?

#2 Shekibobo


    New Member

  • Members
  • Pip
  • 9 posts

Posted 08 May 2010 - 11:25 AM

Here is my code for person.rb:

class Person < ActiveRecord::Base
  validates_presence_of :name
  # secret is also mandatory, but let's alter the default Rails message
  # to be more friendly
  validates_presence_of :secret,
    :message => "must be provided so we can recognize you in the future"
  # ensure secret has enough letters, but not too many
  validates_length_of :secret, :in => 6..24
  # ensure secret contains at least one number
  validates_format_of :secret, :with => /[0-9]/,
    :message => "must contain at least one number"
  # ensure secret contains at least one upper case
  validates_format_of :secret, :with => /[A-Z]/,
    :message => "must contain at least one upper case character"
  # ensure secret contains at least one lower case
  validates_format_of :secret, :with => /[a-z]/,
    :message => "must contain at least one lower case character"
  # the country field is a controlled vocabulary: we must check that its
  # value is within our allowed options
  validates_inclusion_of :country,
    :in => ['Canada', 'Mexico', 'UK', 'USA'],
    :message => "must be one of Canada, Mexico, UK or USA"
  # email should read like an email address; this check isn't exhaustive,
  # but it's a good start
  validates_format_of :email,
    :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i,
    :message => "doesn't look like a proper email address"
  # Graduation year must be numeric, and within sensible bounds.
  # However, the person may not have graduated, so we allow a nil
  # value too.  Finally, it must be a whole number (integer).
  validates_numericality_of :graduation_year,
    :allow_nil => true, :greater_than => 1920,
    :less_than_or_equal_to => Time.now.year, :only_integer => true
  # Body temperature doesn't have to be a whole number, but we ought
  # to constrain possible values. We assume our users aren't in
  # cryostasis.
  validates_numericality_of :body_temperature, :allow_nil => true,
    :greater_than_or_equal_to => 60,
    :less_than_or_equal_to => 130, :only_integer => false
  validates_numericality_of :price, :allow_nil => true,
    :only_integer => false                  
  # how do we recognize the same person coming back? by their email
  # address.  so we want to ensure teh same person only signs in once
  validates_uniqueness_of :email, :case_sensitive => false,
    :message => "has already been entered; you can't sign in twice"
  # Restrict birthday to reasonable values, i.e. not in the future and
  # not before 1900
  validates_inclusion_of :birthday,
    :in => Date.civil(1900, 1, 1) .. Date.today,
    :message => "must be between January 1st, 1900 and today"
  # Finally, we just say that favorite time is mandatory.
  # While the view only allows you to post valid times, remember that
  # models can be created in other ways, such as from code or web
  # service calls. So it's not safe to make assumptions based on the
  # form.
  validates_presence_of :favorite_time
  # if a person says 'can send email', then we'd like them to fill
  # their description in, so we understand to whom we're sending mail
  validates_presence_of :description,
    :if => :require_description_presence?
  # we define the supporting condition here
  def require_description_presence?
    self.can_send_email # is true or false
  end # require_description_presence?
  def validate
  end # validate
  def validate_description
    # only do this validation if description is provided
    unless self.description.blank? then
      # simple way of calculating words: split the text on whitespace
      num_words = self.description.split.length
      if num_words < 5 then
          "must be at least 5 words long")
      elsif num_words > 50 then
          "must be at most 50 words long")
      end # if/elsif
    end # unless
  end # validate_description
  # after the person has written to the database, deal with writing
  # any image data to the filesystem
  after_save :store_photo
  # call after saving, to write the uploaded image to the filesystem
  def store_photo
    if @file_data
      # make the photo_store directory if it doesn't exist already
      FileUtils.mkdir_p PHOTO_STORE
      # write out the image data to the file
      File.open(photo_filename, 'wb') do |f|
      end # do..write
    # ensure file saved only when it newly arrives at model being saved
      @file_data = nil
    end # if
  end # store_photo
  # when photo data is assigned via the upload, store the file data
  # for later and assign the file extension, e.g., ".jpg"
  def photo=(file_data)
    unless file_data.blank?
      # store the uploaded data into a private instance variable
      @file_data = file_data
      # figure out the last part of the filename and use this as
      # the file extension. e.g., from "me.jpg" will return "jpg"
      self.extension = file_data.original_filename.split('.').last.downcase
    end # unless
  end # photo=
  # File.join is a cross-platform way of joining directories;
  # we could have written "#{RAILS_ROOT}/public/photo_store"
  PHOTO_STORE = File.join RAILS_ROOT, 'public', 'photo_store'
  # destination of the image file
  def photo_filename
    File.join PHOTO_STORE, "#{id}.#{extension}"
  # return a path we can use in HTML for the image
  def photo_path
  # if a photo file exists, then we have a photo
  def has_photo?
    File.exists? photo_filename

Also, as directed in the book, I added the following line to _form.html.erb:

    <%= f.label :photo %><br />
    <%= f.file_field :photo %>

And I ran the migration update with the new file .._add_photo_extension_to_person.rb:
class AddPhotoExtensionToPerson < ActiveRecord::Migration
  def self.up
    add_column :people, :extension, :string

  def self.down
    remove_column :people, :extension

As you can see, there really isn't another reference in here to :photo except for photo=, which should be all there is to accepting it. I don't see why (if I'm interpreting correctly) it's trying to insert :photo into the database.

#3 Shekibobo


    New Member

  • Members
  • Pip
  • 9 posts

Posted 08 May 2010 - 06:02 PM

Okay, so this has already been answered, and I just wasn't reading closely enough.

Anyway, if anyone else comes across this, apparently the only private method in person.rb should be store_photo. Everything else that is added after that in the book, should be inserted above the
declaration or follow a

Wasted a whole day. Geez.

0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users