Rails: Displaying published post by all and unpublished post of current_user - ruby-on-rails

I wanted to know how one would do the following:
A user can view all published Posts
A user can view view their unpublished Post
Code:
# Post model
scope :published, where(is_published: true)
scope :unpublished, where(is_published: false)
# Post controller
def index
#Post = Post.published
if user_signed_in?
#Post = Post.published && Post.unpublished.where(user_id: current_user.id)
end
end
I'm not really sure what the right way to setup an active record condition to display what I'm after.
Any much is much appreciated.

You're pretty close! Just replace && with +
# Post controller
def index
#posts = Post.published
if user_signed_in?
#posts = Post.published + Post.unpublished.where(user_id: current_user.id)
end
end
Be aware that joining like this will change the #posts object from a relation to an array.
Also take a look at #SachinR's answer for a nice refinement to the Post.unpublished.where(user_id: current_user.id) line.
Based on your requirement I think you could do better with a scope:
#Post model
scope :published_and_user, lambda{|user| where("is_published = ? OR user_id = ?", true, user.id)}
scope :ordered, :order => "created_at DESC"
# Post controller
def index
#posts = Post.published.ordered
if user_signed_in?
#posts = Post.published_and_user(current_user).ordered
end
end
And now you have a relation that is ordered properly, and only one scope!

To get all published records
#posts = Post.where("user_id = ?", current_user.id).published
To get all unpublished records
#posts = Post.where("user_id = ?", current_user.id).unpublished
or
If Post belongs to user
class Post
belongs_to :user
end
then you can directly use
current_user.posts.published
current_user.posts.unpublished

Related

Rails: Show all posts except last post

I have created a section for my latest posts, and a section for all posts. However, my last created post gets shown twice.
In my controller, how do I show all posts except for the last post?
MainController
def index
#post = Post.all.order('created_at DESC')
#latest_post = Post.ordered.first
end
You're querying twice. Instead, query once, and pull the latest post off the result set:
def index
#posts = Post.all.order('created_at DESC').to_a
#latest_post = #posts.pop
end
I'm not completely sure which side of the results you're considering the "first" record, so if #posts.pop appears to give you what you consider the "last" record then use #posts.shift to remove the record from the opposite end.
This will not fetch #latest_post in #post
def index
#latest_post = Post.ordered.first
#post = Post.where.not(id: #latest_post.id).order('created_at DESC')
end
Or simply
def index
#latest_post = Post.last
#posts = Post.where.not(id: #latest_post.id).order('created_at DESC')
end

How to access child's attribute in index

