Alternative to using 'order' within an array - ruby-on-rails

I've been doing some edits to an output when working with some records and i've ran into a problem where the sorting option no longer is working.
Initially with how things were created when I would do a #record.class the output was Record::ActiveRecord_Relation < ActiveRecord::Relation
However now after making my changes the class has changed. So now when I run a #record.class the output is Array < Object.
I think this is the root reason why i've ran into this problem.
My code is
#q = #records.search(params[:q])
#records = #q.result(distinct: true).select { |k,_v| k[:disabled] }
#records = #records.order("records.updated_at desc").page(params[:page] || 1).per(params[:per] || 30)
The error I am getting is NoMethodError: undefined methodorder' for #`
Now i've tried to workout around this by
#records = #records.sort_by("records.updated_at desc")
#records = #records.paginate.page(params[:page] || 1).per(params[:per] || 30)
However the error with the first line of my solution is #records = #records.sort_by("records.updated_at desc") I'm not able to get past that line in case there is an error with my paginate solution.
If anyone could take a look, I would really appreciate it!

your #records is a Ruby array, not an ActiveRecord "array"
You can try #records.sort_by(&:updated_at) to sort your array

The issue is that you are calling result, which is causing the SQL query to be executed and data returned from the database. You don't want to do that if you want to continue forming your query using the Arel AST that ActiveRecord uses. You want something closer to this (you'll likely need to adapt):
r = records.where(some_param: some_value, disabled: true) \
.order(updated_at: :desc) \
.page(params[:page] || 1).per(params[:per] || 30)

The signature you the methods you use look like you are using ransack.
Instead of loading all records into memory and selecting matching records in Ruby it might be much faster to only load matching records from database in the first place.
Assuming that disabled is a boolean column in your database, I would advise to change
#q = #records.search(params[:q])
#records = #q.result(distinct: true).select { |k,_v| k[:disabled] }
#records = #records.order("records.updated_at desc").page(params[:page] || 1).per(params[:per] || 30)
to
#q = #records.where(disabled: true).search(params[:q])
#records = #q.result(distinct: true)
#records = #records.order("records.updated_at desc").page(params[:page] || 1).per(params[:per] || 30)

Related

Using where search clause after select

