I have some questions about Has Many Through and how to manage a couple of things that keep cropping up. I''m reasonably new to rails, having come from a C# background and I have to admit I''m having some trouble getting my head around models. Anyway this is my second project in rails and it''s a little more complex than the first. I''m trying to do things the ''rails way'' but I''m running into a few problems. So, let me start off with some background. We have a few tables that enjoy various has-many (and has-many-thorugh) relationships. To start off with we have an ''owners'' table (these are essentially users but we call them owners for various reasons) Owners have the usual stuff, username, password, first_name, last_name, etc. We also have ''notebooks''. These notebooks have little information, just a name and are used to hold notes (duh!), but more on that later. Owners have many notebooks through a table called ''notebookshares'' (man I hate that name). Notebookshares carry information about the owners relationship to the notebook, so on top of notebook_id and owner_id the notebookshares table contains a field called ''access''. This field can be one of three values; ''readonly'', ''full'', ''owner''. If it''s ''owner'' then that user created the notebook and owns it and everything in it. They can then share notebooks they own with others, which is where ''readonly'' and ''full'' come in. If I have ''readonly'' access to a notebook then I''d somewhat restricted on what I can do, if I have ''full'' access then I can do everything to the notebook but share it with others. Finally notebooks have many notes, but notes only belong to one notebook. Notes simply contain some text, the date/time they were created and last updated. Essentially it all looks like this: class Owner < ActiveRecord::Base has_many :notebookshares, :dependent => true has_many :notebooks, :through => :notebookshares do def find_editable() find(:all, :conditions => "notebookshares.access <> ''readonly''") end def by_share_type(share_type) find(:all, :conditions => [''notebookshares.access = ?'', share_type]) end end etc.... end class Notebookshare < ActiveRecord::Base belongs_to :owner belongs_to :notebook end class Notebook < ActiveRecord::Base has_many :notes, :order => ''position'', :dependent => :destroy has_many :notebookshares, :dependent => true has_many :owners, :through => :notebookshares do def find_editable() find(:all, :conditions => "notebookshares.access <> ''readonly''") end def by_share_type(share_type) find(:all, :conditions => [''notebookshares.access = ?'', share_type]) end end etc... end class Note < ActiveRecord::Base belongs_to :notebook acts_as_list :scope => :notebook etc... end So, if I have either ''owner'' or ''full'' access to a notebook (as defined by the relationship between my owner_id and the specific notebook_id) then I can edit any given note in that notebook. If I only have ''readonly'' I can view the notes but can''t really manipulate them in any way. One of the things that keeps coming up is the ''access'' level of a particular note or notebook. For example, if I''m trying to delete a note I need to know if I have the right to do so. For example something like this would be great (which would be found in the notes controller): def destroy @owner = Owner.find(session[:owner_id]) @note = @owner.notes.find(params[:id]) @note.destroy unless @note.access == ''readonly'' end Similarly this information would be fantastic for notebooks, so something like #where @owner is an Owner object that was retrieved previously if @owner.notebooks.find(params[:id]).access == ''readonly'' blah...blah...blah end Now, I think it would have to be owner driven since the access an owner has on a notebook is what defines what they can do to the notebook and subsequently to the notes in that notebook. I suspect that we could also have something like Notebook.find(params[:id]).access(@owner) since we would then know the owner.id and the notebook.id but I kind of like the first way better. So, I have a few questions: 1. I know it''s possible to do something like @owner.notebook.notes.find(params[:id]) but is it possible to do something like @owner.notes.find(params[:id]) ? If it is I certainly haven''t been able to work out how - perhaps I''m missing something. 2. If it is possible to do 1 (or even if it isn''t and you have to do the longer and less elegant @owner.notebook.notes.find(params[:id])) is it possible to add a function to the note model that would return the access for that given note, or even add an accessor that get populated after the note is retrieved. I suspect that the difficulty would be in determining if the owner was present, because it would also be possible to do something like Notes.find(params[:id]) which means that we wouldn''t know who was looking for the note and therefore what kind of access level they had. Perhaps in this case the access function would return nil. 3. Similarly for 2 above how would you go about writing a function for Notebook that returned the access level for the supplied owner using something like Notebook.find(params[:id]).access(@owner) and similarly could something like @owner.notebooks.find(:all) prepopulate an access accessor for the notebook such that we could do something like foreach @owner.notebook.find(:all) do |notebook| puts notebook.name unless notebook.access == ''readonly'' end Thanks for any help on this subject - even a link to something similar would be appreciated. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Actually on further examination we can''t even do something like @owner.notebooks.notes.find(params[:id]) is this right? Seems odd to me. On Mar 13, 6:35 pm, "PeteSalty" <petesa...-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:> I have some questions about Has Many Through and how to manage a > couple of things that keep cropping up. I''m reasonably new to rails, > having come from a C# background and I have to admit I''m having some > trouble getting my head around models. Anyway this is my second > project in rails and it''s a little more complex than the first. I''m > trying to do things the ''rails way'' but I''m running into a few > problems. > > So, let me start off with some background. We have a few tables that > enjoy various has-many (and has-many-thorugh) relationships. > To start off with we have an ''owners'' table (these are essentially > users but we call them owners for various reasons) > > Owners have the usual stuff, username, password, first_name, > last_name, etc. > > We also have ''notebooks''. These notebooks have little information, > just a name and are used to hold notes (duh!), but more on that later. > > Owners have many notebooks through a table called > ''notebookshares'' (man I hate that name). Notebookshares carry > information about the owners relationship to the notebook, > so on top of notebook_id and owner_id the notebookshares table > contains a field called ''access''. This field can be one of three > values; ''readonly'', ''full'', ''owner''. If it''s ''owner'' then that user > created the notebook and owns it and everything in it. They can then > share notebooks they own with others, which is where ''readonly'' and > ''full'' come in. If I have ''readonly'' access to a notebook then I''d > somewhat restricted on what I can do, if I have ''full'' access then I > can do everything to the notebook but share it with others. > Finally notebooks have many notes, but notes only belong to one > notebook. Notes simply contain some text, the date/time they were > created and last updated. > > Essentially it all looks like this: > > class Owner < ActiveRecord::Base > > has_many :notebookshares, :dependent => true > has_many :notebooks, :through => :notebookshares do > def find_editable() > find(:all, :conditions => "notebookshares.access <> > ''readonly''") > end > > def by_share_type(share_type) > find(:all, :conditions => [''notebookshares.access = ?'', > share_type]) > end > end > > etc.... > > end > > class Notebookshare < ActiveRecord::Base > > belongs_to :owner > belongs_to :notebook > > end > > class Notebook < ActiveRecord::Base > > has_many :notes, :order => ''position'', :dependent => :destroy > > has_many :notebookshares, :dependent => true > has_many :owners, :through => :notebookshares do > def find_editable() > find(:all, :conditions => "notebookshares.access <> > ''readonly''") > end > > def by_share_type(share_type) > find(:all, :conditions => [''notebookshares.access = ?'', > share_type]) > end > end > > etc... > > end > > class Note < ActiveRecord::Base > > belongs_to :notebook > > acts_as_list :scope => :notebook > > etc... > > end > > So, if I have either ''owner'' or ''full'' access to a notebook (as > defined by the relationship between my owner_id and the specific > notebook_id) then I can edit any given note in that notebook. If I > only have ''readonly'' I can view the notes but can''t really manipulate > them in any way. > > One of the things that keeps coming up is the ''access'' level of a > particular note or notebook. For example, if I''m trying to delete a > note I need to know if I have the right to do so. > For example something like this would be great (which would be found > in the notes controller): > > def destroy > > @owner = Owner.find(session[:owner_id]) > > @note = @owner.notes.find(params[:id]) > > @note.destroy unless @note.access == ''readonly'' > > end > > Similarly this information would be fantastic for notebooks, so > something like > > #where @owner is an Owner object that was retrieved previously > if @owner.notebooks.find(params[:id]).access == ''readonly'' > > blah...blah...blah > > end > > Now, I think it would have to be owner driven since the access an > owner has on a notebook is what defines what they can do to the > notebook and subsequently to the notes in that notebook. > I suspect that we could also have something like > > Notebook.find(params[:id]).access(@owner) since we would then know the > owner.id and the notebook.id but I kind of like the first way better. > > So, I have a few questions: > > 1. I know it''s possible to do something like > @owner.notebook.notes.find(params[:id]) but is it possible to do > something like @owner.notes.find(params[:id]) ? > If it is I certainly haven''t been able to work out how - perhaps > I''m missing something. > > 2. If it is possible to do 1 (or even if it isn''t and you have to do > the longer and less elegant @owner.notebook.notes.find(params[:id])) > is it possible to add a function to the note model that would return > the access for that given note, or even add an accessor that get > populated after the note is retrieved. > > I suspect that the difficulty would be in determining if the owner was > present, because it would also be possible to do something like > Notes.find(params[:id]) which means that we wouldn''t know who was > looking for the note and therefore what kind of access level they had. > Perhaps in this case the access function would return nil. > > 3. Similarly for 2 above how would you go about writing a function for > Notebook that returned the access level for the supplied owner using > something like > > Notebook.find(params[:id]).access(@owner) > > and similarly could something like > > @owner.notebooks.find(:all) > > prepopulate an access accessor for the notebook such that we could do > something like > > foreach @owner.notebook.find(:all) do |notebook| > > puts notebook.name unless notebook.access == ''readonly'' > > end > > Thanks for any help on this subject - even a link to something similar > would be appreciated.--~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
So, there''s a lot of questions here... I''ll actually address the last one first, because it''s kind of easy: When you do @owner.notebooks, it returns a collection of Notebook objects. So, when you do @owner.notebooks.notes.find(:all) or whatever, you''re actually trying to call the notes function on the collection, as opposed to the underlying objects themselves. You''ll either have to iterate over them, or do something like class Owner < ActiveRecord::Base def notes self.notebooks.collect(&:notes) end end Then, @owner.notes will return all the notes for all notebooks. Now, on to some of the other stuff: For #1 in your first email, this (I think) does exactly what you want. For #2 in your first email, I going to make an assumption: I believe from what I read that notes don''t have individual access, they only have access based on the owners access to the notebook they are in. class Note < ActiveRecord::Base def has_access?(owner) self.notebook.has_access?(owner) end end class Notebook < ActiveRecord::Base def has_access?(owner) return nil if owner.nil? begin return Notebookshare.find_by_owner_id_and_notebook_id(owner.id, self.id).access != ''read-only'' rescue ActiveRecord::RecordNotFound return nil end end end Tell me if I''ve answered all your questions, I''m too lazy to read through the whole thing again and look :p --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Luke, Thanks for the response. Super helpful, but I have some more questions (isn''t that always the way) would class Owner < ActiveRecord::Base def notes self.notebooks.collect(&:notes) end end only return notes for the notebooks that the owner has access to (I suspect that it would). I''m unfamiliar with the terminology of &:notes, although I get the idea of running collect on each elelment of the noebooks array. The other 2 functions that you provided (has_access? for both notes and notebooks) would certainly work but I was also wondering if there was some way of prepopulating an accessor when something like @owner.notebooks.find(:all) was run, so that rather than having to call has_access on each notebook you could just call it''s ''access'' accessor perhaps something like: class Notebook < ActiveRecord::Base after_filter :set_access attr_accessor :access has_many :notes, :order => ''position'', :dependent => :destroy has_many :notebookshares, :dependent => true has_many :owners, :through => :notebookshares do def find_editable() find(:all, :conditions => "notebookshares.access <> ''readonly''") end def by_share_type(share_type) find(:all, :conditions => [''notebookshares.access = ?'', share_type]) end end def set_access self.access = <this is where I get stuck> end etc... end where I get stuck is in set_access. Is there a way to determine if the notebook has been retrieved using something like: @owner.notebooks.find(:all) and hence use the calling @owner object to determine the access. I realize that Notebook.find(1) would have to return nil in access because we wouldn''t know which owner we''re looking at the access for and then the has_access? function would come into play. Hope this isn''t too confusing. Once again, thanks for the help. Dale On Mar 14, 12:32 pm, "Luke Ivers" <technod...-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:> So, there''s a lot of questions here... I''ll actually address the last one > first, because it''s kind of easy: > > When you do @owner.notebooks, it returns a collection of Notebook objects. > So, when you do @owner.notebooks.notes.find(:all) or whatever, you''re > actually trying to call the notes function on the collection, as opposed to > the underlying objects themselves. You''ll either have to iterate over them, > or do something like > class Owner < ActiveRecord::Base > def notes > self.notebooks.collect(&:notes) > end > end > > Then, @owner.notes will return all the notes for all notebooks. > > Now, on to some of the other stuff: > > For #1 in your first email, this (I think) does exactly what you want. > For #2 in your first email, I going to make an assumption: I believe from > what I read that notes don''t have individual access, they only have access > based on the owners access to the notebook they are in. > class Note < ActiveRecord::Base > def has_access?(owner) > self.notebook.has_access?(owner) > end > end > class Notebook < ActiveRecord::Base > def has_access?(owner) > return nil if owner.nil? > begin > return Notebookshare.find_by_owner_id_and_notebook_id(owner.id, > self.id).access != ''read-only'' > rescue ActiveRecord::RecordNotFound > return nil > end > end > end > > Tell me if I''ve answered all your questions, I''m too lazy to read through > the whole thing again and look :p--~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
> > class Owner < ActiveRecord::Base > def notes > self.notebooks.collect(&:notes) > end > end > > only return notes for the notebooks that the owner has access to (I > suspect that it would). I''m unfamiliar with the terminology of > &:notes, although I get the idea of running collect on each elelment > of the noebooks array.What I wrote is exactly the same as self.notebooks.collect { |notebook| notebook.notes }... basically, takes each notebook in turn and queries for their notes... this would only work for notebooks that have been cross-referenced through notebookshares, whatever kind of access that is denoting (read-only, whatever). If you want to specify a certain type of access, you can do self.notebooks.collect {|notebook| notebook.notes if notebook.has_access? (self)} More later... gotta run. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---