I have five dropdowns, and I need to put conditions on each of them. My code is:
def search(search, compare, year, rain_fall_type)
if search == 'All'
if rain_fall_type == 'All'
all
else
if year == 'All'
if rain_fall_type == "None"
where('Sector = ? OR Sector = ? OR Sector = ?', "Primary", 'Secondary', 'Tertiary').order('id')
else
# all
where(Sector: rain_fall_type).order('id')
end
else
if rain_fall_type == "All"
order("#{year} ")
elsif rain_fall_type == "None"
where('Sector = ? OR Sector = ? OR Sector = ?', "Primary", 'Secondary', 'Tertiary').order('id')
else
where(Sector: rain_fall_type).order("#{year} ")
end
end
# where(Year: year).order("#{rain_fall_type} ")
end
elsif compare != "None"
if year == 'All'
where('Sector = ? OR Sector = ?', rain_fall_type, compare).order(:id)
else
where('Sector = ? OR Sector = ?', rain_fall_type, compare).order(:id)
end
else
if rain_fall_type == 'All'
all.order('id')
else
if year == 'All'
if rain_fall_type == "None"
where('Sector = ? ', search).order('id')
else
where('Sector = ? ', rain_fall_type).order('id')
end
else
if rain_fall_type == "None"
if search == "All"
where('Sector = ? ', search).order('id')
else
where('Sector = ? ', search).order('id')
end
else
# all
where('Sector = ? ', rain_fall_type).order('id')
end
end
end
end
end
It has many if and else. I am trying to minimise the conditions. What can be the best way to shrink this code? Someone suggested that I should use switch case instead. Should I use it? If so, how?
You can use a guard statement which is basically return something if some_condition?. This only doable in specific scenarios (where one of the condition is executing a single statement:
Bad example:
if condition?
do_something
else
do_something_else
end
This could be written as:
return do_something if condition?
do_something_else
This will give you less code branching.
Also, another recommendation is to call another method with more conditions instead of nesting conditions in one single shot.
Bad example:
if condition?
if condition_two?
do_something_two
else
do_something
end
else
do_something_else
end
This could be written as:
if condition?
call_another_method
else
do_something_else
end
def call_another_method
if condition_two?
do_something_two
else
do_something
end
end
An example from your code could be:
if rain_fall_type == 'All'
all
else
if year == 'All'
if rain_fall_type == "None"
where('Sector = ? OR Sector = ? OR Sector = ?', "Primary", 'Secondary', 'Tertiary').order('id')
else
# all
where(Sector: rain_fall_type).order('id')
end
else
if rain_fall_type == "All"
order("#{year} ")
elsif rain_fall_type == "None"
where('Sector = ? OR Sector = ? OR Sector = ?', "Primary", 'Secondary', 'Tertiary').order('id')
else
where(Sector: rain_fall_type).order("#{year} ")
end
end
end
That could be converted to:
return all if rain_fall_type == 'All'
if year == 'All'
return where('Sector = ? OR Sector = ? OR Sector = ?', "Primary", 'Secondary', 'Tertiary').order('id') if rain_fall_type == "None"
where(Sector: rain_fall_type).order('id')
else
return order("#{year} ") if rain_fall_type == "All"
return where('Sector = ? OR Sector = ? OR Sector = ?', "Primary", 'Secondary', 'Tertiary').order('id') if rain_fall_type == "None"
where(Sector: rain_fall_type).order("#{year} ")
end
I hope this could help :)
NOTE: This is to answer the original question of How to simplify big conditions?. But the original post is not following Rails/Ruby way of doing search and filters and not making a good use of scopes.
This is probably the best explanation of how you should set this up.
class Product < ActiveRecord::Base
# custom_scope_1
scope :status, -> (status) { where status: status }
# custom_scope_2
scope :location, -> (location_id) { where location_id: location_id }
# custom_scope_3
scope :search, -> (name) { where("name like ?", "#{name}%")}
end
def index
#products = Product.where(nil) # creates an anonymous scope
#products = #products.status(params[:status]) if params[:status].present?
#products = #products.location(params[:location]) if params[:location].present?
#products = #products.search(params[:search]) if params[:search].present?
end
This can be cleaned up further by...
def index
#products = Product.where(nil)
filtering_params(params).each do |key, value|
#products = #products.public_send(key, value) if value.present?
end
end
private
# A list of the param names that can be used for filtering the Products
def filtering_params(params)
params.slice(:status, :location, :search)
end
This method uses ruby meta-programming to loop through parameters and dynamically call predefined scopes on a model
You can move this code into a module and include it into any model that supports filtering
app/models/concerns/filterable.rb
module Filterable
extend ActiveSupport::Concern
module ClassMethods
def filter(filtering_params)
results = self.where(nil)
filtering_params.each do |key, value|
results = results.public_send(key, value) if value.present?
end
results
end
end
end
app/models/product.rb
class Product
include Filterable
...
end
app/controllers/product_controller.rb
def index
#products = Product.filter(params.slice(:status, :location, :search))
end
You now have filtering and searching of your models with one line in the controller and one line in the model
First of all, some of your logic doesn't make sense:
def search(search, compare, year, rain_fall_type)
if search == 'All'
if rain_fall_type == 'All'
all
else
# rain_fall_type != 'All'
if year == 'All'
if rain_fall_type == "None"
where('Sector = ? OR Sector = ? OR Sector = ?', "Primary", 'Secondary', 'Tertiary').order('id')
else
where(Sector: rain_fall_type).order('id')
end
else
# in rain_fall_type != 'All' branch, so meaningless 'if'
if rain_fall_type == "All"
order("#{year} ")
elsif rain_fall_type == "None"
where('Sector = ? OR Sector = ? OR Sector = ?', "Primary", 'Secondary', 'Tertiary').order('id')
else
where(Sector: rain_fall_type).order("#{year} ")
end
end
end
elsif compare != "None"
# both are same, so meaningless 'if'
if year == 'All'
where('Sector = ? OR Sector = ?', rain_fall_type, compare).order(:id)
else
where('Sector = ? OR Sector = ?', rain_fall_type, compare).order(:id)
end
else
# search != 'All'
if rain_fall_type == 'All'
all.order('id')
else
if year == 'All'
if rain_fall_type == "None"
where('Sector = ? ', search).order('id')
else
where('Sector = ? ', rain_fall_type).order('id')
end
else
if rain_fall_type == "None"
# in search != 'All' branch, so meaningless 'if'
# AND both are same, so again meaningless 'if'
if search == "All"
where('Sector = ? ', search).order('id')
else
where('Sector = ? ', search).order('id')
end
else
where('Sector = ? ', rain_fall_type).order('id')
end
end
end
end
end
There's more like that and I won't point it all out because we're throwing all that if stuff out, anyway.
Ultimately, we're going to defer the querying to the end of the method, like this:
def search(search, compare, year, rain_fall_type)
...
#query = all
#query = #query.where(Sector: #sectors) if #sectors
#query = #query.order(#order) if #order
#query
end
That way, you take all of your where and order statements, and do them only once at the end. That saves a lot of typing right there. See the comment from muistooshort for why (Sector: #sectors) works.
So, the trick is setting #sectors and #order. First, I'm going to assign the input variables to instance variables because I like it like that (and to avoid confusion between the variable #search and the method search):
def search(search, compare, year, rain_fall_type)
#search, #compare, #year, #rain_fall_type = search, compare, year, rain_fall_type
...
#query = all
#query = #query.where(Sector: #sectors) if #sectors
#query = #query.order(#order) if #order
#query
end
Now, this answer is going on too long already, so I won't drag you through all the gorey details. But, adding in a couple of helper methods (sectors_to_use, and order_to_use) and substituting them in for #sectors and #order, you basically end up with this:
def search(search, compare, year, rain_fall_type)
#search, #compare, #year, #rain_fall_type = search, compare, year, rain_fall_type
#query = all
#query = #query.where(Sector: sectors_to_use) if sectors_to_use
#query = #query.order(order_to_use) if order_to_use
#query
end
private
def sectors_to_use
return [#rain_fall_type, #compare] if #search != 'All' && #compare != 'None'
unless #rain_fall_type == 'All'
if #rain_fall_type == 'None'
#search == 'All' ? ['Primary', 'Secondary', 'Tertiary'] : [#search]
else
[#rain_fall_type]
end
end
end
def order_to_use
return nil if (#search == 'All') && (#rain_fall_type == 'All')
return #year if (#search == 'All') && !(#year == 'All')
return :id
end
That's less than half the lines of code, over a thousand fewer characters, and a whole lot fewer ifs.
Related
I have a controller method for searching and listing invoices, sales orders and homes based on params name, how to simplify this index method`
def index
#so = QbwcSalesOrder.paginate(:page => params[:page], :per_page => 30)
if params[:qs] == "Sales Orders"
#so = #so.where("ref_number = ? ",params[:keyword]) if params[:keyword].present?
elsif params[:qs] == "Invoices"
#so = QbwcInvoice.paginate(:page => params[:page], :per_page => 30)
#so = #so.where("ref_number = ? ",params[:keyword]) if params[:keyword].present?
elsif params[:qs] == "All Homes"
#so = QbwcHome.paginate(:page => params[:page], :per_page => 30)
elsif params[:qs] == "Existing Homes"
#so = QbwcHome.where(record_type: "FromQB").paginate(:page => params[:page], :per_page => 30)
end
end
First of all unify hash syntax and indentation, it's hard to read.
Second paginate once at the end of the index method, looks like you paginate always the same way.
Third think about scopes or query object https://mkdev.me/en/posts/how-to-use-query-objects-to-refactor-rails-sql-queries
Here is simplified version, you can use it in index or move to the query object:
def index
#so = QbwcSalesOrder.all
#so = #so.where("ref_number = ? ", params[:keyword]) if params[:keyword] && params[:qs] == "Sales Orders"
#so = #so.where("ref_number = ? ", params[:keyword]) if params[:keyword] && params[:qs] == "Invoices"
#so = #so.where(record_type: "FromQB") if params[:qs] == "Existing Homes"
#so = #so if params[:qs] == "All Homes"
#so = #so.paginate(page: params[:page], per_page: 30)
end
To me this looks like the method is doing way to much and needs to be split into managable parts:
def index
#so = qs_model_scope(params[:qs])
.paginate(page: params[:page], per_page: 30)
.then do |scope|
filter_by_keyword(scope)
end
end
# ...
private
def qs_model_scope(qs)
case qs
when "Invoices":
QbwcInvoice.all
when "All Homes":
# Smelly - should be a model scope
QbwcHome.where(record_type: "FromQB")
else
QbwcSalesOrder.all
end
end
def filter_by_keyword(scope)
if params[:keyword].present? && ["QbwcSalesOrder","QbwcInvoice"].include?(scope.model.name)
# no need to use a SQL string
scope.where(ref_number: params[:keyword])
else
scope
end
end
If the complexity of this continues to grow you should consider moving this filtering process out of the controller and into a separate object (such as a form object or service object or just a PORO) so that it can be tested in isolation.
I'd split the logic into separate methods. As already mentioned in another answer it makes sense to move it to a query object or something.
def index
scope = invoices || all_homes || existing_homes || sales_orders
#so = scope.paginate(:page => params[:page], :per_page => 30)
end
private
def invoices
return if params[:qs] != 'Invoices'
return QbwcInvoice if params[:keyword].blank?
QbwcInvoice.where(ref_number: params[:keyword])
end
def all_homes
QbwcHome if params[:qs] == 'All Homes'
end
def existing_homes
QbwcHome.where(record_type: 'FromQB') if params[:qs] == 'Existing Homes'
end
def sales_orders
if params[:qs] == 'Sales Orders' && params[:keyword].present?
QbwcSalesOrder.where(ref_number: params[:keyword])
else
QbwcSalesOrder
end
end
I am trying to search through my model using 3 columns. Also if the column is empty, it is valid. This is how I am doing it
def getactivityfortoday
#temp = params[:temp]
logger.debug "params temp:#{#temp.inspect}"
#sky = params[:sky]
#day = params[:day]
#todaysactivities = []
#activities=[]
#finaldata = []
#activities = Weatherclockactivity.all
#attemptactivities = []
#attemptactivities = #user.attempts
for activity in #activities do
logger.debug "activity: #{activity.attributes.inspect}"
if #temp.to_i < activity.temperatureMax.to_i && #temp.to_i > activity.temperatuureMin.to_i
if #sky == activity.sky || activity.sky == ""
if #day == activity.day
#todaysactivities << activity
end
end
end
end
for activity in #todaysactivities
for attempt in #attemptactivities
if attempt == activity
finaldata << {activity: activity, attempt: "yes"}
else
finaldata << {activity: activity, attempt: "no"}
end
end
end
respond_to do |format|
format.html { render action: "new" }
format.json { render json: #finaldata }
end
The response I get is an empty array but I should be getting 3 rows as a response.
spelling mistake here
activity.temperatuureMin.to_i
And
finaldata << {activity: activity, attempt: "yes"}
should be
#finaldata << {activity: activity, attempt: "yes"}
Also you could be more concise
def getactivityfortoday
#temp = params[:temp]
logger.debug "params temp:#{#temp.inspect}"
#sky = params[:sky]
#day = params[:day]
#activities = Weatherclockactivity.all
#attemptactivities = #user.attempts
#finaldata = #activities.map do |activity|
if (activity.temperatureMin.to_i + 1...activity.temperatureMax.to_i).include?(#temp.to_i) && ( #sky == activity.sky || activity.sky == "") && #day
#attemptactivities.include?(activity) ? {activity: activity, attempt: "yes"} : {activity: activity, attempt: "no"}
end
end.compact
respond_to do |format|
format.html { render action: "new" }
format.json { render json: #finaldata }
end
end
How about something like this?
I tried to make it a balance of readability and conciseness. First we filter for the desired activities. Then we structure the output. This should be easier to debug.
def getactivityfortoday
#temp = params[:temp].to_i
#sky = params[:sky]
#day = params[:day]
#activities = Weatherclockactivity.all
#attemptactivities = #user.attempts
selected_activities = #activities.select do |activity|
# Make sure it's the right temperaure
return false unless (activity.temperatureMin.to_i + 1 ... activity.temperatureMax.to_i).include? #temp
# Make sure the sky matches, or the sky is blank
return false unless (#sky.blank? || #sky.activity == activity.sky)
# Make sure the day matches
return false unless #day == activity.day
# Otherwise, it's good!
return true
end
selected_attempted_activities = selected_activities.map do|activity|
ret = {activity: activity}
ret[:attempt] = #attemptactivities.include?(activity) ? "yes" : "no"
ret
end
respond_to do |format|
format.html { render action: "new" }
format.json { render json: selected_attempted_activities }
end
end
There are a few typos in your original (for instance, #finaldata not finaldata). Make sure that you spell instance variables (things starting with #, like #sky) correctly, since if you try to access an undefined instance variable, it'll silently default to nil.
The best and flexible way is to use ActiveModel::Model
It allows you to use many more useful methods.
it will seems like:
app/models/activity_report.rb
Class ActivityReport
include ActiveModel::Model
attr_accessor :day, :activity # and etc.
validates :day, presence: true
def day
#day.to_s # for example
end
def day=(value)
#day = value - 1.month # for example every date which user set will set on one month ago
end
# and etc
end
app/controllers/posts_controller.rb
...
def index
#activity = ActivityReport.new(params[:activity])
end
def create
#activity.create!
end
...
app/views/posts/index.html.haml
= form_for #activity do |f|
= f.day
For more information you could take a look at:
http://edgeapi.rubyonrails.org/classes/ActiveModel/Model.html
http://railscasts.com/episodes/219-active-model (old)
http://railscasts.com/episodes/416-form-objects (newer, but a little complex)
So I am building an application that matches users. User models have 3 attributes (that are relevant to my question anyways: gender:string, looking_for_men:boolean, looking_for_women:boolean.
currently I've got a method in my model like so:
def browse
if self.looking_for_men == true && self.looking_for_women == true
if self.sex == "Male"
User.where("looking_for_men = ?", true)
elsif self.sex == "Female"
User.where("looking_for_women = ?", true)
end
elsif self.sex == "Male" && self.looking_for_men == true
User.where("looking_for_men = ? AND sex = ?", true, "Male")
elsif self.sex == "Female" && self.looking_for_women == true
User.where("looking_for_women = ? AND sex = ?", true, "Female")
else
if self.sex == "Male"
User.where("looking_for_men = ? AND sex = ?", true, "Female")
elsif self.sex == "Female"
User.where("looking_for_women = ? AND sex = ?", true, "Male")
end
end
end
This is pretty messy, as you can tell. Is there anyway to clean this up and make it into a scope, so that say for example I am a male user, and I am looking for women that it returns only women who are looking for men when I do a query like so:
#users = User.all.browse
I would just do the code below, to make it more readable. But somehow,I'm not totally comfortable with this solution. Still lot of code:
class User < ActiveRecord::Base
scope :male, where(:gender => "Male")
scope :female, where(:gender => "Female")
scope :looking_for_men, where(:looking_for_men => true)
scope :looking_for_women, where(:looking_for_women => true)
def browse
#women = #men = []
#women = self.interested_females if self.looking_for_women
#men = self.interested_males if self.looking_for_men
#result = #women.concat(#men)
#result.delete(self) #removes the user itself from the result-set
return #result
end
def interested_females
return User.female.looking_for_men if self.male?
return User.female.looking_for_women if self.female?
end
def interested_males
return User.male.looking_for_men if self.male?
return User.male.looking_for_women if self.female?
end
def male?
return (self.gender == "Male")
end
def female?
return (self.gender == "Female")
end
end
Just from a scope point of view, you could move that logic into a scope fairly easily just by passing it to a proc.
class User
scope :browse_for, lambda { |user|
user.looking_for_men == true && user.looking_for_women == true
...
}
end
#users = User.browse_for(#single_male)
and you could also chain scopes together to clean up the logic: http://edgerails.info/articles/what-s-new-in-edge-rails/2010/02/23/the-skinny-on-scopes-formerly-named-scope/index.html.
I'm not sure if that quite answers your question?
I have this call in my vote model:
fires :vote_updated, :on => :update,
:actor => :user,
:secondary_subject => :video,
:if => lambda { |vote| ((vote.value == 1) || (vote.value == -1)) && (vote.video.user != current_user)}
In case you aren't familiar, it works with the timeline_fu plugin.
I do not want the call to be fired if the user who owns the voted up video is the current user. That is where this line comes in:
:if => lambda { |vote| ((vote.value == 1) || (vote.value == -1)) && (vote.video.user != current_user)}
However, I do not have access to current_user here. How do I get around this?
Here's the create method in my votes controller (there actually is no update method):
def create
#video = Video.find(params[:video_id])
#vote = current_user.video_votes.find_or_create_by_video_id(#video.id)
if #vote.value.nil?
if params[:type] == "up"
#vote.value = 1
else
#vote.value = -1
end
elsif (params[:type] == "up" && #vote.value == 1) || (params[:type] == "down" && #vote.value == -1)
#vote.value = 0
elsif ((params[:type] == "up" && #vote.value == -1) || (params[:type] == "down" && #vote.value == 1)) || (#vote.value == 0)
if params[:type] == "up"
#vote.value = 1
else
#vote.value = -1
end
end
if #vote.save
respond_to do |format|
format.html { redirect_to #video }
format.js
end
else
respond_to do |format|
format.html
format.js
end
end
end
I believe the right thing to do would be validating this in controller. I would create a before filter for this case
UPDATE:
Just as a quick example:
before_filter :valid_vote, :only => :update
def update
#vote.update_attributes(params[:vote]) # or whatever
end
..
private
def valid_vote
#vote = Vote.find params[:id]
unless ( #vote.video.user.id != current_user.id )
render :text => 'You can't vote for your own video', :status => 403
end
end
So #vote is being declared and validated before your 'update' action is proccessed.
If it's not valid then your 'update' action stays untouched
UPDATE 2 :
not sure how you'll like it, but you could also do as follows:
in your Vote model:
attr_accessor :skip_timeline
then use the concept with before filter, but do #vote.skip_timeline = true instead of rendering text
then the statement might look as follows:
:if => lambda { |vote| ((vote.value == 1) || (vote.value == -1)) && !vote.skip_timeline }
You could also move ((vote.value == 1) || (vote.value == -1)) to your before filter :
def valid_vote
#vote = Vote.find params[:id]
unless ( [1,-1].include? #vote.value && #vote.video.user.id != current_user.id )
#vote.skip_timeline = true
end
end
and
:if => lambda { |vote| !vote.skip_timeline }
You are getting this error because it's typically not recommended to access current_user (or session information) in your model. I am not all that familiar with the timeline_fu gem, so this answer isn't going to be the greatest answer you may get. I'm merely going to show you how to access current_user from any model.
First go to your application controller. You'll want to make a method that sets the current user. You need to call the method in the before filter.
before_filter :loadCurrentUser
def loadCurrentUser
User.currentUser = current_user
end
Then in your User model, you need to define 'currentUser'.
def self.currentUser
Thread.currentUser[:user]
end
You don't necessarily have to declare the current_user in the application controller, but since it's a gem, I'm not sure if it has an easily accessible controller.
Edit: This way may be prone to problems, but I'm not entirely sure if you were asking how to make current_user available in models, or a completely different workaround so you do not have that problem... and reading the responses of the other answer, I'm thinking it's not what you were asking.
I've got some logic in a controller that sets a status of an object if certain conditions are met:
if params[:concept][:consulted_legal] == 0 && params[:concept][:consulted_marketing] == 1
#concept.attributes = {:status => 'Awaiting Compliance Approval'}
elsif params[:concept][:consulted_marketing] == 0 && params[:concept][:consulted_legal] == 1
#concept.attributes = {:status => 'Awaiting Marketing Approval'}
elsif params[:concept][:consulted_marketing] == 0 && params[:concept][:consulted_legal] == 0
#concept.attributes = {:status => 'Awaiting Marketing & Legal Approval'}
else
#concept.attributes = {:status => 'Pending Approval'}
end
that I share between create and update actions. How would you go about refactoring this nastiness? Looking for best practices.
New to programming and keen to clean up my code.
TIA.
You can make your code less dependent on both conditions and make it a lot more flexible.
waiting_on = []
waiting_on << 'Compliance' unless params[:concept][:consulted_marketing]
waiting_on << 'Legal' unless params[:concept][:consulted_legal]
status = waiting_on.empty? ? "Awaiting #{waiting_on.join(' & ')} Approval" : 'Pending Approval'
#concept.attributes = {:status => status}
Version for both create and update without filter:
def create
set_concept_status_attribute
...
end
def update
set_concept_status_attribute
...
end
private
def set_concept_status_attribute
waiting_on = []
waiting_on << 'Compliance' unless params[:concept][:consulted_marketing]
waiting_on << 'Legal' unless params[:concept][:consulted_legal]
status = waiting_on.empty? ? "Awaiting #{waiting_on.join(' & ')} Approval" : 'Pending Approval'
#concept.attributes = {:status => status}
end
Or with a before_filter:
before_filter :set_concept_status_attribute, :only => [:create, :update]
def create
...
end
def update
...
end
If you can move it to you view, it looks even better:
module ConceptHelper
def get_concept_status
...
end
end
<%= get_concept_status %>
This is my take on it. I call it Super DRY.
statuses =
[
['Awaiting Marketing & Legal Approval','Awaiting Compliance Approval'],
['Awaiting Marketing Approval','Pending Approval']
]
{:status => statuses[params[:concept][:consulted_legal].to_i][params[:concept][:consulted_marketing].to_i]}
Alternatively, a more conventional approach -- lengthy but readable:
status = if params[:concept][:consulted_legal] == "0"
if params[:concept][:consulted_marketing] == "1"
'Awaiting Compliance Approval'
else
'Awaiting Marketing & Legal Approval'
end
else
if params[:concept][:consulted_marketing] == "0"
'Awaiting Marketing Approval'
else
'Pending Approval'
end
end
#concept.attributes = {:status => status}
Note: It looks like your original code is checking values of check boxes. Values in the params hash are always Strings, not Fixnums so my code compares strings. If for some reason comparing Fixnums is what is required for your situation, just take out the quotes around the numbers.
That looks to be business logic, so it should be in the model really.
Your model probably needs a couple of attributes: consulted_legal and consulted_marketing, and a method to set the status when either one of them is changed something like this:
before_update :set_status
def set_status
if consulted_legal && consulted_marketing
status = "Pending Approval"
elif consulted_legal && !consulted_marketing
status = "Awaiting Marketing Approval"
elif !consulted_legal && consulted_marketing
status = "Awaiting Legal Approval"
elif !consulted_legal && !consulted_marketing
status "Awaiting Marketing & Legal Approval"
end
true # Needs to return true for the update to go through
end
Break it down into nested if statements.
if params[:concept][:consulted_legal] == '0'
if params[:concept][:consulted_marketing] == '1'
#concept.attributes = { :status => 'Awaiting Compliance Approval' }
else
#concept.attributes = { :status => 'Awaiting Marketing & Legal Approval' }
end
else
if params[:consulted_marketing] == '1'
#concept.attributes = { :status => 'Awaiting Marketing Approval' }
else
#concept.attributes = { :status => "Pending Approval" }
end
end
you may think the list of departments consulted is a fixed enough concept to justify variables named consulted_marketing etc.. But for growth, and dryness (in a way), Id prefer a list of departments.
What you really have here is a workflow or state machine, I think a list of deparments with transitions would result in the cleanest, most growable code.
Code is data ! Model your data and the code will be trivial.
This looks like a business rule to me. As such you might want to wrap it into a function:
string GetConceptStatus(bool consulted_legal, bool consulted_marketing)
{
if (consulted_legal && consulted_marketing) {
return "Pending Approval";
}
if (consulted_legal && !consulted_marketing) {
return "Awaiting Marketing Approval";
}
if (!consulted_legal && consulted_marketing) {
return "Awaiting Legal Approval";
}
if (!consulted_legal && !consulted_marketing) {
return "Awaiting Marketing & Legal Approval";
}
}
This also separates the details of how bools are encoded in your interface from the actual implementation of the business rule.
But the actual structure of the code looks good to me, since it probably better models the business rule.