It's getting old rewriting report code for Rails applications. Ya have to create the query, the routes, the action, and the view...(yes, I'm lazy) Is there anything out there that would create a full report in even fewer steps?
Here's what I imagine would be ideal:
You have a Report record that has a name, query code (in Ruby or SQL), and perhaps some report options like so:
Report.create(:name => "With last name 'smith'",
:query => "Person.where( :last_name => 'smith' )")
This would store a record, and you'd dynamically get a route:
method : report_with_last_name_smith_path
http : GET
url : /report_with_last_name_smith
options : {
:controller => 'reports',
:action => 'with_last_name_smith'
}
And the report record would retrieve all columns from the query (which happens to be all
columns in the people table in this case), and generate a view with the data like so (pretend this is html):
| First Name | Last Name | Date of Birth | Sex |
| Bob | Smith | 03-13-2000 | Male |
| Lisa | Smith | 03-23-1980 | Female |
| Jack | Smith | 03-13-1975 | Male |
Anyone know of a plugin that helps achieve at least part of this?
By the way, the Ruport gem will likely be incompatible with Rails 3, and to be honest, it's a little unwieldy.
Here's something that gets us almost there:
http://github.com/wayneeseguin/dynamic_reports
In Dynamic Reports you create a report class that specifies a few parameters, and add a controller action to specify the query to use for the results.
Here's the example on the site:
# In some class that you create
class OrdersReport < DynamicReports::Report
title "Orders Report"
subtitle "All orders recorded in database"
columns :total, :created_at
link :total, '/report/item_sales?id={id}' # => Will substitute ID for the value of ID associated with that record
end
# In any controller of your choosing:
def orders
#orders = Order.find(:all, :limit => 25)
render :text => OrdersReport.on(#orders).to_html, :layout => "application"
end
The docs don't say anything about routes, so I assume we have to create those ourselves.
It also allows you to use any layout or custom templates you want, and it generates charts using the Google charts API.
In my projects i use ransack gem, it is amazing, it allow your user make custom queries, and your can customize the attributes available.
Take a look
Github
Railscast
Related
Perhaps I'm overthinking this, but I want to be able to use a nested form that depends on the data that's coming from another table. Let me elaborate.
I have 4 tables with something like the following pseudo-code:
Product
has_many: providers
Provider
belongs_to: product
Sale
has_many: products
has_many: orders
accepts_nested_attributes_for :orders
Order
belongs_to: sale
So far, so good.
My problem begins when I'm trying to make a Sale. Each Product will have an X amount of Providers (usually 4), and each Sale will always have an Order for each of those Providers (even if it's 0), so I need the data of each Provider so I can specify how much I'm ordering from each.
I have something like this:
=form_for #sale do |s|
=s.label :date, 'Date'
=s.text_field :date
=s.label :other, 'Other Information'
=s.text_area :other
=s.object.product.providers.each do |p|
=s.fields_for :orders do |o|
=p.name
=o.label :amount, 'Amount'
=o.text_field :amount
This doesn't work. What this is achieving is obtaining the amount of Providers of each Product, and listing the name of each Provider - so far, so good - but I need to specify the amount of that each one is going to receive in the Order. If there's data, the text_field will get populated with the very first record that gets matched in Orders, but since I'm looping through it, the same data is also populated for the remaining 3.
I know my logic is flawed (I've been battling through it for the last 4 hours). I think it is a matter of going back to the drawing board (which I'm doing right now), but I wanted to see if anybody could maybe see the obvious.
What I'd like to see is something like this:
|--Provider Name--|---Amount----|
|-----------------|-------------|
| Provider One | 10 |
| Provider Two | 2 |
| Provider Three | 0 |
| Provider Four | 4 |
|-----------------|-------------|
Where the "amount" on each line is a text_field for the Amount field of the form. Initially, the text_fields should come out empty, so I can fill them in with data. Once they have data, they should pop back with the previous data used. Pretty standard.
I thought about creating those x amount of records on the Orders table as soon as I visited the new Sale page, but that'd make me fill the Order table with lots of unused data. That approach would also hinder it later, if for some reason a new Provider is added.
I don't know how to proceed, any pointers would be greatly appreciated.
Try this code for list of text_fields for orders:
=form_for #sale do |s|
=s.label :date, 'Date'
=s.text_field :date
=s.label :other, 'Other Information'
=s.text_area :other
=s.products.each do |product|
=product.providers.each do |provide|
=p.name
=s.fields_for :orders do |o|
=o.label :amount, 'Amount'
=o.text_field :amount
I am new to Ruby on Rails (which may soon be obvious) and I'm trying to figure out a model for the following data scenario. I have read several posts and searched Google at length, but I'm still confused.
I have 5 different tables with identical columns, except the value column has a different data type. They data is in 5 separate tables for a variety of good reasons, but think of it as data sharded across multiple tables.
logbook_strings (user_id, entry_id, field_id, value)
logbook_booleans (user_id, entry_id, field_id, value)
logbook_integers (user_id, entry_id, field_id, value)
logbook_decimals (user_id, entry_id, field_id, value)
logbook_datetimes (user_id, entry_id, field_id, value)
So here's what the data would look like:
------------------------------------------------
| user_id | entry_id | field_id | value |
------------------------------------------------
| 1 | alpha1 | date | 2012-11-14 |
| 1 | alpha1 | duration | 1.2 |
| 1 | alpha1 | remarks | Nice job. |
------------------------------------------------
| 1 | alpha2 | date | 2012-11-13 |
| 1 | alpha2 | duration | 2.7 |
| 1 | alpha2 | remarks | Bad job. |
------------------------------------------------
Entry alpha1:
2012-11-14, 1.2, Nice Job.
Entry alpha2:
2012-11-13, 2.7, Bad job.
etc.
The reason I do this is so that I can have an infinitely flexible database. I can add a new field_id at any time to add a new field/feature to my app instead of doing a schema update to add yet another column to a wide logbook table.
So what I'm wondering, is there a way I can have a single ActiveRecord model in which I can reference all 5 of these tables?
After spending a few minutes trying to shoehorn this into a single ActiveRecord class, I don't think it's a great idea to use ActiveRecord for something like this. I see a few options:
Roll your own model. The extreme downside to this approach is that you lose out on all of ActiveRecord's many nice features. But if your data is relatively simple (not a lot of associations, etc.) then this might be a viable option.
Restructure your data. If this schema/data is either pre-existing or has to match a mobile app's schema for some reason or another, this might not be an option. But if you're starting fresh, Rails' migrations make adding/removing columns on a whim extremely easy and very safe so I might consider using a more traditional approach. While this may not seem ideal, it's something to seriously consider in order to gain the many benefits of ActiveRecord.
If you must keep your schema, creating a separate model for each logbook table could be your best option.
# Migrations
create_table :logbook do |t|
# Default fields, nothing special
end
create_table :logbook_integers do |t|
t.integer :logbook_id # You'd probably want to index this as well
t.string :name
t.integer :value
end
create_table :logbook_strings do |t|
t.integer :logbook_id # You'd probably want to index this as well
t.string :name
t.string :value
end
# etc...
# Models
class Logbook < ActiveRecord::Base
has_many :logbook_integers
has_many :logbook_strings
# etc...
def remarks
self.logbook_strings.find_by_name("remarks").value
end
def remarks= newValue
remark = self.logbook_strings.find_or_create_by_name("remarks")
remark.value = newValue
remark.save
end
# etc...
end
class LogbookInteger < ActiveRecord::Base
belongs_to :logbook
end
class LogbookString < ActiveRecord::Base
belongs_to :logbook
end
# etc...
# Usage
logbook = Logbook.new
logbook.remarks = "Hi"
logbook.duration = 2
logbook.remarks # => Hi
logbook.duration # => 2
If you can change your schema a bit, here's an option:
You can use the serialize class method described here (cmd+f for 'serialize') to store your entries so instead of having many models, you just have two: Logbook and LogbookField. It might look something like this:
# Migration for logbook_fields
create_table :logbook_fields do |t|
t.string :name
t.string :value
end
# Models
class Logbook
has_many :logbook_fields
def self.build_with_default_fields
self.logbook_fields.create name: "date"
self.logbook_fields.create name: "duration"
# etc...
end
# You could probably do some cool Ruby metaprogramming to create all these
# accessors/setters for you, btw.
def date
self.logbook_fields.find_by_name "date"
end
def date= newValue
field = self.logbook_fields.find_by_name "date"
field.value = newValue
field.save
end
def duration
self.logbook_fields.find_by_name "duration"
end
def duration= newValue
field = self.logbook_fields.find_by_name "duration"
field.value = newValue
field.save
end
# etc...
end
class LogbookField
serialize :value
belongs_to :logbook
end
# Usage
logbook = Logbook.build_with_default_fields
logbook.date = DateTime.now
logbook.duration = 2.7
Something to that effect. That way you retain most all the ActiveRecord niceties while still maintaining some of the "infinite-ness" of your schema design. However, adding/removing columns on a single table with migrations would probably prove easier than this even. Again, it depends on whether you can be flexible in your schema or not. Hope this helps.
I think you should probably have a one table with a type column.
Ex:
logbook(user_id, entry_id, field_id, value, value_type)
value type would be
strings
booleans
integers
decimals
datetimes
Example would be
-----------------------------------------------------------
| user_id | entry_id | field_id | value |value_type |
-----------------------------------------------------------
| 1 | alpha1 | date | 2012-11-14 | datetime
| 1 | alpha1 | duration | 1.2 | decimal
| 1 | alpha1 | remarks | Nice job. | string
So basically value column will be string, and from the model you could decide what you wanted to with the value type, your model would be
class Logbook < ActiveRecord::Base
#sample method
#just to give an idea how you could use the same value
#with different times
def multiple_duration_by_two
self.value * 2 if self.value_type == "decimal"
end
end
However, depending on your requirements this implementation might need tweeks, but I guess you get the idea
HTH
So, sometimes you just need a list of options for your selects. Is there a simple gem out there that makes it easy to use one table for all the types of options you might have in your app?
This table would likely look like:
id | type | value | label
01 | color | red | Red
02 | color | black | Black
03 | shape | circle | Circle
04 | shape | square | Square
05 | state | texas | Texas
For instance, a list of countries, a list of states, a list of colors, a list of months, etc...
Then when using a select:
select_tag :color, options_for_colors
Then it would populate the select with options with values/labels from some options table, where the rows have a type of :color.
This would be easy enough to roll on my own but I don't want to spend the time if its already built.
update
I'd like this to be a dynamic table, so the end user can add/remove items from the select options table
I always use this method,
app/models/user.rb
ROLES = %w[admin author normal]
app/views/users/_form.html.erb
<%= f.collection_select :role, User::ROLES, :to_s, :titleize %>
As of Rails 3.2, here's what I do in the initializer:
ActiveRecord::Base.class_eval do
def self.types
Rails.application.routes.routes.select do |r|
r.defaults[:action]=="index" && r.defaults[:controller]== self.name.to_s.downcase.pluralize
end.map do |r|
r.defaults[:type]
end.compact
end
end
And in routes.rb I map STI actions to parent model's controller because I'm controller savvy:
resources :derived_models, :controller => :base_model, :type => "DerivedModel"
resources :more_derived_models, :controller => :base_model, :type => "MoreDerivedModel"
Now, Model.types will give you ["DerivedModel", "MoreDerivedModel"]
There seem to be a number of ways to handle a multiple foreign key association. Each way I have approached this has their draw backs, and as I am new to Rails I am convinced others have come across a similar scenario and I am probably working on something solved long ago.
My question is:
What would be an efficient way of handling a multiple index key association, while still retaining all other Rails sql modifiers (such as :include etc)?
My scenario is:
I have a table association as follows (simplified), which is used to connect people to other people via links:
People
+----+-----------+
| id | name |
+----+-----------+
| 1 | Joe |
+----+-----------+
| 2 | Sally |
+----+-----------+
| 3 | Bob |
+----+-----------+
Links
+----+-----------+---------+
| id | origin_id | rcvd_id |
+----+-----------+---------+
| 1 | 2 | 1 |
+----+-----------+---------+
| 2 | 1 | 3 |
+----+-----------+---------+
| 3 | 3 | 2 |
+----+-----------+---------+
From row 1 of the above Links table, one can see that a Person (Sally = 2) is linked to another Person (Joe = 1).
It is easy for me to find all of a Persons Links if my foreign key was "origin_id". But this would only show People originating a Link. In my scenario I need to see all links regardless if they were originated or received by a Person. If for example I were to ask for all of Sally's links (Sally = 2), the result I would want would be:
Links
+----+-----------+---------+
| id | origin_id | rcvd_id |
+----+-----------+---------+
| 1 | 2 | 1 |
+----+-----------+---------+
| 3 | 3 | 2 |
+----+-----------+---------+
Hence I have 2 index keys, both "origin_id" and "rcvd_id".
One way this could be solved is with a Method:
class Person < ActiveRecord::Base
has_many :link_origins, :class_name => "Link", :foreign_key => :origin_id, :dependent => :destroy
has_many :link_rcvds, :class_name => "Link", :foreign_key => :rcvd_id, :dependent => :destroy
def links
origin_person + rcvd_person
end
However, this is not efficient. For example this requires the entire collection to be gathered from the database and only then does the paginate method work (I am using the will_paginate gem), which defeats the point as paginate should speed up the process by limiting the number of records called. Not limit the records after the entire collection is already done.
Also, the above will not allow me to call for example, Joe.links(:first).origin_id.name. Not exactly this code but meaning I could not call the Person details on the origin_id of a selected link, as the links method does not know that origin_id is related to the People table.
So far the most workable solution seems to be the :finder_sql.
class Person < ActiveRecord::Base
has_many :links, :finder_sql => 'SELECT * FROM links WHERE (links.origin_id = #{id} or links.rcvd_id = #{id})'
This gives all links where the Person_id matches either the Links.origin_id or the Links.rcvd_id.
The down side of this option, is that using :finder_sql, rules out all the other sql modifiers since Rails
doesn't know how to parse and modify the SQL you provide. For example I would not be able to use the :include option with the :finder_sql.
So, right now I am using the :finder_sql, solution. But it seems there might be a away of getting this association done in such a way that I don't need a :finder_sql. For example, is there a way to write a custom sql string while retaining the Rails sql modifiers that Active Record supplies.
Any ideas on the above?
I did find the solution to this, however it turned out I was probably asking the wrong question. I have not found away to have multiple index keys as I asked without implementing some custom sql which breaks different rails helpers.
I guess my question still stands, but how I did resolve this was to look at the problem differently. I just created the associations as they are:
belongs_to :rcvd_person, :class_name => 'Person', :foreign_key => :rcvd_id
belongs_to :origin_person, :class_name => 'Person', :foreign_key => :origin_id
And a custom sql statement:
class Person...
has_many :links, :finder_sql => 'SELECT * FROM links WHERE origin_id = #{id} OR rcvd_id = #{id}'
end
Then I was able to manipulate the records how I wanted in my view. In case anyone else is doing something similar, I did:
<% person.links.each do |link| %>
<% if link.origin_id == person.id %>
<%= link.rcvd_person.given_name %>
<% else %>
<%= link.origin_person.given_name %>
<% end %>
<% end %>
I'm not sure you can really support an association with multiple keys in the same table because Rails won't know which key to set if you attempt to create a relationship.
However, if you just want person.links, Rails 3 provides a way that is better than :finder_sql
class Link
def self.by_person(person)
where("origin_id => :person_id OR rcvd_id => :person_id", :person_id => person.id
end
end
class Person < ActiveRecord::Base
# You can include the has_many relationships if you want, or not
def links
Link.by_person(self)
end
end
This lets you do things like #person.links.limit(3) (which currently appears to be broken when using :finder_sql)
Assume a query of the following form
operatingExpenses = Expense.find(:all,
{:select=>"categories.activityType, categories.name heading, sum(amount) totalAmount",
:joins => "inner join expense_categories categories on category_id = categories.id ",
:group => "categories.activityType, categories.name",
:order => "categories.activityType, totalAmount DESC"}
)
Now amount is defined as a decimal field in the database schema. e.g. definition in the Rails Migration would be
create_table :expenses do |table|
table.column :created_at, :timestamp, :null=>false
table.column :description, :string, :limit=>100, :null=>false
table.column :amount, :decimal, :scale=>2, :precision=>10, :null=>false
table.column :category_id, :integer, {:null=>false, :default =>1}
end
Now the set of records returned by the query fails the following assert
assert_equal 800, operatingExpenses[1].totalAmount
<800> expected but was <"800.00">.
Why is the Sum/aggregate column returned as a string instead of the same datatype as the summed up database column? I'd like to avoid sprinkling .to_s or .to_f everywhere to get around this. Any ideas ?
What I'm trying to get at the end of this rainbow is a cashflow listing like this - (for a specified daterange.. an additional condition to the query listed above).
cat Type Heading TotalAmount
| Operating | ExpCatX001 | 4000 |
| | ExpCatX002 | 200 |
| Financing | ExpCatX003 | 1000 |
| Investing | ExpCatX004 | 4234 |
| | ExpCatX005 | 201 |
For custom queries that require basically a whole custom SQL statement (your find above doesn't exactly abstract much from you) I like to set up a quick little new model that represents the new information. i.e.
class OperatingExpenseReportDatum
attr_accessor :type, :heading, :total
def initialize(row)
# set values from row, like
#total = row["total"].to_f
end
end
and then write a helper method into the model, something like:
class Expense < AR::Base
...
def self.operating_expenses
rows = connection.select_all "SQL STATEMENT HERE"
rows.map { |row| OperatingExpenseReportDatum.new(row) }
end
end
Then your report generation is all nice:
#controller
#expenses = Expense.operating_expenses
#view
<% #expenses.each do |expense| %>
<%= expense.type %>: <%= expense.total %>
<% end %>
Or something similar. :)
Active record is trying to return "Expense" objects built from the results of your query. But expense objects don't have a totalAmount field, so this is unrequested "bonus data" as far as ActiveRecord can tell. It coud just throw it out, but instead it keeps it for you, with the most forgiving type assumptions it can.
My suggestions:
Don't do this; it isn't the way Active record is supposed to work; the total of the details belongs to whatever is rolling them up; it isn't a detail object itself.
If you must treat it as a detail, name the field amount so ActiveRecord knows what to do with it.
If you must have the total as its own field, add an accessor in your Expense model like so:
def total_amount
totalAmount.to_f
end
and use it like so: operatingExpenses[1].total_amount in your code.
Because with :select Rails gives you no way of telling it (or the driver) to what kind of native type to bind the result set's fields (once it's all retrieved -- although most of not all drivers will know, internally, what the column SQL types are) -- so it's all recorded as strings and the SQL type information is generally thrown away. This applies to all fields all the time. You have to perform the conversion manually (to_f), just like Rails' ActiveRecord.instantiate method has to, internally, when you use plain ActiveRecord.find without :select or :joins (and it has to populate a non-string attribute.)
You may confirm this by looking at your database driver's select_raw method.
Cheers,
V.
Just do find_by_sql and apply the SUM on a decimal column :
operating_expenses = Expense.find_by_sql ['SELECT cat.activityType, cat.name heading, SUM(exp.amount) AS amount FROM expenses exp JOIN expense_categories cat ON exp.category_id = cat.id GROUP BY cat.id, cat.activityType, cat.name, exp.id ORDER BY cat.activityType, amount DESC']
Under the 'amount' column of
operating_expenses you'll got your total automatically converted to decimal.
N.B. : cat.id and exp.id are required to respect SQL standard.