I've found an old plugin called acts_as_habtm_list - but it's for Rails 1.0.0.
Is this functionality built in acts_as_list now? I can't seem to find any information on it.
Basically, I have an artists_events table - no model. The relationship is handled through those two models specifying :has_and_belongs_to_many
How can I specify order in this situation?
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.
Additional update for the accepted answer: for Rails 4 and Rails 5:
has_many :events, -> { order 'artist_events.position ASC' }, through: :artist_events
has_many :artists, -> { order 'artist_events.position ASC' }, through: :artist_events
I trying with self-referencing like that
class Product < ActiveRecord::Base
has_many :cross_sales
has_many :cross_sales_products, :through => :cross_sales, :order => 'cross_sales.position'
end
class CrossSale < ActiveRecord::Base
default_scope :order => 'cross_sales.position'
belongs_to :product
belongs_to :cross_sales_product, :class_name => "Product"
acts_as_list :scope => :product
end
create_table :cross_sales, :force => true, :id => false do |t|
t.integer :product_id, :cross_sales_product_id, :position
end
But the field cross_sales.position is never updated ...
An idea ?
Update: Ok the field 'id' it's necessary in the case of additional model with has_many :through option. It's work well now
In the accepted answer, note that :order => 'artist_events.position' is referencing the table artist_events and not the model.
I ran into this minor hiccup when moving from a habtm association to has_many :through.
Related
This question already has an answer here:
Losing an Attribute When Saving Through an Association w/ Scope (Rails 4.0.0)
(1 answer)
Closed 9 years ago.
I want to model such a relationship between the models User and Event.
Therefore I have started with the following classes:
class User < ActiveRecord::Base
...
end
class Attendance < ActiveRecord::Base
# with columns user_id and event_id
...
end
class Event < ActiveRecord::Base
has_many :attendances
has_many :users, :through => :attendances
...
end
So far everything is okay: I can assign users and access attendances. But now I want to bring the state into play, such that I can distinguish e.g. between "attending", "unexcused absent", ... users. My first try was:
class Event < ActiveRecord::Base
has_many :attendances
has_many :users, :through => :attendances
has_many :unexcused_absent_users, -> { where :state => 'unexcused' },
:through => :attendances,
:source => :user
...
end
(:source has to be specified since otherwise it would search for a belongs to association named 'unexcused_absent_users')
The problem here is, that the where-predicate is evaluated on table 'users'.
I am clueless how to solve this 'correctly', without introducing new join tables/models for every state. Especially since every user can be just in one state for every event, I think a solution with one Attendance-model makes sense.
Have you an idea, how to get this right?
You can simply narrow the scope to look at the correct table:
has_many :unexcused_absent_users, -> { where(attendances: {state: 'unexcused'}) },
:through => :attendances,
:source => :user
Evem better, add this scope to the Attendance model and merge it in:
class Attendance < ActiveRecord::Base
def self.unexcused
where state: 'unexcused'
end
end
class Event < ActiveRecord::Base
has_many :unexcused_absent_users, -> { merge(Attendance.unexcused) },
:through => :attendances,
:source => :user
end
I have found a workaround, but I still think, this is ugly.
class Event < ActiveRecord::Base
has_many :user_attendances, :class_name => 'Attendance'
has_many :users, :through => :user_attendances, :source => :user
has_many :unexcued_absent_user_attendances, -> { where :state => 'unexcused'}, :class_name => 'Attendance'
has_many :unexcused_absent_users, :through => :unexcued_absent_user_attendances, :source => :user
end
In general: For every state that I want, I have to introduce a new has_many relationship with a scope and on top of that and an according has_many-through relationship.
this might work for you?
class Event < ActiveRecord::Base
has_many :attendances
has_many :users, :through => :attendances
def unexcused_absent_users
User.joins(:attendances)
.where(:state => 'unexcused')
.where(:event_id => self.id)
end
end
in rails 3+ methods are basically the same as scopes, just less confusing (in my opinion), they are chainable
event = Event.find(xxxx)
event.unexcused_absent_users.where("name LIKE ?", "Smi%")
I have two tables with a join table between them:
Parent: Pages
Child: Things
Join: Grids
In my models they are set up with a many to many relationship (has_many through):
class Page < ActiveRecord::Base
belongs_to :books
has_many :grids
has_many :things, :through => :grids
end
class Thing < ActiveRecord::Base
has_many :grids
has_many :pages, :through => :grids
end
class Grid < ActiveRecord::Base
belongs_to :page
belongs_to :thing
end
Now I want to be able to sort "things" using an ordering id from grid, called number, how can I do that ?
Thanks!
you need the ":include(s)" option in your find method, and then "order_by()" ...
you would use something like this:
Thing.where(...some condition or all..., :include => :grid ).order_by(grid.number)
See:
http://guides.rubyonrails.org/active_record_querying.html
http://m.onkey.org/active-record-query-interface
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
I'm trying to tidy up my code by using named_scopes in Rails 2.3.x but where I'm struggling with the has_many :through associations. I'm wondering if I'm putting the scopes in the wrong place...
Here's some pseudo code below. The problem is that the :accepted named scope is replicated twice... I could of course call :accepted something different but these are the statuses on the table and it seems wrong to call them something different. Can anyone shed light on whether I'm doing the following correctly or not?
I know Rails 3 is out but it's still in beta and it's a big project I'm doing so I can't use it in production yet.
class Person < ActiveRecord::Base
has_many :connections
has_many :contacts, :through => :connections
named_scope :accepted, :conditions => ["connections.status = ?", Connection::ACCEPTED]
# the :accepted named_scope is duplicated
named_scope :accepted, :conditions => ["memberships.status = ?", Membership::ACCEPTED]
end
class Group < ActiveRecord::Base
has_many :memberships
has_many :members, :through => :memberships
end
class Connection < ActiveRecord::Base
belongs_to :person
belongs_to :contact, :class_name => "Person", :foreign_key => "contact_id"
end
class Membership < ActiveRecord::Base
belongs_to :person
belongs_to :group
end
I'm trying to run something like person.contacts.accepted and group.members.accepted which are two different things. Shouldn't the named_scopes be in the Membership and Connection classes?
However if you try putting the named scopes in the Membership and Connection classes then you get this error (because Person.find(2).contacts returns an array of Persons which doesn't have an 'accepted' method:
>> Person.find(2).contacts.accepted
NoMethodError: undefined method `accepted' for #<Class:0x108641f28>
One solution is to just call the two different named scope something different in the Person class or even to create separate associations (ie. has_many :accepted_members and has_many :accepted_contacts) but it seems hackish and in reality I have many more than just accepted (ie. banned members, ignored connections, pending, requested etc etc)
You answered your own question:
Shouldn't the named_scopes be in the Membership and Connection classes?
Yes, they should be. This will let you call them as you wanted. It's also logically where they belong.
If you want something on Person that checks both, you can do something like:
named_scope :accepted, :conditions => ["connections.status = ? OR memberships.status = ?", Connection::ACCEPTED, Membership::ACCEPTED]
Maybe you want this to be an AND? not sure.
I'm sure this is not the best way and I believe you can do this on person and group models, but I also believe the following will work for you:
# models
class Person < ActiveRecord::Base
has_many :connections
has_many :contacts, :through => :connections
has_many :memberships
has_many :groups, :through => :memberships
end
class Group < ActiveRecord::Base
has_many :memberships
has_many :members, :through => :memberships
end
class Connection < ActiveRecord::Base
belongs_to :person
belongs_to :contact, :class_name => "Person", :foreign_key => "contact_id"
named_scope :accepted, :conditions => ["status = ?", Connection::ACCEPTED]
end
class Membership < ActiveRecord::Base
belongs_to :person
belongs_to :group
named_scope :accepted, :conditions => ["status = ?", Membership::ACCEPTED]
end
# controller
# get person's accepted contacts
#person = Person.first
#person.connections.accepted.map(&:contact)
# get group's accepted members
#group = Group.first
#group.memberships.accepted.map(&:person)
Hi I have a relation between 2 models. The models are "Category" and "Page".
I have now the use case that I need 2 Pages/Category. How can I write this?
The fields in Category are "page_id" and "page_en_id".
I'm not sure which is the best solution for this, I only know the belongs_to and has_many solution with foreign key option which makes here no sense for me.
I'm not sure, what you're looking for, but it looks like that:
class Page < ActiveRecord::Base
has_many :categories
has_many :en_categories, :foreign_key => 'page_en_id', :class_name => 'Category'
end
class Category < ActiveRecord::Base
belongs_to :page
end
Ok this is my solution now, thanks guys:
Page
has_one :category
has_one :category_en, :foreign_key => 'page_en_id', :class_name => 'Page'
Category
belongs_to :page
belongs_to :page_en, :class_name => "Page"
Consider the following models:
class Artist < ActiveRecord::Base
has_many :artist_events
has_many :events, :through => :artist_events
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
Is is possible to use ActiveScaffold to administer this type of relationship? The ArtistEvent model exists to define a hbtm relationship with additional attribute of position.
Thanks!
Jonathan
yes. after all, ArtistEvent is yet another model - and active scaffold can operate on it as long as you have routes and controllers on the join model.