I am planning to add some pagination to a couple of models in my application and I feel like I am missing a key concept here. Here is my application:
class Gallery < ActiveRecord::Base
has_many :photos
has_many :comments
end
class GalleryController < ApplicationController
def show
# some process here
#gallery = Gallery.find(params[:id])
end
end
I would like to be able to paginate independently on both the photos and comments for the given gallery that I am displaying. I need this to be done with AJAX and I have a feeling that calling 'show' with a parameter for photos or gallery is overkill (ie. why would I need to find the gallery if I am only looking for photos or comments).
How should I design this feature?
What is the alternative to calling GalleryController.show here?
I'd set it up like this:
class GalleriesController < ApplicationController
def index
#galleries = Gallery.paginate(params)
end
end
class CommentsController < ApplicationController
def index
#comments = parent.comments.paginate(params)
end
def parent
#parent ||= Gallery.find(params[:gallery_id])
end
end
class PhotosController < ApplicationController
def index
#photos = parent.photos.paginate(params)
end
def parent
#parent ||= Gallery.find(params[:gallery_id])
end
end
# config/routes.rb
resources :galleries do
resources :photos
resources :comments
end
Then you just request:
/galleries/1/comments
/galleries/1/photos
Check out the inherited_resources gem for this nested model pattern.
But ideally you'd want to get this all in one request. Check out this post on the Presenter Pattern in Rails.
Hope that helps.
yes, i'd absolutely recommend using inherited_resources, as it saves you a lot of boilerplate code. just set up these resources as described by viatropos, add JSON-responder (with inherited_resources, its as easy as adding respond_to :json, :html to your controller). then on the ajax side use a tempting plugin, like jquery.tmpl or mustache.js or whatever, pre-render an item template for each resource, and then use ajax to retrieve your paginated data as JSON and render it into your templates. it's incredibly easy, but if you need a step by step howto, just ask.
Related
I have the following models:
Post
Tag
TaggedPost (from which Post and Tag derive their associations by has_many :through)
And I have the following routes.rb file:
resources :tags
resources :posts do
resources :tags
end
So when I navigate to, say, /posts/4/tags, that will shoot me into the index action for the Tag controller with the post_id value set in the parameters array. Cool.
My question is though, now that I'm accessing the nested tags resource under posts, should I be hitting the Tags controller still? Or should I setup some other controller to handle the nested nature of tags at this point? Otherwise I have to build additional logic into the Tags controller. This can be done of course, but is this the common way of handling nested routes and resources? The code I have in the index action for the Tags controller is as follows:
TagsController.rb
def index
if params[:post_id] && #post = Post.find_by_id(params[:post_id])
#tags = Post.find_by_id(params[:post_id]).tags
else
#tags = Tag.order(:name)
end
respond_to do |format|
format.html
format.json {render json: #tags.tokens(params[:q]) }
end
end
I can see the code in this controller growing increasingly large, as I plan for many additional resources to be associated with tag resources. Thoughts on how to break this out?
Summary of questions:
If a resource is nested, should the nested resource be going through a different controller representing the nested nature of the resource? This is opposed to going through the normal controller as I am in the code example that I provided.
If so, how should these controllers be named and setup?
Let me know if you need more information.
I think the best solution is to split up controllers:
resources :tags
resources :posts do
resources :tags, controller: 'post_tags'
end
And then you have 3 controllers. Optionally, you can inherit
PostTagsController from TagsController to do something like:
class PostTagsController < TagsController
def index
#tags = Post.find(params[:post_id]).tags
super
end
end
If the difference is only the retrieval of tags, you can:
class TagsController < ApplicationController
def tags
Tag.all
end
def tag
tags.find params[:id]
end
def index
#tags = tags
# ...
end
# ...
end
class PostTagsController < TagsController
def tags
Product.find(params[:product_id]).tags
end
end
Use that methods and simply override tags in the inheriting controllers ;)
All you are doing with nested resources is changing the routing URL. Only thing you would have to do is make sure you are passing the proper id (in your case post)to the tag controller. Most common error is the Can't Find *** ID.
If you don't nest a profile route into a user route it would look like this
domain.com/user/1
domain.com/profile/2
When you nest the routes it would be
domain.com/user/1/profile/2
That is all that it is doing and nothing else. You don't need additional controllers. Doing nested routing is just for looks. allowing your user to follow the association. The most important thing about nesting routes is that you make sure you make the link_to's to the right path.
When not nested: it would be
user_path
and
profile_path
when it is nested you would need to use
user_profile_path
rake routes is your friend to find out how the routes have changed.
Hope it helps.
Having a problem associating my results. Let me walk you though it:
I have 2 tables. dashboards, charts.
The charts table has a dashboard_id field as a dashboard has many charts.
So what I want in the dashboard controller is to grab the dashboard and all associated charts. Here is what I have so far:
Models
class Dashboards < ActiveRecord::Base
has_many :charts
....
end
class Charts < ActiveRecord::Base
has_one :dashboard
....
end
Contoller
class DashboardsController < ApplicationController
def show
#an ID is passed but for testing...
#dashboard = Dashboards.includes(:charts)
end
end
View
/dashboards/show.html.erb
<%=#dashboard.inspect%>
Result
uninitialized constant Dashboards::Chart
Can someone tell me what I'm doing wrong? It looks pretty clean to me and I have spent a few hours researching this. Am I overlooking something?
Firstly, model class names should use the singular (e.g. Dashboard, Chart, etc). Also change the Chart dashboard association to:
class Chart < ActiveRecord::Base
belongs_to :dashboard
....
end
Now in your controller (which generally uses the plural as you have it), this should work:
class DashboardsController < ApplicationController
def show
#an ID is passed but for testing...
#dashboard = Dashboard.includes(:charts)
end
end
tl;dr
I use CanCan for authorization in a single-author blog. I want non-admin users to not be able to view unpublished posts. The following does not do the trick:
can :read, Post do |post|
post.published_at && post.published_at <= Time.zone.now
end
Why doesn't it work, and what can I do to make it work?
Thanks. ;-)
The long version
Hello World,
I have a single-user blogging application and use CanCan for authorization purposes. I want administrators (user.admin? # => true) to be able to do whatever they wish with everything (they are administrators after all…). I also want regular users (both those who are logged in, but does not have the admin role, and those who are not logged in) to be able to view blog posts that have been published. I do not want them to see those that are not published.
Blog posts (of the model Post) each have an attribute called published_at (which is a DateTime and nil by default). Needless to say: when published_at is nil, the post is not published, otherwise it is published at the set date and time.
I have the following in my Ability class:
class Ability
include CanCan::Ability
def initialize user
user ||= User.new # guest user (not logged in)
if user.admin?
can :manage, :all
else
can :read, Post do |post|
post.published_at && post.published_at <= Time.zone.now
end
end
end
end
However, this does not seem to work as I intend it to. I have read on the CanCan wiki that this might not always work. However, I believe it should work in my case here, as I do have an instance of the Post model called #post in my PostsController#show action:
class PostsController < ApplicationController
authorize_resource
respond_to :html, :json
# other actions omitted ...
def show
#post = Post.find params[:id]
respond_with #post
end
# other actions omitted ...
end
Even with this code I am able to visit the blog post through the show action and view. I have also tried removing the authorize_resource call from the PostsController, realizing it might override some abilities or something, but it didn't help.
I have figured out a temporary solution, although I find it ugly and really want to utilize the CanCan abilities. My ugly temporary solution checks internally in the PostsController#show if the user has access to view the resource:
def show
#post = Post.find params[:id]
unless #post.published_at
raise CanCan::AccessDenied unless current_user && current_user.admin?
end
respond_with #post
end
As I said, this works. But I don't really want to go with this solution, as I believe there's a better way of doing this as a CanCan ability.
I'd much appreciate an explanation of why my approach does not work as well as a good solution to the problem. Thanks in advance. :-)
At the point where authorize_resource is being called (before_filter) you don't have a post object to authorize.
Assuming CanCan 1.6 or later, try this..
In your Post model
class Post < ActiveRecord::Base
scope :published, lambda { where('published_at IS NOT NULL AND published_at <= ?', Time.zone.now) }
# the rest of your model code
end
In your Ability model
class Ability
include CanCan::Ability
def initialize user
user ||= User.new # guest user (not logged in)
if user.admin?
can :manage, :all
else
can :read, Post, Post.published do |post|
post.published_at && post.published_at <= Time.zone.now
end
end
end
end
In your controller
class PostsController < ApplicationController
load_and_authorize_resource
respond_to :html, :json
# other actions omitted ...
def show
respond_with #post
end
end
I'm trying to model this inheritance for a simple blog system
Blog has many Entries, but they may be different in their nature. I don't want to model the Blog table, my concern is about the entries:
simplest entry is an Article that has title and text
Quote, however, does not have a title and has short text
Media has a url and a comment...
etc...
What is a proper way to model this with Ruby on Rails? That is
Should I use ActiverRecord for this or switch to DataMapper?
I would like to avoid the "one big table" approach with lots of empty cells
When I split the data into Entry + PostData, QuoteData etc can I have belongs_to :entry in these Datas without having has_one ??? in the Entry class? That would be standard way to do it in sql and entry.post_data may be resolved by the entry_id in the postdata table.
EDIT: I don't want to model the Blog table, I can do that, my concern is about the entries and how would the inheritance be mapped to the table(s).
I've come across this data problem several times and have tried a few different strategies. I think the one I'm a biggest fan of, is the STI approach as mentioned by cicloon. Make sure you have a type column on your entry table.
class Blog < ActiveRecord::Base
# this is your generic association that would return all types of entries
has_many :entries
# you can also add other associations specific to each type.
# through STI, rails is aware that a media_entry is in fact an Entry
# and will do most of the work for you. These will automatically do what cicloon.
# did manually via his methods.
has_many :articles
has_many :quotes
has_many :media
end
class Entry < ActiveRecord::Base
end
class Article < Entry
has_one :article_data
end
class Quote < Entry
has_one :quote_data
end
class Media < Entry
has_one :media_data
end
class ArticleData < ActiveRecord::Base
belongs_to :article # smart enough to know this is actually an entry
end
class QuoteData < ActiveRecord::Base
belongs_to :quote
end
class MediaData < ActiveRecord::Base
belongs_to :media
end
The thing I like about this approach, is you can keep the generic Entry data in the entry model. Abstract out any of the sub-entry type data into their own data tables, and have a has_one association to them, resulting in no extra columns on your entries table. It also works very well for when you're doing your views:
app/views/articles/_article.html.erb
app/views/quotes/_quote.html.erb
app/views/media/_media.html.erb # may be medium here....
and from your views you can do either:
<%= render #blog.entries %> <!-- this will automatically render the appropriate view partial -->
or have more control:
<%= render #blog.quotes %>
<%= render #blog.articles %>
You can find a pretty generic way of generating forms as well, I usually render the generic entry fields in an entries/_form.html.erb partial. Inside that partial, I also have a
<%= form_for #entry do |f| %>
<%= render :partial => "#{f.object.class.name.tableize}/#{f.object.class.name.underscore}_form", :object => f %>
<% end %>
type render for the sub form data. The sub forms in turn can use accepts_nested_attributes_for + fields_for to get the data passed through properly.
The only pain I have with this approach, is how to handle the controllers and route helpers. Since each entry is of its own type, you'll either have to create custom controllers / routes for each type (you may want this...) or make a generic one. If you take the generic approach, two things to remember.
1) You can't set a :type field through update attributes, your controller will have to instantiate the appropriate Article.new to save it (you may use a factory here).
2) You'll have to use the becomes() method (#article.becomes(Entry)) to work with the entry as an Entry and not a subclass.
Hope this helps.
Warning, I've actually used Media as a model name in the past. In my case it resulted in a table called medias in rails 2.3.x however in rails 3, it wanted my model to be named Medium and my table media. You may have to add a custom Inflection on this naming, though I'm not sure.
You can handle this easily using ActiveRecord STI. It requires you to have a type field in your Entries table. This way you can define your models like this:
def Blog > ActiveRecord::Base
has_many :entries
def articles
entries.where('Type =', 'Article')
end
def quotes
entries.where('Type =', 'Quote')
end
def medias
entries.where('Type =', 'Media')
end
end
def Entry > ActiveRecord::Base
belongs_to :blog
end
def Article > Entry
end
def Quote > Entry
end
def Media > Entry
end
I am trying to achieve a subsequent form submission. To clarify things -
I submit a form for #post
then once that #post is created I would immediately (under the hood) like to submit the form for #associations.
The catch is, this second form submission would require the post_id field from the newly created #post.
What would be the best way to achieve this? Would nested forms help me pull the newly created #post.id? Kindly help me with this.
If this is something that should happen whenever you create a Post, then you should use active callbacks to achieve that :
class Post < ActiveRecord::Base
after_create do |post|
# create your association using post.id
end
end
or, you can write it like that also :
class Post < ActiveRecord::Base
after_create :after_create_post
def after_create_post
# create your association using self.id
end
end
Otherwise, if this is something specific to a controller action, you should simple do something like this :
class PostsController < ApplicationController
def create
#post = current_user.posts.build(params[:post])
# then use the #post.id to build your association. something like
#post.associations.build(:prop1 => 'value1', :prop2 => 'value2')
end
end
Hope this helps!