sxross / MotionModel

Simple Model and Validation Mixins for RubyMotion
MIT License
192 stars 67 forks source link

Assigning to belongs_to relation causes "belongs_to is not possible to cast." error to occur on 2nd object #7

Closed boberetezeke closed 11 years ago

boberetezeke commented 11 years ago

Adding these two tests to the end of spec/relation_spec.rb causes an error in the second one. I am not sure if the first test is even supposed to pass (but I would like both to work actually).

describe "supporting belongs_to by assignment" do before do Task.delete_all Assignee.delete_all end

it "allows a child to back-reference its parent" do
  t = Task.create(:name => "Walk the Dog")
  a = Assignee.create(:assignee_name => "Rihanna")
  a.task = t
  Assignee.first.task.name.should == "Walk the Dog"
end

end

describe "supporting belongs_to by assignment with two objects" do before do Task.delete_all Assignee.delete_all end

it "allows a child to back-reference its parent" do
  t = Task.create(:name => "Walk the Dog")
  a1 = Assignee.create(:assignee_name => "Rihanna")
  a2 = Assignee.create(:assignee_name => "Madonna")
  a1.task = t
  Assignee.all[0].task.name.should == "Walk the Dog"
  a2.task = t
  Assignee.all[1].task.name.should == "Walk the Dog"
end

end


The error is:

ArgumentError: type task : belongs_to is not possible to cast. model.rb:325:in cast_to_type:': supporting belongs_to by assignment - allows a child to back-reference its parent model.rb:437:inmethod_missing:' spec.rb:183:in block in run_spec_block' spec.rb:307:inexecute_block' spec.rb:183:in run_spec_block' spec.rb:198:inrun'

sxross commented 11 years ago

The error is specific about what its complaint is. I'm still on the fence as to whether this should work but here's what I think right now: MotionModel was written before define_method worked. That's a game-changer, and will eliminate a lot of the method_missing voodoo while at the same time improving efficiency and making the code clearer. Given that the code will need to be revisited for that -- and soon -- I'd propose not making this spec pass for right now.

That said, I've included my spec for the same thing below. I tried to separate out the expectations in a very granular manner to make certain I knew where the failure is. I still don't have it passing (sigh).

Here are a couple of other random thoughts:

Done with randomness. Please let me know whether you think my roadmap (switch to define_method first) makes sense or if this is a deal-breaker misfeature.

Steve

describe "supporting belongs_to" do before do Task.delete_all Assignee.delete_all end

it "allows a child to back-reference its parent" do
  t = Task.create(:name => "Walk the Dog")
  t.assignees.create(:assignee_name => "Rihanna")
  Assignee.first.task.name.should == "Walk the Dog"
end

describe "mind changing behavior in belongs_to" do
  before do
    @t1 = Task.create(:name => "Walk the Dog")
    @t2 = Task.create :name => "Feed the cat"
    @a1 = Assignee.create :assignee_name => "Jim"
  end

  describe "basic wiring" do
    before do
      @t1.assignees << @a1
    end

    it "pushing a created assignee gives a task count of 1" do
      @t1.assignees.count.should == 1
    end

    it "pushing a created assignee gives a cascaded assignee name" do
      @t1.assignees.first.assignee_name.should == "Jim"
    end

    it "pushing a created assignee enables back-referencing a task" do
      @a1.task.name.should == "Walk the Dog"
    end
  end

  describe "when pushing assignees onto two different tasks" do
    before do
      Debug.resume
      @t2.assignees << @a1
      Debug.silence
    end

    it "pushing assignees to two different tasks lets the last task have the assignee (count)" do
      @t2.assignees.count.should == 1
    end

    it "pushing assignees to two different tasks removes the assignee from the first task (count)" do
      @t1.assignees.count.should == 0
    end

    it "pushing assignees to two different tasks lets the last task have the assignee (assignee name)" do
      @t2.assignees.first.assignee_name.should == "Jim"
    end

    it "pushing assignees to two different tasks lets the last task have the assignee (back reference)" do
      @a1.task.name.should == "Feed the cat"
    end
  end
end 

end

On Oct 25, 2012, at 6:08 PM, Steve Tuckner notifications@github.com wrote:

Adding these two tests to the end of spec/relation_spec.rb causes an error in the second one. I am not sure if the first test is even supposed to pass (but I would like both to work actually).

describe "supporting belongs_to by assignment" do before do Task.delete_all Assignee.delete_all end

it "allows a child to back-reference its parent" do t = Task.create(:name => "Walk the Dog") a = Assignee.create(:assignee_name => "Rihanna") a.task = t Assignee.first.task.name.should == "Walk the Dog" end

end

describe "supporting belongs_to by assignment with two objects" do before do Task.delete_all Assignee.delete_all end

it "allows a child to back-reference its parent" do t = Task.create(:name => "Walk the Dog") a1 = Assignee.create(:assignee_name => "Rihanna") a2 = Assignee.create(:assignee_name => "Madonna") a1.task = t Assignee.all[0].task.name.should == "Walk the Dog" a2.task = t Assignee.all[1].task.name.should == "Walk the Dog" end

end

The error is:

ArgumentError: type task : belongs_to is not possible to cast. model.rb:325:in cast_to_type:': supporting belongs_to by assignment - allows a child to back-reference its parent model.rb:437:inmethod_missing:' spec.rb:183:in block in run_spec_block' spec.rb:307:inexecute_block' spec.rb:183:in run_spec_block' spec.rb:198:inrun'

— Reply to this email directly or view it on GitHub.

boberetezeke commented 11 years ago

The reason that I was hoping this would work was that I want to take a parsed JSON string that gives data like

[ {"name" => "walk the dog", "assignees" => [{"assignee_name" => "Bob"}]}, {"name" => "feed the cat", "assignees" => [{"assignee_name" => "Fred"}]}, {"name" => "do nothing"} ]

And use that data to do the following:

data.each do |task_data| Task.create(task_data) end

So that we end up with three tasks, and two assignees with the first two tasks referencing their assignees.

Task.count == 3 Assignee.count == 2

I guess this does not apply to this issue because in this case, I am assigning to a has_many (:assignees) instead of a belongs_to. I guess I came across the belongs_to case by just playing in the REPL and was surprised that it sometimes worked and other times didn't. I think that the times it worked work just by accident and not by design.

All that said, the standard model for web apps is that the server (ie. rails) creates an object graph with child objects that it renders in HTML for the browser and then that gets submitted back to the server (with possible modified/deleted/created parts of the object graph). That is minimally what I would like to do with MotionModel:

Maybe I am just looking at this wrongly. :-(

boberetezeke commented 11 years ago

I guess what I am looking for is the following

class Task include MotionModel::Model

columns :name => :string has_many :assignees

accepts_nested_attributes_for :assignees end

Task.find(id).update_attributes(json_data)

I am willing to work on this part, but am not sure if you feel it is a good idea or where something like this should be implemented.

sxross commented 11 years ago

I'm going to close this as fixed, even though I didn't implement the accepts_nested_attributes_for. If you want to think through initializing an object graph from JSON, which is, I guess, what accepts_nested_attributes_for would do, feel free to fork this and have a go at it.