Use Ransack sort_link for non-attributes - ruby-on-rails-3

I'm using Ransack's sort_link in my rails project to display a list of fees for my payment model. However, 'fee_amount' isn't an attribute of the payment model, but instead a class method of the bill model (which belongs to payment). What I have currently is:
<%= sort_link #search, :bill_fee_amount, "Convenience Fee" %>
which should accesses the bill of the current payment and call the 'fee_amount' method on that bill, which does some calculations and returns a float. It's these floats that I'm hoping to search by.
Can this be done, or can I only sort by attributes of the model I'm dealing with?

You can't with ransack as it comes, but check this issue. It seems some people has made patches for this functionality, but I haven't tried them.
Ransack, internally, it's always using the model as defined in the database, and I couldn't make it use the temporary tables created by the scopes.
PD: reworked the comment as an answer as I always come back to this question :P

Ransack is the successor to MetaSearch, and according to the MetaSearch documentation, you can create custom searches. Here's the link:
https://github.com/ernie/meta_search
I think something like this would work:
scope :sort_by_bill_fee_amount_asc, joins(:bill).select('sum("bills"."fee_amount") AS bill_fee_amount').order('bill_fee_amount ASC')
scope :sort_by_bill_fee_amount_desc, joins(:bill).select('sum("bills"."fee_amount") AS bill_fee_amount').order('bill_fee_amount DESC')
The question wasn't clear about what the calculation is exactly so I assumed it was just a sum of a field called fee_amount. Unfortunately, it's probably much worse than that and I think the nasty SQL calculation will need to be included in the Payment model.
The key point I think is that the SQL needs to include the column you are sorting on so you need to have the SQL calculate it and include it with the name that Ransack wants to search on.
I hope that helps.

Related

sql count filtering - rails way

Suppose I have Posts and posts' Comments. I want to filter all the Posts that have more than 10 comments. I began writing something like Posts.includes(:comments).group("post.id").count("comments.id"), to obtain a hash of posts and their counts, and I can extract the information from there, but I want some one-line straightforward way to do that
Sure I can use some pure sql syntax statements, but I want it in a pure rails way. Any idea ?
Assuming the models are named in the more typical singular form of Post and Comment and have the usual association relationship, then the following should work:
Post.joins(:comments).group('posts.id').having('count(comments.id) > 10')

Rails sum on an associated record

I have a survey model that works like so:
ResponseSets have many Responses
Responses belong_to Answer
Answer model has a "value" column.
Given a ResponseSet, I'd like the sum of the Answers that are associated with each Response.
Ie, what I'd like to be able to do, (in imaginary code) is:
response_set.responses.answers.sum('value')
However, this obviously doesn't work, I need to build a query through response_set.responses, but I don't know how.
What's the SQL-fu way to tackle this in ActiveRecord?
After much trial and error I came up with this relatively simple solution, I hope this helps others in the future:
response_set.responses.joins(:answer).sum('answers.value')
To make this more convenient I just made this a method in the ResponseSet Model:
def total_value
self.responses.joins(:answer).sum('answers.value')
end
Well if you're using Rails 3.2 you can do something like:
response_set.responses.answers.pluck(:value).inject{|sum,x| sum + x }
Are the answers Integers, in the sense that you're looking to find ALL numeric answers associated with a response and literally add them all up? I think you could use map and inject for something like this, depending exactly on how your models/associations are set up..
response_set.responses.answers.map(:&value).inject(:+)
Can you post your models?

How to resuse deleted model id number in Rails?

Say I have a Post model. When I delete last post 'Post 24', I want the next post to take id of Post 24 and not Post 25.
I want to show id in views and I don't want missing numbers. How do I do that?
Thanks for your help.
The purpose of an id is to be nothing more than an internal identifier. It shouldn't be used publicly at all. This isn't a Rails thing, but a database issue. MySQL won't reclaim id's because it can lead to very serious complications in your app. If a record is deleted, its id is laid to rest forevermore, so that no future record will be mistaken for it.
However, there is a way to do what you want. I believe you want a position integer column instead. Add that to your model/table, and then install the acts_as_list plugin.
Install it the usual way:
script/plugin install git://github.com/rails/acts_as_list.git
Then add the "hook" to your model:
class Post < ActiveRecord::Base
acts_as_list
end
Now the position column of your post model will automatically track itself, with no sequence gaps. It'll even give you some handy methods for re-ordering if you so choose.
Conversely, you could let the SQL do this itself:
SELECT rownum AS id, [whatever other columns you want]
FROM posts_table
WHERE [conditions]
ORDER BY [ordering conditions]
This will add numbers to each row without skipping any like you said.
NOTE: I use Oracle. I don't know if this exact code will work in other flavors.

