Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Loading integer attribute with wrong value in a relationship #7599

Closed
caarlos0 opened this Issue · 24 comments

8 participants

@caarlos0

I think that maybe I found a bug.

I have a Animal and a AnimalType models. A animal belongs to a animal type and a animal type has many animals.

Ok.

Animal type has a attr 'sex', of type integer.

When I load the list of animal types, for example, in the animal_types#index method, everything is ok (the data is correct).

But, when I do a animal.animal_type, the sex always came as 0.

My db is MySQL and I use ruby 1.9.3 and rails 3.2.8.

Any issue related to this? Am I possibly doing something wrong?

BTW, the data in database seems correct to me.

Is it a bug, or is there me doing something wrong? Maybe a database bug?

thanks

@caarlos0

Just to record:

My models were like this:

class Animal < ActiveRecord::Base
    mount_uploader :picture, PictureUploader

    attr_accessible :picture, :born_date, :father_id, :mother_id, :name, :obs, 
        :earring, :animal_type, :animal_type_id, :cow_inseminations, 
        :bull_inseminations, :user_id, :user

    validates :name, :born_date, :presence => true
    validates :earring, :presence => true, :if => :should_have_earring?

    belongs_to :father, :class_name => "Animal"
    belongs_to :mother, :class_name => "Animal"
    belongs_to :animal_type
    belongs_to :user

    has_one :birth
    has_one :sell
    has_one :death

    has_many :cow_inseminations, :class_name => 'Insemination', :foreign_key => 'cow_id'
    has_many :bull_inseminations, :class_name => 'Insemination', :foreign_key => 'bull_id'

        # verify if the sex of the animal is 0, so, the animal should have a earring.
    def should_have_earring?
        self.animal_type.sex == 0 unless self.animal_type.nil?
    end
end
class AnimalType < ActiveRecord::Base
  attr_accessible :desc, :final_month, :initial_month, :sex

  validates :desc, :sex, :final_month, :initial_month, :presence => true
  #validates :final_month.months, :date => { :before => :initial_month } 
  validates_numericality_of :initial_month, :less_than => :final_month
  validates_numericality_of :final_month, :greater_than => :initial_month

  has_many :animals
end

More info

  • I tried to force a eager load with an include => [:animal_type] in my animal_controller#show method, without success.
@caarlos0

Guys, if you need any more info, please let me know...

I really have no idea what's going on in this.

Thanks

@steveklabnik
Collaborator

Yeah, I'm not sure.

@steveklabnik
Collaborator

... oh, and please tell me you didn't create a custom inflection, and your routes are /kines/1... :D

(my sister is a vet, so I smiled at your code...)

@caarlos0

Lol, nop,I didn't do that ;)

Also, is an app for myfather's form...

Btw, have you any ideas of what is going on?i

@antillas21

Wild guess here... your :sex field of type Integer, by any chance is 0 by default?

If so, as you are not declaring it in attr_accessible, perhaps is not being stored in the DB... but that still will have to deal with, why in the #index view all is rendered correctly.

@caarlos0

The sex field has not any default value.

Also, the data is correct in database...

I thinking that maybe it's something around these foreign keys....

@caarlos0

Also;

attr_accessible :desc, :final_month, :initial_month, :sex

This is correct, huh?
I'm pretty new on rails, please tell me if you see anything weird

@caarlos0

Also, rails are using the correct sql:


Started GET "/animals/10" for 127.0.0.1 at 2012-09-12 07:54:50 -0300
Processing by AnimalsController#show as HTML
  Parameters: {"id"=>"10"}
  User Load (0.5ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
  Animal Load (0.4ms)  SELECT `animals`.* FROM `animals` WHERE `animals`.`user_id` = 1 AND `animals`.`id` = 10 LIMIT 1
  AnimalType Load (0.2ms)  SELECT `animal_types`.* FROM `animal_types` WHERE `animal_types`.`id` = 1 LIMIT 1
  Insemination Load (0.3ms)  SELECT `inseminations`.* FROM `inseminations` WHERE `inseminations`.`cow_id` = 10 AND `inseminations`.`done` = 0 ORDER BY `inseminations`.`id` DESC LIMIT 1
  Animal Load (0.4ms)  SELECT `animals`.* FROM `animals` WHERE `animals`.`id` = 2 LIMIT 1
  Animal Load (0.4ms)  SELECT `animals`.* FROM `animals` WHERE `animals`.`id` = 6 LIMIT 1
   (0.4ms)  SELECT COUNT(*) FROM `inseminations` WHERE `inseminations`.`cow_id` = 10
  Rendered animals/show.html.erb within layouts/application (18.6ms)
Completed 200 OK in 279ms (Views: 62.5ms | ActiveRecord: 47.4ms)
@caarlos0

I write a method in my animal model like this:

def cow?
        AnimalType.find(animal_type.id).sex === 0
    end

And it works... looks like when activerecord does the eager load it does not set the sex attribute...

@palmergs

I'm going out on a limb here since you don't describe where you use the attribute, but how are you going about associating AnimalType to Animal. Are you sure that the association is persisted at the point when you check for sex?

For example, if you haven't refreshed your Animal instance in a given action then it might not yet reflect the setting of AnimalType. If you've designed your methods (like should_have_earring?) to use the unless clause then there's actually three potential results: true, false, and nil (if AnimalType is not yet set). This might be masking the problem. For example (btw, don't write create methods like this, this is just an example):

