Rails Ransack - removing element from array - ruby-on-rails

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.

Related

How to construct where clause in ruby using if

I am finding something like below. Constructing a where clause using condition. Is it possible in ruby? or I need to separate it into two where clause?
Post
.where(tag: "A") if condition A
.where(tag: "B") if condition B
.where(user_id: 1)
.order(....)
Actually, my case is like this. Is there any way to handle?
def this_function
#questions = Question.joins(:comment_threads)
.tagged_with(tag_variable, wild: true, any: true) if tag_variable.present?
.where(index_where_clause)
.where("questions.created_at < ?", query_from_date_time)
.order(created_at: :desc).limit(5)
end
def index_where_clause
where_clause = {}
where_clause[:user_detail_id] = current_user_detail.id if params[:type] == "my_question"
where_clause[:comments] = {user_detail_id: current_user_detail.id} if params[:type] == "my_answer"
where_clause[:wine_question_score_id] = params[:wine_question_score_id] if params[:wine_question_score_id].present?
where_clause
end
The methods you're using return relations so you can say things like this:
#questions = Question.joins(:comment_threads)
#questions = #questions.where("questions.created_at < ?", query_from_date_time)
#questions = #questions.tagged_with(tag_variable, wild: true, any: true) if tag_variable.present?
#questions = #questions.where(:user_detail_id => current_user_detail.id) if params[:type] == "my_question"
#questions = #questions.where(:comments => { user_detail_id: current_user_detail.id}) if params[:type] == "my_answer"
#questions = #questions.where(:wine_question_score_id => params[:wine_question_score_id]) if params[:wine_question_score_id].present?
#questions = #questions.order(created_at: :desc).limit(5)
and build the query piece by piece depending on what you have in params.
I'd probably break it down a little more:
def whatever
#questions = Question.joins(:comment_threads)
#questions = #questions.where("questions.created_at < ?", query_from_date_time)
#questions = with_tag(#questions, tag_variable)
#...
#questions = #questions.order(created_at: :desc).limit(5)
end
private
def with_tag(q, tag)
if tag.present?
q.tagged_with(tag, wild: true, any: true)
else
q
end
end
#...
and bury all the noisy bits in little methods to make things cleaner and easier to read. If you're doing this more than once then you could use scopes to hide the noise in the model class and re-use it as needed.
#tap can be helpful for modifying an object in place to apply conditional logic, in this case the object would be your .where conditions:
Post
.where(
{ user_id: 1 }
.tap do |conditions|
conditions[:tag] = 'A' if condition A
conditions[:tag] = 'B' if condition B
end
)
.order(...)
Or, perhaps it's a little cleaner if you create a helper method:
def specific_conditions
{ user_id: 1 }.tap do |conditions|
conditions[:tag] = 'A' if condition A
conditions[:tag] = 'B' if condition B
end
end
Post.where(specific_conditions).order(...)
But as a side note, if there's a case where condition A and condition B can both be true, the second conditions[:tag] = ... line will override the first. If there is not a case where both can be true, you might try to use some kind of collection to look up the proper value for tag.
CONDITION_TAGS = {
a: 'A'.freeze,
b: 'B'.freeze,
}.freeze
def specific_conditions
{ user_id: 1 }
.tap do |conditions|
conditions[:tag] = CONDITION_TAGS[condition_value] if condition_value
end
end
Post.where(specific_conditions).order(...)
#in Question class
scope :with_user_detail, -> (user_detail_id, flag=true) do
where("user_detail_id = ?", user_detail_id) if flag
end
scope :with_user_detail_comments, -> (user_detail_id, flag=true) do
joins(:comment_threads).where("comments.user_detail_id = ?", user_detail_id) if flag
end
scope :with_wine_question_score, -> (wine_question_score_id) do
where("wine_question_score_id = ?", wine_question_score_id) if wine_question_score_id.present?
end
scope :tagged_with_condition, -> (tag_variable, wild, any) do
tagged_with(tag_variable, wild, any) if tag_variable.present?
end
def this_function
my_question_flag = params[:type] == "my_question"
my_answer_flag = params[:type] == "my_answer"
Question.with_user_detail(current_user_detail.id, my_question_flag)
.tagged_with_condition(tag_variable, wild: true, any: true)
.with_user_detail_comments(current_user_detail.id, my_answer_flag)
.with_wine_question_score(params[:wine_question_score_id])
.order(created_at: :desc).limit(5)
end
You can do the following:
condition = {:tag => "A"} if condition A
condition = {:tag => "B"} if condition B
Post
.where(condition)
.where(:user_id => 1)
.order(....)
you have to use scope :
scope :my_scope, -> (variable) { where(some: vatiable) if my_condition }

How to handle multiple conditions of instance variable assignment

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

How to remove current_user from index of all users?