Micropost has content that I want in the index (#user.micropost.content). I can do this in the show but not in index.
My models:
class User < ActiveRecord::Base
has_many :microposts
end
class Micropost < ActiveRecord::Base
belongs_to :user
end
My user_controller.rb:
def index
#users = User.all
end
def show
#user = User.find(params[:id])
#posts = #user.microposts
end
First you need a little eager loading to avoid N+1 queries
def index
#users = User.includes(:microposts).all
end
Then in the view you just loop normally and you'll be able to access the object
#users.each do |user|
user.microposts.each do |micropost|
micropost
end
end
Try with this code in your users controller:
def index
#users = User.includes(:microposts).all
end
And in your index page you show the microposts using:
<% #user.microposts do |micropost| %>
<p><%= micropost.title%></p>
<% end %>
This will allow you to use your users' microposts in the view and is also a good practice because the query in the database for the microposts will be executed only once.
You will have to loop through #users = User.all and access the microposts from each user that way.
def index
#users = User.all
#users.each do |user|
current_users_microposts = user.microposts
# your code here
end
end
Not sure what your requirements are but you can make an array and append each group of user.microposts onto that array as well, in order to have all of them in one variable.

Rails: collection based on params

I had an index method that looked, awfully, like this:
def index
if params[:brand]
#users = User.includes(:brand).where(brand_id: params[:brand]).order("#{sort_column} #{sort_direction}").page(params[:page]).per(10)
elsif params[:search]
#user = User.includes(:brand).find_by_client_code(params[:search])
redirect_to edit_user_path(#user)
elsif params[:page] == 'all'
#users = User.includes(:brand).order("#{sort_column} #{sort_direction}").all
elsif params[:state]
#users = User.includes(:brand).where(state: params[:state]).page(params[:page]).per(10)
else
#users = User.includes(:brand).order("#{sort_column} #{sort_direction}").page(params[:page]).per(10)
end
end
Pretty messy, I know, but it worked. Now I'm trying to refactor it and I can't figure out the best way to split it into smaller collections without complicating my routes.
def index
[:brand, :page, :search, :state].each do |param|
if params[:page] == 'all'
#users = User.includes(:brand).order(column + ' ' + direction)
elsif params.key?(param)
param
else
#users = User.includes(:brand).order(column + ' ' + direction)
.page(params[:page]).per(10)
end
end
end
def brand
#users = User.includes(:brand).where('brand_id in (?)', params[:brand])
.order(column + ' ' + direction).page(params[:page]).per(10)
end
def state
#users = User.includes(:brand).where(state: params[:state])
.page(params[:page]).per(10)
end
def search
#user = User.includes(:brand).find_by_client_code(params[:search])
redirect_to edit_user_path(#user)
end
The above doesn't work but you get the idea. Anyone know a good way to deal with this type of situation? Cheers.
I'd probably do it this way -
First, update this code where you have defined sort_column and sort_direction methods to have default values:
def sort_column
colum_name = params[:colum_name]
colum_name ||= 'id'
end
def sort_direction
direction = params[:direction]
direction ||= 'ASC'
end
Add a new method to have per_page(at the same place where you have sort_column and sort_direction) from params or default from User class:
def per_page
per = params[:per_page]
per ||= User.per_page
end
in app/models/user.rb:
scope :with_brand_id, ->(id) { where(brand_id: id) }
scope :with_state, ->(state) { where(state: state) }
scope :order_with, ->(column_name, direction) { order("#{sort_column} #{sort_direction}") }
# never use/avoid magic numbers in your application at multiple places as they gets unmanageable as your application grows
# single place to manage your per page entries for users.
def self.per_page
10
end
# easy to use key:value based arguments since you're using Ruby 2, cheers!
def self.fetch_with_brand(brand: nil, state: nil, page: nil, sort_column: 'id', sort_direction: 'ASC', per_page: User.per_page)
user_scope, pagination_scope_applies = if brand.present?
[self.with_brand_id(brand), true]
elsif state.present?
[self.with_state(state), true]
else
[self.scoped, (page != 'all')]
end
user_scope.merge(pagination_scope(page, per_page)) if pagination_scope_applies
user_scope.includes(:brand).order_with(sort_column, sort_direction)
end
# since I am not sure about your implementation of `page` and `per` methods, I'd create a class method, otherwise you can create a `scope` for this, too
def self.pagination_scope(page_number, per_page)
self.page(page_number).per(per_page)
end
Do you se the line: [self.scoped, (page != 'all')] in code mentioned above? Here self.scoped is equal to self.all(when evaluated), but we will have to use scoped instead of all as in Rails 3 it gives an Array, while in Rails 4 it will be an ActiveRecord::Relation object so you can use self.all if you're on Rails 4. NOTE: scoped is deprecated in Rails 4 in favor of all.
Also, I'd like to point out a gotcha here. In your code you're giving priority to params[:page] == 'all' condition and then to params[:search]. In the code I mentioned above gives priority to search and then to page, but you get the idea, right?
Now, let's add user specific params method in the controller:
def user_params
params.slice(:brand, :page, :state).merge!({sort_column: sort_column, sort_direction: sort_direction, per_page: per_page })
end
However, in Rails 4 it is easier to do with strong parameters, e.g.: params.require(:user).permit(:search,..) etc.
Now, your controller's index method can look something like this:
def index
if params[:search].present?
#user = User.find_by_client_code(params[:search])
redirect_to edit_user_path(#user)
else
#users = User.fetch_with_brand(user_params)
end
end
Or you can refactor it further if you tend to redirect user to edit page in more places:
before_filter :redirect_to_edit, only: [:index, :some_other_method_name]
def index
#users = User.fetch_with_brand(user_params)
end
def redirect_to_edit
if params[:search].present?
#user = User.find_by_client_code(params[:search])
redirect_to edit_user_path(#user)
end
end
You have your skinny controller up now.

rails controller for mixed nested and non-nested resources

I'm working on a blog like application,
my user module has_many posts and the posts module belongs_to user
I want to access both users/:id/posts and posts/
routes.rb is something like this:
resources :users do
resources :posts
end
resources:posts
how can i know within the posts controller if its accessed directly (/posts) or through the nested route (/users/:id/posts) ?
for example, what should be the index method of the posts controller for doing the correct INDEX action for /users/:id/posts and for /posts
is there a better way for doing this ?
One solution could be to use a before filter on your controller, like:
before_filter :load_user
def load_user
#user = User.find(params[:user_id]) if params[:user_id]
#posts = #user ? #user.posts : Post.all
end
Then you have to rewrite your controller a bit to function properly.
No refactoring needed on index action, #posts already loaded correctly, but you can do further filtering as you like
def index
#posts = #posts.where('updated_at < ?' Time.now)
end
Then update every member action: new, create, show, edit, update, destroy and use posts as a base like:
def new
#post = #posts.build
end
def create
#post = #posts.build(params[:task])
end
def show
#post = #posts.find(params[:id])
end
def edit
#post = #posts.find(params[:id])
end
def update
#post = #posts.find(params[:id])
end
def destroy
#post = #posts.find(params[:id])
end
Of course you can add other before filters to remove duplicate code.
Check the params.
If just post you'll just have :id
If user/post you'll have user and ID for post.
So check if params[:user]...
n.b. If not user, try params[:user_id]
As for the index method for posts I think it will actually be the SAME in both cases. What will change things is its usage, association and scoping within user.

refactoring a controller query with If/else

This is what my index action currently looks like. If the current_user is not enrolled to any courses it lists all courses. If he is enrolled to any, it only lists the other ones. Is this a good way to get the needed records or is there a nicer way? Any suggestions? Thx for your time!
if current_user.courses.empty?
#courses = Course.all
else
#courses = Course.where("id not in (?)", current_user.courses)
end
Use:
#user = current_user
#courses = #user.courses.empty? ? Course.all : Course.where("id not in (?)", #user.courses)
You could move all of this logic into a method on user:
class User < ActiveRecord::Base
def courses
if self.courses.empty?
#courses = Course.all
else
#courses = Course.where("id not in (?)", current_user.courses)
end
end
end
Then in your controller:
#courses = current_user.courses
You may also want to re-examine your schema and possibly get rid of the if/else altogether.

Resources