I'm new to rails and I'm adding some updates to already existing app. I need to add filtering by search word and I'm trying to use where with ILIKE to do that but due to the original code is written. the original code uses Model.select to get the records and when i try to chain where with the ILIKE clause to that it still returns the whole array not filtered.
date_format = get_js_date_format_by_id(#account.date_format_id)
sort_format = "#{params[:sort_item] || "date"} #{params[:sort_order] || "asc"}"
#expenses = Expense.select("expenses.*, to_char(expenses.date, '#{date_format}') as formatted_date,
to_char(
CASE
WHEN expense_categories.is_calculated = true then expenses.amount * expense_categories.unit_cost
ELSE expenses.amount
END, 'FM999999990.00') as total,
CASE
WHEN expense_categories.is_calculated = true then expense_categories.unit_name
ELSE null
END as unit_name,
'' as images_list,
users.name as user_name, expense_categories.name as category_name, projects.name as project_name, clients.name as client_name")
.joins(:user, :project, :expense_category, :client)
if params[:query]
#expenses.includes(:user).where('users.name ILIKE :query', { query: "%#{params[:query]}%"}).references(:users)
end
#expenses.where(conditions)
.order(sort_format)
if params[:page]
#expenses = #expenses.page(params[:page]).per(params[:per_page])
end
#expenses
I'm adding the filtering part starting at if params[:query] the rest is the original code. any help would be appreciated to pin pointing the problem.
thanks!
The problem is that you don't modify the current #expenses scope with:
if params[:query]
#expenses.includes(:user).where('users.name ILIKE :query', { query: "%#{params[:query]}%"}).references(:users)
end
This line returns the new scope you want, but you are not saving it anywhere. Following code should work:
if params[:query]
#expenses = #expenses.includes(:user).where('users.name ILIKE :query', { query: "%#{params[:query]}%"}).references(:users)
end
Note: same thing applies for the sorting under the params[:query] condition.

ActiveRecord how to use Where only if the parameter you're querying has been passed?

I'm running a query like the below:
Item.where("created_at >=?", Time.parse(params[:created_at])).where(status_id: params[:status_id])
...where the user can decide to NOT provide a parameter, in which case it should be excluded from the query entirely. For example, if the user decides to not pass a created_at and not submit it, I want to run the following:
Item.where(status_id: params[:status_id])
I was thinking even if you had a try statement like Time.try(:parse, params[:created_at]), if params[created_at] were empty, then the query would be .where(created_at >= ?", nil) which would NOT be the intent at all. Same thing with params[:status_id], if the user just didn't pass it, you'd have a query that's .where(status_id:nil) which is again not appropriate, because that's a valid query in itself!
I suppose you can write code like this:
if params[:created_at].present?
#items = Item.where("created_at >= ?", Time.parse(params[:created_at])
end
if params[:status_id].present?
#items = #items.where(status_id: params[:status_id])
end
However, this is less efficient with multiple db calls, and I'm trying to be more efficient. Just wondering if possible.
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.starts_with(params[:starts_with]) if params[:starts_with].present?
end
You can do something like this. Rails is smart in order to identify when it need to build query ;)
You might be interested in checking this blog It was very useful for me and can also be for you.
If you read #where documentation, you can see option to pass nil to where clause.
blank condition :
If the condition is any blank-ish object, then #where is a no-op and returns the current relation.
This gives us option to pass conditions if valid or just return nil will produce previous relation itself.
#items = Item.where(status_condition).where(created_at_condition)
private
def status_condition
['status = ?', params[:status]] unless params[:status].blank?
end
def created_at_condition
['created_at >= ?', Time.parse(params[:created_at])] unless params[:created_at].blank?
end
This would be another option to achieve the desired result. Hope this helps !

Best way to conditionally chain scopes

I'm trying to extend the functionality of my serverside datatable. I pass some extra filters to my controller / datatable, which I use to filter results. Currently in my model I am testing whether the params are present or not before applying my scopes, but I'm not convinced this is the best way since I will have a lot of if/else scenario's when my list of filters grows. How can I do this the 'rails way'?
if params[:store_id].present? && params[:status].present?
Order.store(params[:store_id]).status(params[:status])
elsif params[:store_id].present? && !params[:status].present?
Order.store(params[:store_id])
elsif !params[:store_id].present? && params[:status].present?
Order.status(params[:status])
else
Order.joins(:store).all
end
ANSWER:
Combined the answers into this working code:
query = Order.all
query = query.store(params[:store_id]) if params[:store_id].present?
query = query.status(params[:status]) if params[:status].present?
query.includes(:store)
You could do it like this:
query = Order
query = query.store(params[:store_id]) if params[:store_id].present?
query = query.status(params[:status]) if params[:status].present?
query = Order.joins(:store) if query == Order
Alternatively, you could also just restructure the status and store scopes to include the condition inside:
scope :by_status, -> status { where(status: status) if status.present? }
Then you can do this instead:
query = Order.store(params[:store_id]).by_status(params[:status])
query = Order.joins(:store) unless (params.keys & [:status, :store_id]).present?
Since relations are chainable, it's often helpful to "build up" your search query. The exact pattern for doing that varies widely, and I'd caution against over-engineering anything, but using plain-old Ruby objects (POROs) to build up a query is common in most of the large Rails codebases I've worked in. In your case, you could probably get away with just simplifying your logic like so:
relation = Order.join(:store)
if params[:store_id]
relation = relation.store(params[:store_id])
end
if params[:status]
relation = relation.status(params[:status])
end
#orders = relation.all
Rails even provides ways to "undo" logic that has been chained previously, in case your needs get particularly complex.
The top answer above worked for me. Here is an example of its' real-life implementation:
lessons = Lesson.joins(:member, :office, :group)
if #member.present?
lessons = lessons.where(member_id: #member)
end
if #office.present?
lessons = lessons.where(office_id: #office)
end
if #group.present?
lessons = lessons.where(group_id: #group)
end
#lessons = lessons.all

wrong number of arguments (1 for 0) when trying to index posts in reverse order?

I'm trying to display all of my app's "thoughts" in reverse order, but the .all method is saying I can't pass it any arguments? I have seen many examples of this being done, so I am confused as to why I can't get it to work? Thanks. Here is my index action:
def index
#thoughts = Thought.all(:order => "created_at DESC")
end
#thoughts = Thought.order('created_at desc')

Rails 4, Postgresql - Custom query incorrectly constructed inside controller, correctly in console

I'm trying to write a query that will match last names that at least start with the submitted characters. No matter how I construct the query though, whether I use ILIKE, LIKE, ~*, or ##, the query that's constructed when the method is called from the controller changes the query to a simple = operator.
The full method:
def self.search(params)
if params.empty?
return Evaluation.none
end
results = nil
if params.include?(:appointment_start_date) && params.include?(:appointment_end_date)
results = Evaluation.where(appointment_time: params.delete(:appointment_start_date)..params.delete(:appointment_end_date))
end
params.each do |key, value|
case key
when :l_name
results = (results || Evaluation).where("to_tsquery('english', '#{value}:*') ## to_tsvector('english', l_name)")
when :ssn
results = (results || Evaluation).joins(:candidate).where(candidates: { ssn: value })
else
results = (results || Evaluation).where("#{key} = ?", value)
end
end
results.includes(:evaluation_type, :agency, :psychologist)
end
But the pertinent piece is:
.where("to_tsquery('english', '#{value}:*') ## to_tsvector('english', l_name)")
If I call the method from the Rails console, the expected SQL query is generated and the correct records are returned. Now here's the controller action:
def search
authorize! :read, Evaluation
#evaluations = Evaluation.search(evaluation_search_params).paginate(page: params[:page]).decorate
end
Let's say the user submits 'kel' for the search. For some flippin' reason, the generated query is
SELECT "evaluations".* FROM "evaluations" WHERE (l_name = 'kel') ORDER BY "evaluations"."appointment_time" ASC LIMIT 30 OFFSET 0
(There's a default_scope on Evaluation, hence the ORDER BY, so that's not part of the mystery.)
I've tried removing the pagination and the Draper decoration to no avail, and both of those also work fine in the console. So, my question is, why the heck is the generated query different when called from the controller? I'm seriously at my wit's end with this.
Are you param keys strings instead of symbols? That might be affecting which branch of the case statement is selected. Try debugging and stepping through the code if you are unsure.
You may just need to change to string values:
params.each do |key, value|
case key
when "l_name"
results = (results || Evaluation).where("to_tsquery('english', '#{value}:*') ## to_tsvector('english', l_name)")
when "ssn"
results = (results || Evaluation).joins(:candidate).where(candidates: { ssn: value })
else
results = (results || Evaluation).where("#{key} = ?", value)
end
end

Resources