I have the following in my controller that will assign a different collection of results depending on what params are received with an Ajax call. It is messy and i would like to just call a function with all the logic in rather than all this in my index controller
class PublicController < ApplicationController
def index
if params[:literacy_param].present?
#skills = Skill.search(params)
elsif params[:numeracy_param].present?
#skills = Skill.numeracy_default_params
elsif params[:numeracy_number_skills].present?
#skills = Skill.numeracy_number_skills
elsif params[:numeracy_measuring_skills].present?
#skills = Skill.numeracy_measuring_skills
elsif params[:numeracy_data_skills].present?
#skills = Skill.numeracy_data_skills
else
#skills = Skill.default_params
end
end
end
Im just a bit unsure on how to set out my function so that it can read the params that are being sent,
I have come up with this so far
private
def skills(params)
if params[:literacy_param].present?
#skills = Skill.search(params)
elsif params[:numeracy_param].present?
#skills = Skill.numeracy_default_params
elsif params[:numeracy_number_skills].present?
#skills = Skill.numeracy_number_skills
elsif params[:numeracy_measuring_skills].present?
#skills = Skill.numeracy_measuring_skills
elsif params[:numeracy_data_skills].present?
#skills = Skill.numeracy_data_skills
else
#skills = Skill.default_params
end
end
Then in my index action i would do
#skills = skills(params)
would this be an efficient way?
Thanks
You can do this
class PublicController < ApplicationController
def index
skills = ['literacy_param', 'numeracy_param', 'numeracy_number_skills', 'numeracy_measuring_skills', 'numeracy_data_skills']
common_in_params = (skills & params).first
#skills = common_in_params.present? ? (common_in_params.eql?('literacy_param') ? Skill.search(params) : Skill.send(common_in_params)) : Skill.default_params
end
end
You can define skills array in an initializer for resusability
One way of doing it would be this:
def skills(params)
set_of_skills = params.slice(
:numeracy_param,
:numeracy_number_skills,
:numeracy_measuring_skills,
:numeracy_data_skills,
).first
#skills = if params[:literacy_param]
Skill.search(params)
elsif set_of_skills
Skill.public_send(set_of_skills)
else
Skill.default_params
end
end
I would also advise to have this extracted into a lib/ folder, and unit-tested. So that in your controller you could perform the following:
def index
#skills = SkillSearch.new(params).search
end
Two ways I can think of doing this right now:
Wrap the params in a unique key. As in params = { :keyword => :literacy_param }, and then use this unique key to identify the right operation.
In you skill.rb:
def self.filter(params)
if params[:keyword] == :literacy_param
search(params)
elsif available_filters.include?(params[:keyword])
public_send(params[:keyword])
else
default_params
end
end
private
def self.available_filters
%i{numeracy_default_params numeracy_number_skills numeracy_measuring_skills numeracy_data_skills}
end
considering that instead of :numeracy_param, you send :numeracy_default_params in :keyword key. Otherwise you'll have to make another elsif inside filter method.
then in your index method:
def index
#skilles = Skill.filter(params)
end
You create a separate filter class, which is an expandable solution, just in case when you need to go for complex search queries and filtering.
Let's call it SkillSeacrher, inside you app/models/skill_searcher.rb:
class SkillSearcher
attr_reader :keyword
def initialize(keyword)
#keyword = keyword
end
def filter
if keyword == :literacy_param
Skill.search(params)
elsif available_filters.include?(keyword)
Skill.public_send(keyword)
else
Skill.default_params
end
end
private
def self.available_filters
%i{numeracy_default_params numeracy_number_skills numeracy_measuring_skills numeracy_data_skills}
end
end
then in index method:
def index
#skills = SkillSearcher.new(params[:keyword]).filter
end
However, you can do one more change to filter method(depends on your taste):
def filter
if keyword == :literacy_param
Skill.search(params)
else
Skill.public_send(available_filters.include?(keyword) ? keyword : :default_params)
end
end
And, if you have all these methods accepting params as arguments then it'd be much more sleek:
def filter
Skill.public_send(available_filters.include?(keyword) ? keyword : :default_params, params)
end
Related
I'm learning how to use ransack, so I have a problem there I'm not sure if it is because the ransack or if it is because the array.
I have a form with 2 text fields (:search and :discipline). So I'm trying do a search using the 1º field parameter AND the 2º field parameter.
The idea is search for all elements that comes from the 1º parameter (field :search, and then remove all the elements that are different from the 2º parameter (field :discipline).
class PagesController < ApplicationController
def home
#rooms = Room.limit(6)
end
def search
if params[:search].present? && params[:search].strip != ""
session[:loc_search] = params[:search]
end
if params[:discipline].present? && params[:discipline].strip != ""
session[:loc_discipline] = params[:discipline]
end
arrResult = Array.new
if session[:loc_search] && session[:loc_search] != ""
#rooms_address = Room.where(active: true).near(session[:loc_search], 5, order: 'distance')
else
#rooms_address = Room.where(active: true).all
end
#search = #rooms_address.ransack(params[:q])
#rooms = #search.result
#arrRooms = #rooms.to_a
if (session[:loc_discipline] && !session[:loc_discipline].empty?)
#rooms.each do |room|
not_available = Room.where(
"(room_type != ?)",
session[:loc_discipline]
)
if not_available.length > 0
#arrRooms.delete(room)
end
end
end
end
end
My #arrRooms is returning NULL after I try do this #arrRooms.delete(room).
I dont know if have a better way to do this, but I'm trying do it like a tutorial that I found.
I assume that you're trying to show all rooms that are not available?
I think the best strategy is to load what you really want, and not loading everything an then deleting the things you don't need. Your code is really hard to read, I suggest you take a little tutorial like this: http://tryruby.org/levels/1/challenges/0, or this: https://www.codeschool.com/courses/ruby-bits
Try extracting code like where(active: true) into a scope like:
class Room < ActiveRecord::Base
scope :active, -> { where(active: true) }
scope :available, -> (discipline) { where.not(room_type: discipline) }
end
In your controller you can then make this:
def index
#rooms = Room.active.available(params[:discipline])
search_param = params[:search].present?
if search_param.present?
#rooms = #rooms.near(session[:loc_search], 5, order: 'distance')
end
#rooms = #rooms.ransack(params[:q]).result(distinct: true)
end
This is what I could guess out of your code.
Hi I create a controller Game to display a Q/A game
And I am blocked with <<, here is the code
def play
lvlup(lvl)
if lvl == 1
set_questions
else
get_questions
end
#answers = Answer.where.not(id: question.answer_id).limit(2).order("RANDOM()")
#answer ||= []
#answers << question.answer
#answers = #answers.shuffle
render 'play'
end
I create an array and I put the related answer in the global answers I want to display 4 Max.
Why does the undefined is here?
Here is the total code
class GamesController < ApplicationController
attr_accessor :lvl
def welcome
end
def congrat
end
def play
lvlup(lvl)
if lvl == 1
set_questions
else
get_questions
end
#answers = Answer.where.not(id: question.answer_id).limit(2).order("RANDOM()")
#answer ||= []
#answers << question.answer
#answers = #answers.shuffle
render 'play'
end
def loose
#question = Question.find(params[:question])
flash.now[:alert] = "Miss..."
render 'loose'
end
def check
#lvl = params[:lvl].to_i
answer_id = params[:id].to_i
question = Question.find(params[:question])
if #lvl == lvlmax
render action: 'congrat' and return
elsif answer_id == question.answer_id
flash.now[:notice] = "Well done !"
play
else answer_id != question.answer_id
loose
end
end
private
def lvlup(value)
#lvl = 1 + value.to_i
end
def lvlmax
#lvlmax = Question.all.count
end
def set_questions
#questionsids = []
Question.all.shuffle.each do |d|
#questionsids << d.id
end
cookies[:questions] = #questionsids
end
def get_questions
#questions = cookies[:questions].split('&')
end
def questions
#questions = cookies[:questions]
end
def question
#question = Question.find(questions[lvl])
end
end
Thank you for your help.
You are trying to append to the #answers result - this is an ActiveRecord relation, you cannot append data to that array.
Add .to_a in the end of your line where you set #answers to allow you to append to the array.
#answers = Answer.where.not(id: question.answer_id).limit(2).order("RANDOM()").to_a
mtrolle's answer might be correct, but I have my doubts as to why ActiveRecord::Relation was not returned as Array by default. (Also as mentioned by BroiStatse in his comment.)
I too noticed the same problem with my local setup however it was attributed to another issue all together. I am sharing this here in case you too happen to use MySQL.
Answer.where.not(id: question.answer_id).limit(2).order("RANDOM()")
returns an ActiveRecord::Relation object. And it translates to following SQL:
SELECT `answers`.* FROM `answers` WHERE (id != ID) ORDER BY RANDOM() LIMIT 2
When I try running the same in MySQL, I get:
ERROR 1305 (42000): FUNCTION database.RANDOM does not exist
Apparently MySQL does not have RANDOM() function, instead it uses RAND().
Converting ActiveRecord query accordingly returned correct Array to me:
Answer.where.not(id: question.answer_id).limit(2).order("RAND()")
Hi in the following code although the where query inside create method i.e. variable #count_of_fav_texts_present fetches 1 record in the rails console, but in controller the value of #count_of_fav_texts_present is zero and going inside the first if clause.
Also in the internal if clause the query for find_by i.e variable var_fav_text is giving NilClass. Although when I check it in console the value is not Nil, and it has one record.
I am very new to Rails and I am not sure what mistake I am making. Please help.
class NewfavoriteTextsController < ApplicationController
before_action :set_text
before_action :set_favgroup
before_action :authenticate_user!
def create
#count_of_fav_texts_present = Favorite.where(favorited_id: #text_id, user_id: current_user.id).count
if #count_of_fav_texts_present == 0
if Favorite.create(favorited: #text, user: current_user)
if Newfavorite.create(favorite_group_id: #fav_group, newfavorited: #text)
var_fav_text = Favorite.find_by(favorited_id: #text_id, user_id: current_user.id)
cnt_of_var = var_fav_text.counter
var_fav_text.counter = cnt_of_var + 1
var_fav_text.save
else
# do something
end
else
# do something
end
else
# for condition when var is greater than 0
if Newfavorite.create(favorite_group_id: #fav_group, newfavorited: #text)
var_fav_text = Favorite.find_by(favorited_id: #text_id, user_id: current_user.id)
cnt_of_var = var_fav_text.counter
var_fav_text.counter = cnt_of_var + 1
var_fav_text.save
else
# do something
end
end
end
def destroy
# do something
end
private
def set_text
#text = Text.find(params[:text_id] || params[:id])
end
def set_favgroup
#fav_group = params[:fav_group_id]
end
end
Thanks in advance.
I have two methods in two different controllers (Posts & Boards). They are almost same. The difference is only model-instance-association name. To DRY this I think to write the method in module, but how to share it between Post and Board?
def init_post_comments
#user = current_user
a = #user.posts.pluck(:id) # not very nice...
b=params[:post_ids] ||= []
b = b.map(&:to_i)
follow = b - a
unfollow = a - b
follow.each do |id| # checkbox just checked
#post = Post.find_by_id(id)
if #post.users.empty?
#post.update_attribute(:new_follow, true)
end
#user.posts << #post
end
unfollow.each do |id| # if checkbox was unchecked
#post = Post.find_by_id(id)
remove_post_from_user(#post)# here we destroy association
end
if follow.size > 0
get_post_comments_data
end
redirect_to :back
end
UPDATE Ok, if I'll move the methods to model's concern how I should work with associations here? Here #user.posts.pluck(:id) and here #user.boards.pluck(:id) with what I can replace posts and boards so it can work with both of them?
So, I did it! I don't know if it's right way, but I DRY this code.
Two controllers:
posts_controller.rb
def init_comments
if Post.comments_manipulator(current_user, params[:post_ids] ||= []) > 0
#posts = Post.new_post_to_follow
code = []
#posts.each do |post|
group = post.group
code = code_constructor('API.call')
end
Post.comments_init(get_request(code), #posts)
end
redirect_to :back
end
boards_controller.rb
def init_comments
if Board.comments_manipulator(current_user, params[:board_ids] ||= []) > 0
#boards = Board.new_board_to_follow
code = []
#boards.each do |board|# подготовка запроса
group = board.group
code = code_constructor('API.call')
end
Board.comments_init(get_request(code), #boards)
end
redirect_to :back
end
As you can see they are absolutely same.
In models board.rb and post.rb - include CommentsInitializer
And in models\concerns
module CommentsInitializer
extend ActiveSupport::Concern
module ClassMethods
def comments_manipulator(user, ids)
relationship = self.name.downcase + 's'
a = user.send(relationship).pluck(:id)
b = ids.map(&:to_i)
follow = b - a
unfollow = a - b
follow.each do |id| # start to follow newly checked obj
#obj = self.find_by_id(id)
if #obj.users.empty?
#obj.update_attribute(:new_follow, true)
end
user.send(relationship) << #obj
end
unfollow.each do |id| # remove from following
#obj = self.find_by_id(id)
remove_assoc_from_user(#obj, user)#destroy relation with current user
end
follow.size
end
def comments_init(comments, objs)
i = 0
objs.each do |obj| # updating comments data
if comments[i]['count'] == 0
obj.update(new_follow: false)
else
obj.update(new_follow: false, last_comment_id: comments[i]['items'][0]['id'])
end
i += 1
end
end
def remove_assoc_from_user(obj, user)
user = user.id
if user
obj.users.delete(user)
end
end
end
My code works. If you know how to make it better please answer!
I am working on an events application where i want to filter events depending on the 3 parameters location or starts_at or ends_at in the query string. There can be any one, two or all the parameters in the query string. In i use if-else statement i need to make 6 cases which will make my code clumsy. Rather i am thinking to implement something this way:
class EventsController < ApplicationController
def index
unless params.empty?
unless params[:location].nil?
#events = Event.where("location = ?", params[:location])
end
unless params[:starts_at].nil?
unless #events.empty?
#events = #events.where("start_date = ?", params[:start_date])
else
#events = Event.where("Date(starts_at) = Date(?)", params[:starts_at])
end
end
unless params[:ends_at].nil?
unless #events.empty?
#events = #events.where("end_date = ?", params[:end_date])
else
#events = Event.where("Date(ends_at) = Date(?)", params[:ends_at])
end
end
end
end
end
But this code doesnt work since where query doen not work on an array. Can someone suggest me some solution for this..
You should be able to pass your params hash directly to where, and it will form the correct SQL based on the keys and values of that hash:
Event.where(params)
An example in the console:
1.9.3p194 :001 > puts Example.where(:location => 'here', :started_at => '2012-08-13').to_sql
SELECT "examples".* FROM "examples" WHERE "examples"."location" = 'here' AND "examples"."started_at" = '2012-08-13'
Try Following
def index
unless params.empty?
where_array, arr = [], []
if params[:location]
where_array << "location = ?"
arr << params[:location]
end
if params[:starts_at]
where_array << "start_date = ?"
arr << params[:starts_at]
end
if params[:ends_at]
where_array << "end_date = ?"
arr << params[:ends_at]
end
#events = arr.blank? ? [] : Event.where([where_array.join(" AND "), *arr])
end
end