Refactor this controller? - ruby-on-rails-3

class ArticlesController < ApplicationController
def index
#articles = Article.by_popularity
if params[:category] == 'popular'
#articles = #articles.by_popularity
end
if params[:category] == 'recent'
#articles = #articles.by_recent
end
if params[:category] == 'local'
index_by_local and return
end
if params[:genre]
index_by_genre and return
end
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #articles }
end
end
def index_by_local
# 10 lines of code here
render :template => 'articles/index_by_local'
end
def index_by_genre
# ANOTHER 10 lines of code here
render :template => 'articles/index_by_genre'
end
end
As you can see from above. My controller is not exactly thin. What its doing is, depending on the params that were passed, it interacts with the model to filter out records.
And if params[:local] or params[:genre] was passed. Then it calls its own methods respectively (def index_by_local and def index_by_genre) to do further processing. These methods also load their own template, instead of index.html.erb.
Is this quite typical for a controller to look? Or should I be refactoring this somehow?

We can move the first few lines into the model(article.rb):
def get_by_category(category)
# Return articles based on the category.
end
In this way we can completely test the article fetching logic using unit tests.
In general move all the code related to fetching records inside model.
Controllers in general
should authorize the user
get records using the params and assign them to instance variables
[These must typically be function
calls to model]
Render or redirect

