I have this Rails model: (Parameters removed for clarity)
class Folder < ActiveRecord::Base
belongs_to :parent, :class_name => :folder
has_many :children, :class_name => :folder
end
I want this model to be used like a file system folder. How do I have to configure the routes and the controller to make this possible?
1) As for the model: check out acts_as_tree
2) As for the routes: do something like
map.folder '/folders/*path', :controller => 'folders', :action => 'show'
and in the FoldersController,
def show
# params[:path] contains an array of folder names
#folder = Folder.root
params[:path].each |name|
#folder = #folder.children.find_by_name(name) or raise ActiveRecord::RecordNotFound
end
# there you go, #folder contains the folder identified by the path
end
Related
So I have a User model, and a Group model which has several users thanks to the GroupUserAssociation model. Here's how my relationships are defined:
class Group < ActiveRecord::Base
has_many :group_users, :class_name => 'GroupUserAssociation', :foreign_key => :group_id
has_many :group_admins, :class_name => 'GroupUserAssociation', :foreign_key => :group_id, :conditions => ['level = 1']
has_many :group_not_admins, :class_name => 'GroupUserAssociation', :foreign_key => :group_id, :conditions => ['level = 0']
has_many :users, :through => :group_users, :source => :user
has_many :admins, :through => :group_admins, :source => :user
has_many :not_admins, :through => :group_not_admins, :source => :user
end
If I want to add/remove users to group, there is an elegant way to write it (elegant because it doesn't involves the GroupUserAssociation object):
Group.first.users << User.first # Adds to group
Group.first.users.delete(User.first) # Removed from group
But if I do
Group.first.admins << User.first
Group.first.admins.delete(User.first)
it also deletes the association (hence has the same effect as the first lines).
Is there an elegant way (without handling the GroupUserAssociation object to promote/demote admin (= to update GroupUserAssociation.level from 1 to 0) ?
I could do
Group.first.users.delete(User.first) # Removed from group
Group.first.admins << User.first
But that would mean 2 times commiting to DB which is not really good...
I read there are some nice things for this in Rails 4, but unfortunately I'm using Rails 3.2...
Thanks
We do this using this code:
#config/routes.rb
resources :entries do
post :category
delete ":category_id", to: :category, as: "remove_category"
end
#Categories
def category
entry = #entry = Entry.find(params[:entry_id])
category = #category = Category.find(params[:category_id])
#Actions
entry.categories << category if request.post? && !entry.categories.include?(category)
entry.categories.delete(category) if request.delete?
#Return
respond_to do |format|
format.html { redirect_to collection_path }
format.js
end
end
I am converting a User object to json via:
user.to_json :methods => :new_cookies
the new_cookies method is:
cookies.all :include => :fortune, :conditions => {:opened => false}
This embed the cookies inside the user json object, but I want fortune to be embedded inside the cookie object as well. I passed inside :include => :fortune but that doesn't that work.
Is this possible?
Models:
class User < ActiveRecord::Base
has_many :cookies
has_many :fortunes, :through => :cookies
def new_cookies
cookies.all :include => :fortune, :conditions => {:opened => false}
end
end
class Cookie < ActiveRecord::Base
belongs_to :user
belongs_to :fortune
end
class Fortune < ActiveRecord::Base
serialize :rstatuses
serialize :genders
has_many :cookies
has_many :users, :through => :cookies
end
I am not sure that the :includes => :fortune option works as you expect (or perhaps at all) -- near the end of this section of the current Rails guides it mentions this option for finder methods other than .all.
I assume it works similarly to the new Active Relation query interface, e.g. Cookies.include(:fortunes).where(:opened => false) -- in this case, Rails "eager loads" the related records, meaning fortunes are fetched as part of the query for cookies. This is a performance enhancement, but doesn't otherwise change the behavior of Rails.
As I noted in the comments, I think as_json will do what you want -- it defines what is and is not part of the object when serialized using to_json. You specify methods that should be called in addition to (or to exclude) the methods of the data-backed object itself, for example, your new_cookies method.
In this example, I have added as_json to User (which gets Cookie) and also to Cookie in hopes that each cookie will nest its fortunes in its JSON. See below for an alternative.
class User < ActiveRecord::Base
has_many :cookies
has_many :fortunes, :through => :cookies
def as_json(option = {})
super(:methods => :new_cookies)
end
def new_cookies
cookies.all :conditions => {:opened => false}
end
end
class Cookie < ActiveRecord::Base
belongs_to :user
belongs_to :fortune
def as_json(options = {})
super(:methods => :cookie_fortune) # or perhaps just :fortune
end
def cookie_fortune
self.fortune
end
end
In a case where I was writing an API and didn't need to reflect the nested relationships between objects in the JSON, in the controller, I used something like
respond_to do |format|
format.html
format.json { render json: { :foo => #foo, :bar => #bar }}
end
to produce parallel nodes (objects) in the JSON.
I have a three models:
class Feed < ActiveRecord::Base
has_many :filters, :dependent => :destroy
has_many :keywords, :through => :filters, :uniq => true
end
class Filter < ActiveRecord::Base
belongs_to :feed
belongs_to :keyword
validates_uniqueness_of :keyword_id, :scope => :feed_id
end
class Keyword < ActiveRecord::Base
has_many :filters, :dependent => :destroy
has_many :feeds, :through => :filters
end
What I want is to have only unique entries in the database for keywords. For example, if two feeds both have a keyword 'hello', there should be two filters (one for each feed) both pointing to the same keyword.
What I am having trouble with is the controller code. Perhaps I am looking for too simple a solution, but I figure there must be an easy way to do this. This is what I have in my create action so far:
def create
#feed = Feed.find(params[:feed_id])
#keyword = #feed.keywords.create(params[:keyword])
redirect_to feed_keywords_path(#feed), notice: 'Keyword added successfully.'
end
With this controller code, the previous example would result in a duplicate keyword in the database, one for each feed/filter. Is there a straight-forward solution to this or do I need to do a check beforehand to see if there is already a keyword and in that case just create the filter?
Use a dynamic finder find_or_create_by :
def create
#feed = Feed.find(params[:feed_id])
#keyword = Keyword.find_or_create_by_keyword(params[:keyword]) # I assume here that you have a column 'keyword' in your 'keywords' table
#feed.keywords << #keyword unless #feed.keywords.all.include?(#keyword)
redirect_to feed_keywords_path(#feed), notice: 'Keyword added successfully.'
end
I have the following route definition:
resources :documents do
collection do
post :filter
end
end
and the following model structure:
class Document < ActiveRecord::Base
belongs_to :documentable, :polymorphic => true
end
class User < ActiveRecord::Base
has_many :documents, :as => :documentable
end
and controller structure:
class DocumentsController < ApplicationController
def index
# not important
end
def filter
# not important
end
end
I can easily in a view say:
polymorphic_path([#user, Document])
to get the path /users/1/documents, but I want to be able to say:
filter_polymorphic_path([#user, Document])
to get the path /users/1/documents/filter, unfortunately, this doesn't work.
Anyone know how I can pull this off without adding the following to my routes, for each of my documentable models:
resources :users do
resources :documents do
collection do
post :filter
end
end
end
polymorphic_path([#user, Document], :action => 'filter') gives you /users/:user_id/documents/filter.
Also, polymorphic_path([#user, Document], :action => 'filter', :sort_order => 'this-order') gives you /users/:user_id/documents/filter?sort_order=this-order.
I ran into the same problem thinking you can replace the edit in edit_polymorphic_path to whatever method you want.
See: http://api.rubyonrails.org/classes/ActionDispatch/Routing/PolymorphicRoutes.html
This would do it, and it reads nicely.
polymorphic_path([:filter, #user, Document])
Or these
polymorphic_path([:filter, #user, :documents])
polymorphic_path([:filter, #user, Document.new])
And with a query param
polymorphic_path([:filter, #user, Document], :q => 'keyword')
And, in a view you can also do this:
= link_to "Documents", [[:filter, #user, :documents], :q => 'keyword']
Suppose I have a polymorphic structure like this.
map.resources :bar, :has_many => :foo
map.resources :baz, :has_many => :foo
map.resources :qux, :has_many => :foo
class Foo
belongs_to :parent, :polymorphic => true
end
class FooController < AC
before_filter :find_parent
...
private
def find_parent
# ugly way:
#parent = if params[:bar_id]
Bar.find(params[:bar_id])
elsif params[:baz_id]
Baz.find(params[:baz_id])
elsif params[:qux_id]
Qux.find(params[:qux_id])
end
end
end
That's pretty ugly. Whenever we add a new thing that it might belong to, we need to add it un-DRYly to that before_filter.
It gets worse, too. Suppose that Foos are really polymorphic things that could show up anywhere, like comments or tags. And suppose that you have the following routes:
map.resources :bar, :has_many => :foo do |bar|
bar.resources :baz, :has_many => :foo
end
map.resources :qux, :has_many => :foo do |qux|
qux.resources :baz, :has_many => :foo
end
... now we have to worry about whether to check for bar_id or baz_id first.
For more complex resources, it's possible that this won't even be enough to be sure you get the parent's id.
What would be ideal is if we could do something like this:
def get_parent
# fetch the parameter that immediately preceeded :id
#parent = if x = params.before(:id)
# polymorphic find
x.key.to_s[0..-4].classify.constantize.find x.value
end
end
After all, our routes already encode the parent by virtue of the order of parameters in the URL. Why discard that information?
So: how can this be done?
You should be able to ask for foo.parent
You'll need to have something like this:
class Bar < ActiveRecord::Base
has_many :foos, :as => :parent
...
end