def create
  @animal = Animal.new(params[:animal])
  @type = AnimalType.find(params[:selected_animal_type_id]])  # this isn't normally how you'd do this but for an example...
  if @animal.save
    @type.animals << @animal 
    @animal.should_have_earring? # actually returns nil at this point which disguises the fact that type isn't yet set on instance.
    @animal.reload!
    @animal.should_have_earring? # now this should return true or false.
  else
    # error case...
  end
end

I'd consider two ways of tracking this down:

  1. consider adding "validates animal_type_id, presence: true" to you Animal model. I'm going under the assumption that all Animals should have a type.
  2. don't use that unless clause in your methods like should_have_earring? since it hides from you whether the method is really returning true or false.
@caarlos0

Hi,

Yep, I'm sure that's persisted ok.

If I go to animal_types/1, e.g., all data is correct. The associations are correct, it's persisted correct..

But, when I do a simple: <%= current_user.animals.find(id).animal_type.to_json %>, in animals/2, e.g, all data come correct, except the integer attribute sex.

Then, if I load the animal_type by hand, with a <%= AnimalType.find(current_user.animals.find(id).animal_type.id).to_json %> it works as expected.

I really think that's something weird here. I also recreate my database, and the problem persists.

Also, my MySQL version is 5.5.27-4 from Arch Linux repositories.

@palmergs

Random thought... remove :animal_type (and :user) from attr_accessible but leave :animal_type_id (and :user_id). Technically, you don't set object via mass assignment you set the object_id.

@caarlos0

hmm, ok.

I'm working right now.. tonight in my home I'll test it and update you about the results.

Thanks

@caarlos0

I do that, still the same bug...

@caarlos0

any other ideas?

@palmergs

I don't.... it would be interesting to see the relevant controller code that precedes current_user.animals.find(id).animal_type.to_json. If you're in an Animal#show page I'm curious why you're "finding" the animal off the current user rather than something simple in the controller like:

def show
  @animal = Animal.find(params[:id])

  Rails.logger.warn("Somthing is incorrect :: #{ @animal.inspect }") unless @animal.animal_type and !@animal.animal_type.sex.nil?

end

But, my only suggestion is reduce method chaining, reduce complexity and increasing logging until the problem becomes apparent.

@al2o3cr

@palmergs - finding records via an association of current_user is a pretty common way of providing basic access control; if the current user isn't associated to the animal it raises RecordNotFound which (by default) translates to a 404 response. Doing a find against the bare class means you've got a security hole unless you do additional access control - changing the URL shows other user's records.

@caarlos0 - can you post the results of running Animal.first.animal_type.attributes_before_typecast and AnimalType.columns in the console? It sounds like something weird is going on with typecasting.

@Systho

Wild guess here but would you try renaming AnimalType to AnimalKind ?
Rails might do some weird thing when dealing with "_id" and "_type" associations

@jasonlevens

Sex should probably be an attr of animal unless you have types setup for each sex

@senny
Owner

what is the status on this issue? Is it sill happening on master? Does anyone have a test-case or a sample app to reproduce?

@caarlos0

I end up changing my model, doesn't know anything about status and/or test case.

@senny
Owner

Since the ticket owner no longer experiences the problem I'm closing the issue. If someone else has the same bug please comment and I'll reopen and investigate.

@caarlos0 thanks for reporting.

@senny senny closed this
@caarlos0

That's fine @senny. Thank you and the guys for doing this good job =)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.