I would define scopes for each of the collections you want to use.
class Article < ActiveRecord::Base
...
scope :popular, where("articles.popular = ?", true) # or whatever you need
scope :recent, where(...)
scope :by_genre, where(...)
scope :local, where(...)
...
def self.filtered(filter)
case filter
when 'popular'
Article.popular, 'articles/index'
when 'recent'
Article.recent, 'articles/index'
when 'genre'
Article.by_genre, 'articles/index_by_genre'
when 'local'
Article.local, 'articles/index_by_local'
else
raise "Unknown Filter"
end
end
end
Then in your controller action, something like this:
def index
#articles, template = Article.filtered(params[:category] || params[:genre])
respond_to do |format|
format.html { render :template => template }
format.xml { render :xml => #articles }
end
end

Related

strong param method expects to `define method_name` as `controller name`

rails (5.2.2.1)
ruby 2.5.0p0
Parent controller of Country, State, City
class LocalityController < ApplicationController
def create
locality = model_name.new(locality_master_params)
respond_to do |format|
if locality.save
format.html { redirect_to locality, notice: 'Record was successfully created.' }
else
format.html { render :new }
end
end
end
private
def model_name
"#{controller_name.titleize.delete(' ').singularize}".constantize
end
def locality_params
#locality_params = %i|name code status|
end
def locality_master_params
params.require("#{controller_name.singularize}".to_sym).permit(locality_params)
end
end
State controller
class StateMastersController < LocalityController
alias_method :state_master_params, :locality_master_params
private
def locality_params
#locality_params = %i|name code status country_code|
end
end
Expectation: country-state-city controllers should be inherited from one controller and manage same templates, methods for all those controllers to DRY.
This code works fine as per the expectation.
Issue: after removing below code(as it is unnecessary):
alias_method :state_master_params, :locality_master_params
it gives error as:
ActiveModel::ForbiddenAttributesError
I've added alias_method to prevent above error.
Getting same error in other controllers too: country-city controllers.
Is there any convention to define method as state_master_params for state_master_controller?
`

How to tell if something has been read in Rails for notification

I'd like to implement a simple notification system on my site where an unseen/unread item is displayed to the user. Similar to the one used across Stack Exchange for the user's inbox where unread comments on questions, etc are displayed.
I came across this question that provides an overview of how I'd do this. What I'm confused about is how to figure out if something has been read. I could add a read_at column but how do I actually fill it? If anyone could help me with some basic guidance I'd appreciate it!
UPDATE #1: What if I add a conditional to my Item#show action where I check the user_id (ID of the user creating the item) against current_user.id. Something like the below:
unless #item.user_id == current_user.id
#item.read_at = Time.now
end
UPDATE #2: Using the code below, I'm attempting to update the message's read_at if its recipient_id matches the current_user ID. However it's not working.
def show
#message = Message.find(params[:id])
if #message.recipient_id == current_user.id
#message.read_at == Time.now
#message.save
end
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #message }
end
end
FINAL UPDATE: Thanks to #prasvin, here's my solution. I added a read_at column to the object. The object also has an existing recipient_id column. So in my Controller's show action, I put the following:
def show
#message = Message.find(params[:id])
if #message.recipient_id == current_user.id
#message.update_attributes(:read_at => Time.now)
end
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #message }
end
end
Then in my helper.rb file:
def new_messages
Message.where(:recipient_id => current_user.id, :read_at => nil).count
end
And in my layout:
<% if new_messages > 0 %><span class="notification"><%= new_messages %></span><% end %>
How about filling in read_at column in show action, i.e. we have the object in the show action,and then update its read_at attribute before redering the page

how to display an alternate message ,if particular record is not available?

presently i am playing with rails3.Can it possible to show a message like"Sorry , this data is not available", in view if we do not have that data in record.
This is a way to...
class ModelController < ApplicationController
def show
respond_to do |format|
begin
#model = Model.find(params[:id])
rescue ActiveRecord::RecordNotFound
format.html { render :text => "Sorry , this data is not available" }
end
end
end
end

Rails - Capitalize Article Tags and Titles

Am trying to find a way of capitalizing the 1st letter of all Titles and Tags when a user submits an article. I can use the capitalize method, but where do I add it to the controller code blocks for it to work?
Thx
controllers/articles_controller:
def new
#article = Article.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #article }
end
end
controllers/tags_controller:
class TagsController < ApplicationController
def show
#tag = Tag.find(params[:id])
#articles = #tag.articles
end
end
models/article:
def tag_names
#tag_names || tags.map(&:name).join(' ')
end
private
def assign_tags
if #tag_names
self.tags = #tag_names.split(/\,/).map do |name|
Tag.find_or_create_by_name(name)
end
end
...
Where do you plan to capitalize it? before saving in the database? or when you're showing it to the user?
There are two ways to this:
Use rail's titleize function or capitalize
or do it using CSS with:
<p class="tag">im a tag</p>
#CSS
.tag {
text-transform:capitalize;
}
I would do something like this to force them to be capitalized before saving.
class Article < ActiveRecord::Base
def title=(title)
write_attribute(:title, title.titleize)
end
private
def assign_tags
if #tag_names
self.tags = #tag_names.split(/\,/).map do |name|
Tag.find_or_create_by_name(name.capitalize)
end
end
end
end
Try to use .capitalize
e.g. "title".capitalize will make "Title"
As Francis said, use .capitalize in your controller
I use this
#article= Article.new(params[:article])
#article.name = #article.title.capitalize
#article.save

override to_xml to limit fields returned

using ruby 1.9.2 and rails 3, i would like to limit the fields returned when a record is accessed as json or xml (the only two formats allowed).
this very useful post introduced me to respond_with and i found somewhere online that a nice way to blanket allow/deny some fields is to override as_json or to_xml for the class and set :only or :except to limit fields.
example:
class Widget < ActiveRecord::Base
def as_json(options={})
super(:except => [:created_at, :updated_at])
end
def to_xml(options={})
super(:except => [:created_at, :updated_at])
end
end
class WidgetsController < ApplicationController
respond_to :json, :xml
def index
respond_with(#widgets = Widgets.all)
end
def show
respond_with(#widget = Widget.find(params[:id]))
end
end
this is exactly what i am looking for and works for json, but for xml "index" (GET /widgets.xml) it responds with an empty Widget array. if i remove the to_xml override i get the expected results. am i doing something wrong, and/or why does the Widgets.to_xml override affect the Array.to_xml result?
i can work around this by using
respond_with(#widgets = Widgets.all, :except => [:created_at, :updated_at])
but do not feel that is a very DRY method.
In your to_xml method, do the following:
def to_xml(options={})
options.merge!(:except => [:created_at, :updated_at])
super(options)
end
That should fix you up.