Forum: Ruby on Rails acts_as_list with scope : position update problem?

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Bernard D. (Guest)
on 2006-04-01 23:21
Hello,
I've tried to set up a class with acts_as_list with a scope argument
that restricts a list to records with the same foreign key.

For example :

database :

CREATE TABLE `families` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(255) NOT NULL default '',
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=3 ;

INSERT INTO `families` VALUES (1, 'Smith');
INSERT INTO `families` VALUES (2, 'Jones');

CREATE TABLE `people` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(255) NOT NULL default '',
  `family_id` int(11) default NULL,
  `position` int(5) NOT NULL default '0',
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=6 ;

INSERT INTO `people` VALUES (1, 'Robert', 1, 1);
INSERT INTO `people` VALUES (2, 'Brian', 1, 2);
INSERT INTO `people` VALUES (3, 'John-Paul', 2, 1);
INSERT INTO `people` VALUES (4, 'Grace', 2, 2);


class Family < ActiveRecord::Base
	has_many :people
end

class Person < ActiveRecord::Base
	belongs_to :family, :order => 'position'
	acts_as_list :scope => 'family_id'
	validates_uniqueness_of :position, :scope => 'family_id'
end

Now, all this works fine, I can execute commands like
person.move_higher, person.move_to_bottom, etc. So the class behaves as
expected with acts_as_list, updating the position column all by itself :

>> @person = Person.find_by_id(2)
=> #<Person:0x239cd64 @attributes={"family_id"=>"1", "name"=>"Brian",
"id"=>"2", "position"=>"2"}>
>> @person.move_higher
=> true
>> @person.position
=> 1


Except for one thing : when I change a Person's scope (thus changing the
value for family_id), nothing gets updated except the scope value. The
position column doesn't change :

>> @person.family_id = 2
=> 2
>> @person
=> #<Person:0x239cd64 @attributes={"family_id"=>2, "name"=>"Brian",
"id"=>"2", "position"=>1}>

I would expect the "acts_as_list with scope" behavior to ;
a) update all the positions of the objects in the original scope to mend
the gap left by the departure of the updated record moved to another
scope,
b) the moved object to be added to the bottom of the list corresponding
to its new family_id scope.

This appears not to be the case.
The validation method raises an error if the position of the object to
update already exists in the new scope (though not in console).

This is what I have tried, although not working. I somehow can't access
params() from the callback method, and I can't figure out what's wrong.

class Person < ActiveRecord::Base
	belongs_to :family, :order => 'position'
	acts_as_list :scope => 'family_id'
	validates_uniqueness_of :position, :scope => 'family_id'
	before_validation_on_update :reorder_positions

	private

	def reorder_positions
		@updated_family_id = params[:person][:family_id]
		unless @updated_family_id == self.family_id
			self.move_to_bottom # reorder original list
			params[:person][:family_id] =
Family.find_by_id(@updated_family_id).people.length + 1
		end
	end
end

When I update a Person by changing its family_id, I get this error
message :

"undefined local variable or method `params' for #<Person:0x22e5060>"

Can someone help me figure it all out?
Am I making this more complicated than it really is?

Thanks in advance !

Bernard.
Bernard D. (Guest)
on 2006-04-02 13:46
Actually, I found a workaround by myself, including the position
reordering directly inside the people controller update method like so :

 def update
    @person = Person.find(params[:id])
    unless params[:person][:family_id] == @person.family_id
	@person.move_to_bottom
	params[:person][:position] =
Family.find(params[:person][:family_id]).people.length + 1
    end
    if @person.update_attributes(params[:person])
      flash[:notice] = 'Person was successfully updated.'
      redirect_to :action => 'list'
    else
      render :action => 'edit'
    end
  end

It works fine... but I'm concerned about breaking the MVC model, since
such a kind of rectification should belong to the model, not the
controller, shouldn't it? Although, I still can't access the params hash
from a callback method in the model. Still that error :

"undefined local variable or method `params' for #<Person:0x22e5060>"

Bernard.
Thomas B. (Guest)
on 2006-07-22 01:57
(Received via mailing list)
Hello Bernard,

I faced the same problem.
I found this solution to solve the problem in my Model :

1/ in my controller, I update the Person object with :
@person.update_attributes(params[:person])

2/ so in my Person model, I have overwritten the update_attributes
method :
 def update_attributes(attributes)
    # reorder the positions if the category has changed
    reorder_positions(attributes[:category_id]) unless
attributes[:category_id].to_s==self.category_id.to_s
    super(attributes)
  end

3/ the "reorder_positions" method in my Person model :
  private
  def reorder_positions(new_category_id)
    # reorder source list
    self.move_to_bottom

    # find the position in the destination list
    new_position = Category.find(new_category_id, :include =>
"contents").contents.length + 1
  end

Hope this helps.
Thomas B..
Bernard D. (Guest)
on 2006-09-26 01:25
Thanks Thomas,

That was indeed a much cleaner solution.
For the record though it didn't work for me until I added a line to the
rewritten update_attributes method in the model, like so :

  def update_attributes(attributes)
     # reorder the positions if the category has changed
     unless attributes[:category_id].to_s==self.category_id.to_s
       reorder_positions(attributes[:category_id])
       #this is needed to get rid of the old params[:position]
       attributes.delete("position")
     end
     super(attributes)
   end

Otherwise, the old position stuck in the params hash was still in the
way upon updating.

Cheers
Bernard.



Thomas B. wrote:
> Hello Bernard,
>
> I faced the same problem.
> I found this solution to solve the problem in my Model :
>
> 1/ in my controller, I update the Person object with :
> @person.update_attributes(params[:person])
>
> 2/ so in my Person model, I have overwritten the update_attributes
> method :
>  def update_attributes(attributes)
>     # reorder the positions if the category has changed
>     reorder_positions(attributes[:category_id]) unless
> attributes[:category_id].to_s==self.category_id.to_s
>     super(attributes)
>   end
>
> 3/ the "reorder_positions" method in my Person model :
>   private
>   def reorder_positions(new_category_id)
>     # reorder source list
>     self.move_to_bottom
>
>     # find the position in the destination list
>     new_position = Category.find(new_category_id, :include =>
> "contents").contents.length + 1
>   end
>
> Hope this helps.
> Thomas B..
This topic is locked and can not be replied to.