Using Acts_As_List with Has_Many :Through in Rails

Using acts_as_list with has_many :through in rails

The final version:

class QuestionMembership < ActiveRecord::Base
belongs_to :form
belongs_to :question
acts_as_list :scope => :form
end

class Form < ActiveRecord::Base
has_many :question_memberships, :order => "position"
has_many :questions, :through => :question_memberships
end

class Question < ActiveRecord::Base
has_many :question_memberships, :order => "position"
has_many :forms, :through => :question_memberships
acts_as_list :scope => :form
end

acts_as_list on has_many :through and sortablejs

With that structure you'll definitely need to reorder by calling methods on the join model. You can however use the Rails delegate class method to delegate reordering methods to the join model if you like:

https://api.rubyonrails.org/classes/Module.html#method-i-delegate

All of this assumes that you need items and categories to have a has_and_belongs_to_many type relationship.

acts_as_list with has_and_belongs_to_many relationship

I'm assuming that you have two models - Artist and Event.

You want to have an habtm relationship between them and you want to be able to define an order of events for each artist.

Here's my solution. I'm writing this code from my head, but similar solution works in my case. I'm pretty sure there is a room for improvement.

I'm using rails acts_as_list plugin.

That's how I would define models:

class Artist < ActiveRecord::Base
has_many :artist_events
has_many :events, :through => :artist_events, :order => 'artist_events.position'
end

class Event < ActiveRecord::Base
has_many :artist_events
has_many :artists, :through => :artist_events, :order => 'artist_events.position'
end

class ArtistEvent < ActiveRecord::Base
default_scope :order => 'position'
belongs_to :artist
belongs_to :event
acts_as_list :scope => :artist
end

As you see you need an additional model ArtistEvent, joining the other two. The artist_events table should have two foreign ids and additional column - position.

Now you can use acts_as_list methods (on ArtistEvent model, unfortunately) but something like

Artist.find(:id).events

should give you a list of events belonging to specific artist in correct order.

Ordering a has_many list in view

After a lot of research I'll post my results up to help someone else encase they need to have list of records attached to a model via many-to-many through relationship with being able to sort the choices in the view.

Ryan Bates has a great screencast on doing sorting with existing records: http://railscasts.com/episodes/147-sortable-lists-revised

However in my case I needed to do sorting before my Person model existed.

I can easily add an association field using builder or simple_form_for makes this even easier. The result will be params contains the attribute trait_ids (since my Person has_many Traits) for each association field:

#view code (very basic example)
<%= simple_form_for @character do |f| %>
<%= (1..5).each do |i| %>
<%= f.association :traits %>
<% end %>
<% end %>

#yaml debug output
trait_ids:
- ''
- '1'
- ''
- '2'
- ''
- '3'
- ''
- '4'
- ''
- '5'

So then the question is will the order of the elements in the DOM be respected whenever the form is submitted. Specially if I implement jQuery UI draggable? I found this Will data order in post form be the same to it in web form? and I agree with the answer. As I suspected, too risky to assume the order will always be preserved. Could lead to a bug down the line even if it works in all browsers now.

Therefore after much looking I've concluded jQuery is a good solution. Along with a virtual attribute in rails to handle the custom output. After a lot of testing I gave up on using acts_as_list for what I am trying to do.

To explain this posted solution a bit. Essentially I cache changes to a virtual property. Then if that cache is set (changes were made) I verify they have selected five traits. For my purposes I am preserving the invalid/null choices so that if validation fails when they go back to the view the order will remain the same (e.g. if they skipped the middle select boxes).

Then an after_save call adds these changes to the database. Any error in after_save is still wrapped in a transaction so if any part were to error out no changes will be made. It was easiest therefore to just delete all the endowments and save the new ones (there might be a better choice here, not sure).

class Person < ActiveRecord::Base
attr_accessible :name, :ordered_traits

has_many :endowments
has_many :traits, :through => :endowments, :order => "endowments.position"

validate :verify_list_of_traits

after_save :save_endowments

def verify_list_of_traits
return true if @trait_cache.nil?

check_list = @trait_cache.compact

if check_list.nil? or check_list.size != 5
errors.add(:ordered_traits, 'must select five traits')
elsif check_list.uniq{|trait| trait.id}.size != 5
errors.add(:ordered_traits, 'traits must be unique')
end
end

def ordered_traits
list = @trait_cache unless @trait_cache.nil?
list ||= self.traits
#preserve the nil (invalid) values with '-1' placeholders
list.map {|trait| trait.nil?? '-1' : trait.id }.join(",")
end

def ordered_traits=(val)
@trait_cache = ids.split(',').map { |id| Trait.find_by_id(id) }
end

def save_endowments
return if @trait_cache.nil?
self.endowments.each { |t| t.destroy }
i = 1
for new_trait in @trait_cache
self.endowments.create!(:trait => new_trait, :position => i)
i += 1
end
end

Then with simple form I add a hidden field

  <%= f.hidden :ordered_traits %>    

I use jQuery to move the error and hint spans to the correct location inside
the div of five select boxes I build. Then I had a submit event handler on the form and convert the selection from the five text boxes in the order they are in the DOM to an array of comma separated numbers and set the value on the hidden field.

For completeness here is the other classes:

class Trait < ActiveRecord::Base
attr_accessible :title
has_many :endowments
has_many :people, :through => :endowments
end

class Endowment < ActiveRecord::Base
attr_accessible :person, :trait, :position
belongs_to :person
belongs_to :trait
end

has any one gotten acts_as_list to work on rails 3?

Looks like it should work: http://www.railsplugins.org/plugins/317-acts-as-list?version_id=418

ordering a many_to_many relationship in rails

You'll need a position field in the contents_playlists (or playlists_contents - as the model is called PlaylistsContents?) table. You can add multiple scopes by using:

acts_as_list scope: [:playlist, :link, :media]

Edit: added :playlist

Resetting position attribute when using acts_as_list gem? (Rails)

Okay, I ended up re-writing the reset_position to what is shown below, since acts_as_list clearly does not handle association changes.

before_update :reset_position

def reset_position
if self.goal_id_changed?
self.position = self.goal.objectives.count > 0 ? self.goal.objectives.last.position + 1 : 1
end
end

Basically, it moves the Objective to the bottom of the Objective list when it is associated with a different Goal. Seems to work.

rails: sort a has_many using :order is not working due to order by timestamp

You can use a default scope:

class Post < ActiveRecord::Base
scope :by_position, order("position ASC")
default_scope by_position
#...
end

Then @user.posts should return a list ordered by the position (ASC).

Also, the following should be working for you: (plural on post)

has_many :posts, :dependent => :destroy, :order => "posts.position ASC"


Related Topics



Leave a reply



Submit