For anyone struggling, like me, to get a good implementation of this without having to resort to calling two different associations in your views/controllers, there's an awesome plugin called "has_many_friends" on github that handles all the annoying details for you.
I've been using Sphinx, via Ultrasphinx, for a couple years now, but thought I'd give this a try.
I've had 0 success in getting it to generate the correct sql for associated models.
I created a new project with this model:
class Article < ActiveRecord::Base
define_index do
indexes subject, :sortable => true
indexes content
indexes author.first_name, :as => :author_fname, :sortable => true
has author_id, created_at, updated_at
end
end
the sql generated looks like this:
sql_query = SELECT `articles`.`id` * 1 + 0 AS `id` , CAST
(`articles`.`subject` AS CHAR) AS `subject`, CAST(`articles`.`content`
AS CHAR) AS `content`, CAST(`articles`.`first_name` AS CHAR) AS
`author_fname`, `articles`.`id` AS `sphinx_internal_id`, 3448190970 AS
`class_crc`, '3448190970' AS `subclass_crcs`, 0 AS `sphinx_deleted`,
IFNULL(`articles`.`subject`, '') AS `subject_sort`, IFNULL
(`articles`.`first_name`, '') AS `author_fname_sort`,
`articles`.`author_id` AS `author_id`, UNIX_TIMESTAMP
(`articles`.`created_at`) AS `created_at`, UNIX_TIMESTAMP
(`articles`.`updated_at`) AS `updated_at` FROM `articles` WHERE
`articles`.`id` >= $start AND `articles`.`id` <= $end GROUP BY
`articles`.`id` ORDER BY NULL
It is trying to use the first_name column from the article object not the author!
I can't see anything I am doing wrong (I am not doing anything!)... so to me it looks like either a bug or a serious lack of documentation.
Hello Ryan,
The last two episodes have been especially useful. I am still waiting for your nested forms and more jquery episodes either here or as screencasts at PragProg. The ActiveRecord and Forms screencasts have been extremely useful too.
Thanks.
Bharat
@Fredd - yes you're right. That's what I get for commenting before I've finished the s'cast. I'm used to the auto-recipricate type self-referential many-to-many as per the excellent Rails Recipes book and based on the notes thought it might perform those functions automatically.
I further recommend this article about how to get through this jungle of redundant ? Friendship, Membership, XYZOwnership & Co. assocation tables to put/abstract them into 1 table (acts_as_double_polymorphic_join) with the yet up to date plugin () has_many_polymorphs:
Very nice !
Anyway, you don't get all friends at once like this. I've exposed the problem here : http://stackoverflow.com/questions/788582/cant-define-joins-conditions-in-hasmany-relationship
For performances reasons, friendships must be retreived both ways in one request, especially if your friendship table contains 1.000.000+ lines like in http://woopets.com !
It's apparently not doable with rails 2.3.
I don't think theres a special inverse parameter. Ryan just creates another association with some special conditions and because the name will interfer with the first "friends" association he add the namespace "inverse" to it. Or am I wrong?:)
Great screencast as always! I wasn't aware of the inverse parameter yet either.
I am missing one thing though; I want someone who was "friended by" to be able to cancel the friendship and not just leave it up to the one who initiated the friendship.
Also I wonder won't this method leave you open for duplicate friendships? After Ryan befriends Fred, Fred can still befriend Ryan as well. Is there a way to validate against this without custom logic?
I could have done with this 18 months ago; I spent yonks working through the complexities of a 'I follow you, but you might not follow me' association type with the people on the RailsSpace google group.
The app ended up with a Subcriber/Subscribee naming convention (it sums up how people are interacting with one another on social networks nowadays), and, therefore, used a subscribers_controller and subscribee_controller, giving /users/1/subscribers and /users/1/subscribees routes and API. Is that something you looked at for this Friendships example, Ryan?
Thanks for this review of many to many associations and much more! It's funny because I discovered a lot of these features my self when implemented a group based permission system last week. Groups had memberships to different sections and so one (almost the same thing). The problem I discovered though was that it's hard to sort (in my case) the groups belonging to a certain section because you looping over the membership. If I wanted to sort by name it wouldn't work because the name field is not in the membership table. It's probably easy to fix this, I just need to read the API some more:)
After showing the Cucumber and raw webrat approaches for integration tests... I think it would be GREAT if you can screencast Stories, the minimalistic integration testing framework announced recently.
I think it fits all those people who think Cucumber is too much, but love Cucumber's plain english output and webrat. Take a look over here:
Hello Ryan and thanks for your many and highly appreciated tutorials!
Something I always dread in a Rails project is setting up a good and solid production environment, ie choosing http server, setting up clusters of application servers, setting up the database, etc. Do you think this might be material for an upcomming tutorial maybe? I'm certain more people must share this headache.
You don't even need to create the :js output for the category index action, you can use the native method (auto_complete_for :category, :name) provided by the plugin by using the :url parameter of the view helper:
I found an error (line 8),
http://github.com/ryanb/railscasts-episodes/blob/287ea29922e0b5d9f5b3c7db0b6a3a433d5adfc7/episode-154/blog/app/controllers/articles_controller.rb
It should be:
http://pastie.org/487142
#-----
class ArticlesController < ApplicationController
def show
@article = Article.find(params[:id])
@comment = Comment.new(:commentable => @article)
end
#...
end
#-----
With ":commentable => @article",
not ":article => @article".
What if another thread updates your object in the database, while you are working with an old instance of the user? I think it is much better to cache these objects on the database server, or through a data access object. It really depends what your doing with the user, but it can get kind of messy caching stuff in this way.
for those who were asking how to pass id instead of name because there are many situation where you cannot find a unique entity by name, here is the trick i have used make it work for me.
I'm having the same problem as Vikas. I found a full gist example of this technique at http://gist.github.com/33011 which supposedly worked fine. I used all this code simply changing out the models for my own names, but making sure to have everything singular/plural and the right model as needed. No matter what I do I keep getting the same error:
@line_sheet[new_page_image_attributes]' is not allowed as an instance variable name
and it's driving me crazy. Can anyone offer any help, I'll give you as much info as you need if you'd be willing. Thanks guys.
Tree and nested set plugins is a confusing topic. Thanks for clarifying some things Ryan.
acts_as_dag is useful in some circumstances because its fast and it allows for multiple parents, Acts as ordered tree also allows multiple parents but makes a lot of calls to the database. something that tree and nested set plugins are not designed to handle.
Tree and Nested set allow for only a single parent. Nested set plugins should use farey fractions as this gives them a considerable performance boost but most don't. According to the Hyrarchy plugin that uses farey fractions on MySQL, "Hyrarchy scales to at least one million nodes with insertion and access times below 100ms. On SQLite, times are below 200ms."
Cheers for this! I've been plodding through the paper pages of pragmatic programmer books til now, this is the most enlightening rails 5 minutes I've spent : )
I recently made an awesome Rails Template that emulates what Bort and some other people have done. Check it out at:
http://github.com/schmidtjra/Rails-Templates/tree/master
Great webcast! I need to produce several different documents from the one model. For example, off my "contact" model I want to have several letters. "Welcome", "Happy Birthday" etc etc. Can this be done with this method, or is there a 1-1 relationship between a view and a pdf??
For anyone struggling, like me, to get a good implementation of this without having to resort to calling two different associations in your views/controllers, there's an awesome plugin called "has_many_friends" on github that handles all the annoying details for you.
http://github.com/swemoney/has_many_friends/tree/master
I've been using Sphinx, via Ultrasphinx, for a couple years now, but thought I'd give this a try.
I've had 0 success in getting it to generate the correct sql for associated models.
I created a new project with this model:
class Article < ActiveRecord::Base
define_index do
indexes subject, :sortable => true
indexes content
indexes author.first_name, :as => :author_fname, :sortable => true
has author_id, created_at, updated_at
end
end
the sql generated looks like this:
sql_query = SELECT `articles`.`id` * 1 + 0 AS `id` , CAST
(`articles`.`subject` AS CHAR) AS `subject`, CAST(`articles`.`content`
AS CHAR) AS `content`, CAST(`articles`.`first_name` AS CHAR) AS
`author_fname`, `articles`.`id` AS `sphinx_internal_id`, 3448190970 AS
`class_crc`, '3448190970' AS `subclass_crcs`, 0 AS `sphinx_deleted`,
IFNULL(`articles`.`subject`, '') AS `subject_sort`, IFNULL
(`articles`.`first_name`, '') AS `author_fname_sort`,
`articles`.`author_id` AS `author_id`, UNIX_TIMESTAMP
(`articles`.`created_at`) AS `created_at`, UNIX_TIMESTAMP
(`articles`.`updated_at`) AS `updated_at` FROM `articles` WHERE
`articles`.`id` >= $start AND `articles`.`id` <= $end GROUP BY
`articles`.`id` ORDER BY NULL
It is trying to use the first_name column from the article object not the author!
I can't see anything I am doing wrong (I am not doing anything!)... so to me it looks like either a bug or a serious lack of documentation.
Oh I see you fixed it in the code here in the post... ;-)
Hey Ryan, shouldn't the code for maximum_price_conditions say " <= " instead of the " >= " in the screencast?
@Jason
You can specify the foreign_key to use:
class Reseller < Contact
has_many :contacts, :through => supervisions, :source => :user, :foreign_key => 'user_id'
end
Is there a way to use rails-layouts with prawn(to)?
Something like:
render foo.pdf.prawn :layout => "default.pdf.prawn}
My example don't work (it's render the foo.pdf inside the default.pdf). :-(
Thanks again Ryan. Keep up the good work. I would love to see a Globalize 2 and Rails 2.2 tutorial.
Hello Ryan,
The last two episodes have been especially useful. I am still waiting for your nested forms and more jquery episodes either here or as screencasts at PragProg. The ActiveRecord and Forms screencasts have been extremely useful too.
Thanks.
Bharat
@Fredd - yes you're right. That's what I get for commenting before I've finished the s'cast. I'm used to the auto-recipricate type self-referential many-to-many as per the excellent Rails Recipes book and based on the notes thought it might perform those functions automatically.
@Untitled TV: Try restarting script/server. That worked for me.
-Daniel
you must have been hungry.. you typed "dessert" instead of delete.. :-D
Hey, acts_as_network looks sweet! Thanks
Hello there
i need help for adding custom fields .. can u help me out please i need it badly. Ryan please help me on it.
How to associate custom fields with the model.
Ryan, have you tried the acts_as_network plugin?
This is exactly what I needed!
Thank you, Ryan.
I further recommend this article about how to get through this jungle of redundant ? Friendship, Membership, XYZOwnership & Co. assocation tables to put/abstract them into 1 table (acts_as_double_polymorphic_join) with the yet up to date plugin () has_many_polymorphs:
http://m.onkey.org/2007/8/14/excuse-me-wtf-is-polymorphs
Correct me for a niftier solution :-)
makes so much sense now, thanks
Hi Ryan,
Thanks for screencast.
One thing I noticed that in any social networking site you can't add any person directly!!!
You must need permission of that person.
Did you miss it or you didn't include it because of lack of time?
@Ryan: great screencast, this comes just right in time. Could anyone advise on how to adapt this with STI user models, e.g.
class User < ARB
class Contact < User
class Reseller < Contact
class Employee < Reseller
Both, resellers and employees should have many contacts through supervisions (:user_id => references, :contact_id => integer). If i use:
class Reseller < Contact
has_many :contacts, :through => supervisions, :source => :user
end
ActiveRecord throws an InvalidStatement error, because there is no reseller_id column in the supervisions table. Any hints?
Very nice !
Anyway, you don't get all friends at once like this. I've exposed the problem here : http://stackoverflow.com/questions/788582/cant-define-joins-conditions-in-hasmany-relationship
For performances reasons, friendships must be retreived both ways in one request, especially if your friendship table contains 1.000.000+ lines like in http://woopets.com !
It's apparently not doable with rails 2.3.
Hi!!!
I want downloadable code of this episode.
Tyler Gannon your said URL is password protected
*Adam Hill
I don't think theres a special inverse parameter. Ryan just creates another association with some special conditions and because the name will interfer with the first "friends" association he add the namespace "inverse" to it. Or am I wrong?:)
Great screencast as always! I wasn't aware of the inverse parameter yet either.
I am missing one thing though; I want someone who was "friended by" to be able to cancel the friendship and not just leave it up to the one who initiated the friendship.
Also I wonder won't this method leave you open for duplicate friendships? After Ryan befriends Fred, Fred can still befriend Ryan as well. Is there a way to validate against this without custom logic?
I could have done with this 18 months ago; I spent yonks working through the complexities of a 'I follow you, but you might not follow me' association type with the people on the RailsSpace google group.
The app ended up with a Subcriber/Subscribee naming convention (it sums up how people are interacting with one another on social networks nowadays), and, therefore, used a subscribers_controller and subscribee_controller, giving /users/1/subscribers and /users/1/subscribees routes and API. Is that something you looked at for this Friendships example, Ryan?
Thanks for this review of many to many associations and much more! It's funny because I discovered a lot of these features my self when implemented a group based permission system last week. Groups had memberships to different sections and so one (almost the same thing). The problem I discovered though was that it's hard to sort (in my case) the groups belonging to a certain section because you looping over the membership. If I wanted to sort by name it wouldn't work because the name field is not in the membership table. It's probably easy to fix this, I just need to read the API some more:)
Very nice - I wasn't aware of the inverse parameter
Great job bro!!
always wait for your casts...
How can you generate nested URLs?
Ie. page-1 is parent of page-2, page-2 is parent of page-3
and have something like
http://mysite/page1/page2/page3/
Hey, just wondering if a queuing system could be used for uploading files? not sure how this would work and if so which one would be recommended?
After showing the Cucumber and raw webrat approaches for integration tests... I think it would be GREAT if you can screencast Stories, the minimalistic integration testing framework announced recently.
I think it fits all those people who think Cucumber is too much, but love Cucumber's plain english output and webrat. Take a look over here:
http://blog.citrusbyte.com/2009/05/20/stories/
Hello Ryan and thanks for your many and highly appreciated tutorials!
Something I always dread in a Rails project is setting up a good and solid production environment, ie choosing http server, setting up clusters of application servers, setting up the database, etc. Do you think this might be material for an upcomming tutorial maybe? I'm certain more people must share this headache.
BR,
Erik
and what about security when using newrelic? everybody can see my logs. am i right? :)
Thanx a lot Ryan ..
You r a life saver . I was actually looking for a tutorial on acts_as_tree for a long time ..
I am a new in Ror and couldnt have imagine to build anything without your tutorials .
Thanx again dear , U r truly my hero.
MY good wishes r with you always and forever. U really are great and doing a marvellous job
Hey Ryan!
You don't even need to create the :js output for the category index action, you can use the native method (auto_complete_for :category, :name) provided by the plugin by using the :url parameter of the view helper:
:url => url_for(:controller => :categories, :action => :auto_complete_for_category_name, :escape => false)
Cheers, Sazima
If i'm using file_store caching, where the database record cached with Rails.fetch get stored? In the memory aniway?
Thanks
Great screencast, Ryan.
@Bharat, @Dario
I found an error (line 8),
http://github.com/ryanb/railscasts-episodes/blob/287ea29922e0b5d9f5b3c7db0b6a3a433d5adfc7/episode-154/blog/app/controllers/articles_controller.rb
It should be:
http://pastie.org/487142
#-----
class ArticlesController < ApplicationController
def show
@article = Article.find(params[:id])
@comment = Comment.new(:commentable => @article)
end
#...
end
#-----
With ":commentable => @article",
not ":article => @article".
What if another thread updates your object in the database, while you are working with an old instance of the user? I think it is much better to cache these objects on the database server, or through a data access object. It really depends what your doing with the user, but it can get kind of messy caching stuff in this way.
for those who were asking how to pass id instead of name because there are many situation where you cannot find a unique entity by name, here is the trick i have used make it work for me.
http://pastie.org/486924
Hope that helps.
I'm having the same problem as Vikas. I found a full gist example of this technique at http://gist.github.com/33011 which supposedly worked fine. I used all this code simply changing out the models for my own names, but making sure to have everything singular/plural and the right model as needed. No matter what I do I keep getting the same error:
@line_sheet[new_page_image_attributes]' is not allowed as an instance variable name
and it's driving me crazy. Can anyone offer any help, I'll give you as much info as you need if you'd be willing. Thanks guys.
Tree and nested set plugins is a confusing topic. Thanks for clarifying some things Ryan.
acts_as_dag is useful in some circumstances because its fast and it allows for multiple parents, Acts as ordered tree also allows multiple parents but makes a lot of calls to the database. something that tree and nested set plugins are not designed to handle.
Tree and Nested set allow for only a single parent. Nested set plugins should use farey fractions as this gives them a considerable performance boost but most don't. According to the Hyrarchy plugin that uses farey fractions on MySQL, "Hyrarchy scales to at least one million nodes with insertion and access times below 100ms. On SQLite, times are below 200ms."
Cheers for this! I've been plodding through the paper pages of pragmatic programmer books til now, this is the most enlightening rails 5 minutes I've spent : )
How to download source code from github?
thanks great introduction www.momo.ie
Very cool stuff, thanks Ryan.
Its a great idea to review some gems like that for a change Ryan.
Thanks a lot and keep up the good work.
Just went through this for my Rails 2.3.2 app. Great stuff...but according to the log, the formatted_articles_url method is deprecated.
Should be
xml.link articles_url(:format=>:rss)
FYI!
I recently made an awesome Rails Template that emulates what Bort and some other people have done. Check it out at:
http://github.com/schmidtjra/Rails-Templates/tree/master
Could it be possible to send parameters to the javascript functions ?
Michael, thanks a bunch that worked like a charm.
Great webcast! I need to produce several different documents from the one model. For example, off my "contact" model I want to have several letters. "Welcome", "Happy Birthday" etc etc. Can this be done with this method, or is there a 1-1 relationship between a view and a pdf??