In otherwords, in my app I have an index that displays the number of total users I'd like to exclude the current_user from this list..
I've used the following code to prevent the user.name and user.profile_photo from showing successfully:
<% unless user.hidden_for?(current_user) || user.blocked_for?(current_user) || user == current_user %>
However, it still says Showing 2 users when it only shows the profile photo and name of the other user, it should really only be saying Showing one user and not include me there.
Here is the line of code that ultimately needs the fix:
<h4 class="events_counter">
<%= #users.any? ? "Showing #{pluralize(#users.size, 'people')}" : "No users to show" %>
</h4>
Any help would be awesome!!
Cheers!
Update: (Here's the current relevant index action)
class ProfilesController < ApplicationController
before_filter :authenticate_user!
def index
if params[:search]
terms = params[:search][:terms] || nil
min_age = params[:search][:age_min] || nil
max_age = params[:search][:age_max] || nil
zipcode = params[:search][:zipcode] || nil
distance = params[:search][:distance] || nil
education_id = params[:search][:education_id] || nil
# #ethnicity_id = params[:search][:ethnicity_id] || nil
ethnicity_ids = params[:search][:cat_ids].split('').uniq || nil
gender = params[:search][:gender] || nil
#users = User.active.scoped_by_search(terms, min_age, max_age, education_id, ethnicity_ids, gender)
else
#users = User.active.page params[:page]
end
end
With Rails 4.x,
Assuming that your action name is index where you set the #users instance variable, you could do something like this:
def index
#...
#users = User.active.scoped_by_search(terms, min_age, max_age, education_id, ethnicity_ids, gender).where.not(id: current_user.id)
else
#users = User.active.where.not(id: current_user.id).page params[:page]
#...
end
Here, if you have a current_user then #users will contain all the users except the current_user. Otherwise, #users will have all the users.
Also, you don't require any checks in your view, i.e.,
<% unless user.hidden_for?(current_user) || user.blocked_for?(current_user) || user == current_user %>
is no longer required so you can remove it safely.
Also, #users.size would give the correct count of other users (excluding current_user).
Recommend excluding current_user in the query instead of view. For example in controller:
#users = User.where('id != ?', current_user.id)
However, if you need to keep the current user in the #users array then, use reject to exclude the current user as:
#users.reject { |u| u.id == current_user.id }
I'd suggest building a new array of visible_users somewhere else.
Here is a way to get that array based on your current logic and array.
visible_users = #users.reject{|user| user.hidden_for?(current_user) || user.blocked_for?(current_user) || user == current_user}
Hopefully that helps.

Rails controller index exclude records

I have the following index in workorder controller:
def index
#workorders = Workorder.scoped
#workorders = Workorder.where("created_at > #{params[:after]}") if params[:after].present?
end
How would I add the following so that it becomes an AND. I want it to only include a workorder if maxsync <> 'E'. Even if the parameter :after is present or not.
I tried this:
#workorders = Workorder.scoped.where("maxsynch != 'E' ")
Thanks for the help!
You can chain scopes like this:
def index
#workorders = Workorder.scoped.where("maxsynch != 'E' ")
#workorders = #workorders.where("created_at > ?", params[:after]) if params[:after].present?
#examples:
#workorders = #workorders.where(parent_id: params[:parent_id]) if params[:parent_id].present?
#workorders = #workorders.active if params[:filter_active].present?
end

Refactoring a complex filter in Rails

I'm trying to deal with a somewhat complicated query. I've read a few methods on how I might approach this but they don't really apply here because this isn't like a complicated search form (like on a vBulletin search post form), but rather a set of routes which filter both by 'category' (unreleased, popular, latest) and by 'time' (all time, last month, last week, today)
I realize the below code is very bad. My goal was only to get it working, and refactor after. Not to mention, it doesn't even truly work because it doesn't take into account BOTH category AND time, just one or the other, but I figured I would deal with that in this thread.
Also, to make this a lot clearer for this SO code paste, I excluded the .page(params[:page]).per(30) from every single line, however it needs to go on all of them.
So, does anyone know how I might go about doing this? I have mulled over it for some time and am kind of stumped
def index
case params[:category]
when "latest"
#books = Book.all.page(params[:page]).per(30)
when "downloads"
#books = Book.order('downloads DESC')
when "top100"
#books = Book.order('downloads DESC').limit(100)
when "unreleased"
#books = Book.unreleased
else
#books = Book.all.page(params[:page]).per(30)
end
case params[:time]
when "today"
#books = Book.days_old(1)
when "week"
#books = Book.days_old(7)
when "month"
#books = Book.days_old(30)
when "all-time"
#books = Book.all
else
#books = Book.all.page(params[:page]).per(30)
end
end
Routes:
# Books
get 'book/:id', to: 'books#show', as: 'book'
resources :books, only: [:index] do
get ':category/:time(/:page)', action: 'index', on: :collection
end
Move all queries to the model as scopes
class Book < ActiveRecord::Base
scope :downloads, -> { order('downloads DESC') }
scope :top100, -> { order('downloads DESC').limit(100) }
scope :unreleased, -> { unreleased }
scope :today, -> { days_old(1) }
scope :week, -> { days_old(7) }
scope :month, -> { days_old(30) }
scope :latest, -> { }
scope :all_time, -> { }
end
Create auxiliary methods to filter the params and avoid unmatching data
class BooksController < ApplicationController
private
def category_params
%w(downloads top100 unreleased).include?(params[:category]) ? params[:category].to_sym : nil
end
def time_params
%w(today week month latest all_time).include?(params[:time]) ? params[:time].to_sym : nil
end
end
Get rid of the case statement by applying the scope with the same name as the params
def index
query = Book.all
query = query.send(category_params) if category_params
query = query.send(time_params) if time_params
#books = query.page(params[:page]).per(30)
end
At four lines we're still within the boundaries of Sandi Metz' guidelines! :)
In rails you can 'chain' queries, for example
Book.where(:released => true).where(:popular => true)
is the same as
Book.where(:released => true, popular => true)
You can use this to help with your refactoring. Here is my take on it:
def index
# Start with all books, we are going to add other filters later
query = Book.scoped
# Lets handle the time filter first
query = query.where(['created_at > ?', start_date] if start_date
case params[:category]
when "latest"
query = query.order('created_at DESC')
when "downloads"
query = query.order('downloads DESC')
when "top100"
query = query.order('downloads DESC').limit(100)
when "unreleased"
query = query.where(:released => false)
end
# Finally, apply the paging
#books = query.page(params[:page]).per(30)
end
private
def start_date
case params[:time]
when "today"
1.day.ago
when "week"
7.days.ago
when "month"
1.month.ago
when "all-time"
nil
else
nil
end
end

Resources