Can RoR deal with char(1) fields as "boolean" fields?

I am looking at using Ruby on Rails for a storefront that has to make use of existing data, but I can create my own database schema if I need to. A lot of the fields in the existing data are char(1) emulating a boolean field (i.e. Y/N) for, I believe, cross-platform portability. Since the data is prone to change, I don't want to have to change the existing structure and convert these fields to bit/boolean fields.
If I do use Rails I really would like to make use of Ruby's beautiful boolean syntax and say something like <%= image_tag 'recycled.jpg' if product.recycled? %>, but will Rails recognize char(1) as a boolean, or do I have to define those methods myself in the model like:
class Product < ActiveRecord::Base
# ... other stuff here
def recycled?
self.recycled == 'Y'
end
end
I'm thinking I will have to redefine them myself, which is no big deal, I just want to make sure since using char(1) as yes/no values isn't something I've used in the past.
As far as I know, what you describe is not possible with ActiveRecord out-of-the-box.
However, if you have a lot of columns like this you could look at doing a little bit of meta-programming to provide a declarative way to add the relevant accessor logic. Something like :-
class Product < ActiveRecord::Base
yes_no_accessor :recycled
end
Another possibility is to monkey-patch ActiveRecord. I think the relevant method is ActiveRecord::ConnectionAdapters::Column.value_to_boolean(value). You could try overriding the ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES constant to include 'Y'. I haven't actually tried this!
I'd probably attack it at the model level - when you load a row into a model instance, compute a boolean attribute based on the char. Add a getter for the virtual attribute that returns this value, and a setter that updates both the boolean and the underlying char.
Can't you just wrap it in your database engine with a view or stored procedure to produce a consistent interface to your application?

Column order of results from rails ActiveRecord find_by_sql call

I'm attempting to put together some basic report screens. I've got some fairly complicated SQL queries that I'm feeding into ActiveRecord's find_by_sql method. The problem I am having here is that I am losing the order of the columns as given in the original query. I'm assuming that this is because the Hash class does not preserve entry order of its keys.
Is there a way around this problem? Should I be using a different method then find_by_sql for my queries?
I like to use Ruport for reporting. It has good ActiveRecord integration and it enables you to control column order and pretty much anything else. And it's sufficiently simple to use that I don't consider it overkill even for "basic" reports.
You're correct in that the Ruby Hash does not preserve order. That's part of the point, really - you access it using the key.
I assume your query is written to deliver the columns in the order that you want to output them and you were hoping to output the values via a loop? Seems like a decent enough idea, but I can't think of a way to achieve it without at least some extra work.
What I'd recommend is to explicitly access the columns by key in your template, since you're probably going to end up applying styles, formatting using helper functions like number_with_delimiter, that kind of thing.
To get something like the shortcut mentioned above, I suppose you could create an array of symbols in the order required and pull the values out of the hash in a loop. Something like this? (please excuse the potentially dodgy erb: I'm a haml user!)
<% for row in #report.rows %>
<tr>
<% for col in [:a, :b, :c] %>
<td><%= row[col] %></td>
<% end %>
</tr>
<% end %>
In rails 3.2 and higher you can use attribute_names for each record of find_by_sql results.
This is documented in find_by_sql:
Executes a custom SQL query against your database and returns all the
results. The results will be returned as an array with columns
requested encapsulated as attributes of the model you call this method
from. If you call Product.find_by_sql then the results will be
returned in a Product object with the attributes you specified in the
SQL query.
If you call a complicated SQL query which spans multiple tables the
columns specified by the SELECT will be attributes of the model,
whether or not they are columns of the corresponding table
For Models you can use column_names. For more info on the variations see other SA answer: How do you discover model attributes in Rails
How are you creating these "report screens"? Are they erb templates? Are you just calling .each on columns to print them all out?
If that's the case you could override the columns() method in your models to return an ordered array.