Eval alternative - ruby-on-rails

I have a bazillion controllers in my app, and I was wondering about using some metaprogramming to make maintenance less of a headache. This works, but it's spiked with danger in the form of eval:
def plural_action(method_name)
class_name = self.class.to_s.gsub( %r{^(\w*)Controller} ) {|s| $1 }
#title = "#{method_name.to_s.titlecase} of #{class_name}"
eval "#q = #{class_name.singularize}.where(:client_id => current_user.client_id).search(params[:q])"
eval "##{class_name.downcase} = #q.result(:distinct => true).paginate(:page => params[:page])"
eval "session[:query] = ##{class_name.downcase}.map(&:id)"
eval "respond_with(##{class_name.downcase})"
end
Can I do this without using eval? I've tinkered with instance_variable_set, send and const_get but no luck so far.
Here's an example of what I'd like the method to eval to.
def index
#title = "Index of Books"
#q = Book.where(:client_id => current_user.client_id).search(params[:q])
#books = #q.result(:distinct => true).paginate(:page => params[:page])
session[:query] = #books.map(&:id)
respond_with(#books)
end

There's a magnificent method constantize which turns a string into the constant (of which class types are an example) it represents. With that in mind, I think you could rewrite your method as:
def plural_action(method_name)
class_name = self.class.to_s.gsub( %r{^(\w*)Controller} ) {|s| $1 }
#title = "#{method_name.to_s.titlecase} of #{class_name}"
#q = class_name.singularize.constantize.where(:client_id => current_user.client_id).search(params[:q])
self.instance_variable_set("##{class_name.downcase}", #q.result(:distinct => true).paginate(:page => params[:page]))
session[:query] = self.instance_variable_get("##{class_name.downcase}").map(&:id)
respond_with(self.instance_variable_get("##{class_name.downcase}"))
end

def plural_action(method_name)
class_name = self.class.to_s.gsub( %r{^(\w*)Controller} ) {|s| $1 }
#title = "#{method_name.to_s.titlecase} of #{class_name}"
#q = class_name.singularize.constantize.where(:client_id => current_user.client_id).search(params[:q])
instance_variable_set class_name.downcase, #q.result(:distinct => true).paginate(:page => params[:page])
session[:query] = #q_result.map(&:id)
respond_with(#q_result)
end

Related

How to optimize queries and remove SQL Injection

I have following query
query = {}
if params[:post_id]
query.merge!('post_id' => "profiles.post_id = '#{params[:post_id]}'")
end
if params[:name].present?
query.merge!('name' => "name = '#{params[:name]}'")
end
if params[:gender].present?
query.merge!('gender' => "gender = '#{params[:gender}' ")
end
ProductPost.includes(:profiles).where("#{query.values.join(' and ') }").references(:profiles)
How to optimize above code and how to remove my SQL Injection.
I'm getting SQL Injection on every value of my HASH.
Thanks.
I would write something like this:
scope = ProductPost.includes(:profiles).references(:profiles)
scope = scope.where(profiles: { post_id: params[:post_id] }) if params[:post_id]
scope = scope.where(name: params[:name]) if params[:name]
scope = scope.where(gender: params[:gender]) if params[:gender]
scope

How do I pass an array to another action in a controller?

I created a array in start for displaying the IDs one by one, and I want to the same array used in another action called next. In start I created a array called ques. I want to use ques in next.
START:
class AnswersController < ApplicationController
def start
#user = current_user
#student = Student.find_by_admission_no(#user.username)
#exam_group = ExamGroup.find_by_id(params[:exam_group_id])
#answer = Answer.new(params[:ans])
#module = params[:student_additional_field]
#questions = shuf_order(#exam_group,#module)
ques = []
#questions.each do |a|
ques.push q.id unless q.id.nil?
end
a = ques.first
#s = 1
#ans = Question.find_by_id(a)
render(:update) do |page|
page.replace_html 'main', :partial => 'ans', :object => #ans
page.replace_html 'quespan', :partial => 'ques'
end
end
Next:
def next
#user = current_user
#student = Student.find_by_admission_no(#user.username)
#exam_group = ExamGroup.find_by_id(params[:exam_group_id])
#answer = Answer.new(params[:ans])
#answer.answer = params[:answer]
#answer.exam_group_id = #exam_group.id
#answer.user_id = #user.id
passed_question = params[:passed_question]
#answer.questions_id = passed_question
#question = Question.find_by_id(passed_question)
#module = Question.find_by_sql ["SELECT student_additional_field_id FROM questions WHERE id=#{passed_question}"]
student_additional_field_id = #module[0].student_additional_field_id
#questions = shuf_order(#exam_group,student_additional_field_id)
a = #questions.first
#answer.modules_id = student_additional_field_id
if #answer.save
#ans = Question.find_by_id(a, :conditions => [' id not in (?)',answered])
unless #ans.nil?
render(:update) do |page|
page.replace_html 'main', :partial => 'ans', :object => #ans
end
else
render(:update) do |page|
page.replace_html 'main', :partial => 'ans2'
end
end
end
end
The usual way to pass variables between actions is the flash variable.
In start:
flash[:ques] = []
flash[:ques].push 'whatever'
In next:
flash[:ques]
to access the saved var.
But in your case maybe you want to save your ques in session or DB to use ques in more than these 2 actions:
In start:
session[:ques] = []
session[:ques].push 'whatever'
In next:
session[:ques]
Or in DB, you maybe would need a new table.

rails apply where if variable is not nil else apply all - nicer way

How to optimize/refactor this rails code,
I want to apply where condition to the Country and the City if the co is not nil, if its nil, then apply all.
def pre(co = nil,ci = nil)
cond1 = co.nil? ? "all" : "where(:id => co)"
cond2 = ci.nil? ? "all" : "where(:id => ci)"
#countries = Country.send(cond1).order(:name).map{|i| [i.name,i.id]}
#cities = City.send(cond2).order(:name).map{|i| [i.name,i.id]}
end
Is it a nice way or is there a nicer way?
#countries = Country.where(co.nil? || {:id => co}).order(:name).map{|i| [i.name,i.id]}
#cities = City.where(ci.nil? || {:id => ci}).order(:name).map{|i| [i.name,i.id]}
You can try something like this
#country = Country
#city = City
if co.blank?
#country = #country.where(:id => co)
end
if ci.blank?
#city = #city.where(:id => ci)
end
#countries = #country.order(:name).all.map{|i| [i.name,i.id]}
#cities = #city.order(:name).all.map{|i| [i.name,i.id]}
You would think about something like this.
def pre(co = nil, ci = nil)
#countries = scopify(Country, co)
#cities = scopify(City, ci)
end
def scopify(model_or_scope, attribute)
scope = model_or_scope.scoped
scope = scope.where(:id => attribute) if attribute.present?
scope.order(:name).map { |s| [s.name, s.id] }
end

Why am I getting undefined method 'save' error?

My code is this
def footstamp
if current_user
#user = User.find(params[:id])
#tracking = Tracking.where(:user_id => current_user.id, :target_user_id => #user.id)
if #tracking
#tracking.accessed_at = Time.now
#tracking.save
else
#tracking = Tracking.new
#tracking.user_id = current_user.id
#tracking.target_user_id = #user.id
#tracking.accessed_at = Time.now
#tracking.save
end
end
end
Then I get this error
NoMethodError (undefined method `save' for []:ActiveRecord::Relation):
Use:
#tracking = Tracking.where(:user_id => current_user.id, :target_user_id => #user.id).first
You are getting this error because the result of your method do not return a Tracking, but an array of Trackings.
Either you introduce more conditions to match only one Tracking, or use the first method or iterate over the results,
To use first:
#tracking = Tracking.where(:user_id => current_user.id, :target_user_id => #user.id).first
Iterate:
Tracking.where(:user_id => current_user.id, :target_user_id => #user.id).each do |tracking|
if tracking
tracking.accessed_at = Time.now
tracking.save
else
tracking = Tracking.new
tracking.user_id = current_user.id
tracking.target_user_id = #user.id
tracking.accessed_at = Time.now
tracking.save
end
end
Optimised answer,
def footstamp
if current_user
#user = User.find(params[:id])
#tracking = Tracking.where(:user_id => current_user.id, :target_user_id => #user.id).first_or_create
#tracking.accessed_at = Time.now
#tracking.save
end
end
More usefull methods here

RoR - undefined local variable or method `user'

I am fairly still new to ruby on rails and don't fully understand why I am getting the following error:
undefined local variable or method `user' for #<StatisticsController:0xb9a20d0>
The code:
class StatisticsController < ApplicationController
before_filter :authenticate, :only => [:index]
def index
#title = "Statistics"
#projects = Project.all
#data = []
Project.all.each do |project|
projdata = { 'name' => project.project_name.to_s,
'values' => [] }
['Pre-Sales','Project','Fault Fixing','Support' ].each do |taskname|
record = Effort.sum( :hours,
:joins => {:project_task => {:efforts => :user}},
:conditions => { "project_tasks.efforts.user_id" => user.id,
"project_tasks.project_id" => project.id,
"project_tasks.task_name" => taskname } )
projdata[ 'values' ].push( record )
end
#data.push( projdata )
end
end
end
Update
class StatisticsController < ApplicationController
before_filter :authenticate, :only => [:index]
def index
#title = "Statistics"
#projects = Project.all
#data = []
User.all.each do |user|
projdata = { 'name' => user.user_id.to_s,
'values' => [] }
['Pre-Sales','Project','Fault Fixing','Support' ].each do |taskname|
user = User.all
record = Effort.sum( :hours,
:joins => {:project_task => {:efforts => :user}},
:conditions => { "project_tasks.efforts.user_id" => user.id,
"project_tasks.project_id" => project.id,
"project_tasks.task_name" => taskname } )
projdata[ 'values'].push( record )
end
#data.push( projdata )
end
end
end
In string :conditions => { "project_tasks.efforts.user_id" => user.id, you call id for user object, but it is not instantiated in code above.
Your update doesn't loop over the users at all; user is now a collection of all the users. You need to iterate over the users if you want to get individual statistics for individual users.
Are you using devise? Use current_user instead of user.
Fix of your code:
User.all.each do |user|
projdata = { 'name' => user.user_id.to_s,
'values' => [] }
['Pre-Sales','Project','Fault Fixing','Support' ].each do |taskname|
record = Effort.sum( :hours,
:joins => {:project_task => {:efforts => :user}},
:conditions => { "project_tasks.efforts.user_id" => user.id,
"project_tasks.project_id" => project.id,
"project_tasks.task_name" => taskname } )
projdata[ 'values'].push( record )
end
#data.push( projdata )
end
So: removed the rogue user=User.all :)
Question: in 1 place you write user.user_id and in the other you write user.id. Is that correct?

Resources