Rails: Sum in 3rd level of nested resource - ruby-on-rails

in my rails 3.1 application I have these models:
class Project < ActiveRecord::Base
has_many :tasks
has_many :hours, :through => :tasks
def sum_hours
self.hours.sum(:hours)
end
end
class Task < ActiveRecord::Base
belongs_to :project
has_many :hours
def sum_hours
self.hours.sum(:hours)
end
end
class Hour < ActiveRecord::Base
attr_accessible :hours # column in table
belongs_to :task
end
in projects_controller.rb this:
def show
#project = Project.find(params[:id])
#tasks = #project.tasks.includes(:hours)
end
and in projects/show.html.erb I use this:
...
<td>Sum hours: <%= #project.sum_hours %></td>
...
<% for task in #tasks do %>
<tr>
<td><%= task.sum_hours %></td>
</tr>
...
But even with .includes(:hours) it still uses a query separately for the project and for every task. Am I missing something?? where am I doing mistake???

You can try something like this,
#tasks = #project.tasks.joins(:hours).select("tasks.*, sum(hours) as total")
Then you can access the sum by,
task["total"].to_i
I cannot ensure these code works for you. But the basic idea is to join the table and select the sum as an extra field.

Related

Ruby on Rails - how to belong to many?

I have a table called Report, and another called Phases. Let's say I have 10 phases and I want to have checkboxes on the report form that lists out all the phases. How can I collect the items that are checked and store that data in the database?
Do I need to have columns such as phase_one:integer phase_two:integer phase_three:integer and just pull back the ones that aren't null? Or can I somehow store the IDs of x phases in one column and pull back those IDs in an array?
To clarify NateSHolland's answer, you can use has_and_belongs_to_many if the phases are predefined:
#app/models/report.rb
class Report < ActiveRecord::Base
has_and_belongs_to_many :phases
end
#app/models/phase.rb
class Phase < ActiveRecord::Base
has_and_belongs_to_many :reports
end
This will allow you to populate the phase_ids (collection_singular_ids) attribute from your form:
#config/routes.rb
resources :reports
#app/controllers/reports_controller.rb
class ReportsController < ApplicationController
def new
#report = Report.new
#phases = Phase.all
end
def create
#report = Report.new report_params
#report.save
end
private
def report_params
params.require(:report).permit(:phase_ids)
end
end
#app/views/reports/new.html.erb
<%= form_for #report do |f| %>
<%= f.collection_check_boxes :phase_ids, #phases, :id, :name %>
<%= f.submit %>
<% end %>
This would allow you to specify which phases your report has, although it will not have any changes.
What I think you'd be better doing is using has_many :through, allowing you to define which phase you're referencing:
#app/models/report.rb
class Report < ActiveRecord::Base
has_many :progress
has_many :phases, through: :progress
accepts_nested_attributes_for :progress #-> define level
end
#app/models/progress.rb
class Progress < ActiveRecord::Base
#id report_id phase_id phase_lvl created_at updated_at
belongs_to :report
belongs_to :phase
end
#app/models/phase.rb
class Phase < ActiveRecord::Base
has_many :progress
has_many :reports, through: :progress
end
The important factor here is the phase_lvl column - it's my understanding that you have certain "levels" which your phases will be a part of. I can't describe it properly, but to give you context, you'll have something like...
Report is written (phase 5)
Report is published (phase 3)
Report is Sent (phase 15)
The above will give you the ability to define which "level" each phase denotes. I think this is the distinction you're looking for.
This would be a little more tricky to implement, but worth it:
#app/controllers/reports_controller.rb
class ReportsController < ApplicationController
def new
#report = Report.new
#phases = Phase.all
10.times do
#report.progress.build
end
end
def create
#report = Report.new report_params
#report.save
end
private
def report_params
params.require(:report).permit(progress_attributes: [:phase_id, :phase_lvl])
end
end
This will give you the ability to define the following:
#app/views/reports/new.html.erb
<%= form_for #report do |f| %>
<%= f.fields_for :progress do |p| %>
<%= p.collection_select :phase_id, #phases, :id, :name %>
<%= p.number_field :phase_lvl %>
<% end %>
<%= f.submit %>
<% end %>
It sounds like what you may want is a many to many relationship or a has_and_belongs_to_many. To do this you will need to create a join table between Report and Process that will have a report_id column and a phase_id. For more information check out this documentation: http://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association
In the models you will need to add:
Class Report < ActiveRecord::Base
has_and_belongs_to_many :phases
...
end
and
Class Phase < ActiveRecord::Base
has_and_belongs_to_many :reports
...
end

Rails Invoicing App

I want to create an invoice in rails. Invoice can have items and each item will have quantity, tax & price. It's a typical invoice we see everyday.
In order to create an invoice what is the best approach.
What is the common model for invoice and items?
I know Items will be a separate model. But how can we have one view for invoice, which creates both the invoice and items added to it?
What I mean is, Inside a new invoice page, there will be list of the clients, and list of the items , But here i'm not sure how to make the association when i create invoice. Is there any good example that i can follow ?
Please I'd appreciate some Help. Or even just a walk through of the steps i need to follow in order to accomplish that...
Here's my basic ERD
Quite a broad question, here's what I'd do:
#app/models/invoice.rb
class Invoice < ActiveRecord::Base
belongs_to :user
has_many :line_items
has_many :items, through: :line_items
accepts_nested_attributes_for :line_items
end
#app/models/line_item.rb
class LineItem < ActiveRecord::Base
belongs_to :invoice
belongs_to :item
end
#app/models/item.rb
class Item < ActiveRecord::Base
belongs_to :company
has_many :line_items
has_many :invoices, through: :line_items
end
--
#app/models/user.rb
class User < ActiveRecord::Base
has_many :invoices
end
This will be the base level "invoice" association structure - your clients/users can be built on top of it.
Your routes etc can be as follows:
#config/routes.rb
resources :invoices
#app/controllers/invoices_controller.rb
class InvoicesController < ApplicationController
def new
#invoice = current_user.invoices.new
#invoice.line_items.build
end
def create
#invoice = current_user.invoices.new invoice_params
#invoice.save
end
end
Then your view will be something like this:
#app/views/invoices/new.html.erb
<%= form_for #invoice do |f| %>
<%= f.fields_for :line_items do |l| %>
<%= f.text_field :quantity %>
<%= f.collection_select :product_id, Product.all, :id, :name %>
<% end %>
<%= f.submit %>
<% end %>
This would create the corresponding #invoice, with which you'll be able to call as follows:
#user.invoices.first
Apart from this, I don't have anywhere enough specific information to help specifically
May I recommend using the payday gem? I have created invoice models in the past applications and I'll tell you what, it can get pretty tricky sometimes depending on the type of application you're building. But the reason I like using this gem besides the convenience factor is that it can also render your invoices as a customizable PDF.
It makes adding items to the invoice a breeze as well, for example from their GitHub page:
invoice = Payday::Invoice.new(:invoice_number => 12)
invoice.line_items << Payday::LineItem.new(:price => 20, :quantity => 5, :description => "Pants")
invoice.line_items << Payday::LineItem.new(:price => 10, :quantity => 3, :description => "Shirts")
invoice.line_items << Payday::LineItem.new(:price => 5, :quantity => 200, :description => "Hats")
invoice.render_pdf_to_file("/path/to_file.pdf")

Rails: Sunspot text searching with model associations, using :through

How do I search with associations and through with sunspot?
class StaticController < ApplicationController
def search
#search = Sunspot.search Business, Service do
fulltext params[:q]
paginate :per_page => 10
order_by_geodist(:location, *Geocoder.coordinates(params[:loc]))
end
#biz = #search.results
end
class Business < ActiveRecord::Base
attr_accessible :name
has_many :services, :through => :professionals
searchable do
text :name #name in business column
# how to do I get the services?
end
end
class Service < ActiveRecord::Base
attr_accessible :service
belongs_to :professional
end
class Professional < ActiveRecord::Base
belongs_to :business
has_many :services, as: :servicable
end
In the view, I have this (lots of looping)
<%= #biz.each do |b| %>
<%= b.name %>
<!-- looping through professionals model -->
<% b.professionals.each do |prof| %>
<!-- looping through services model -->
<% prof.services.each do |s| %>
<%= s.service %>
<% end %>
<% end %>
<% end %>
This works if I search for a name that is within the business model, but what if I'm searching through a term that's in the Service model? It won't display correctly because my view is only coming from the business side. How do I make it so the business name will pop up if I search through Service model?
Thanks
You will need to make additional indexes for the associated models in the calling model to make this happen. For example:
class Business < ActiveRecord::Base
attr_accessible :name
has_many :services, :through => :professionals
searchable do
text :name #name in business column
text :services do # this one for full text search
services.map(&:service).compact.join(" ")
end
string :services , :multiple => true do #this one for exact searches
services.map(&:service).compact
end
end
end
After that you can do queries like:
Bussines.search do
with(:services, "some_service")
end.execute.results
Now you no longer have to do join on mysql tables to fetch data. You can just fetch data from the solr. This is one of biggest advantages of solr.
I hope this makes it clear. Fell free to drop a comment if you need more details.

using nested forms with has_many through - can't assign mass attributes

I have read pretty much every question here about the nested forms with has_many through associations, but I can't get my model to work. Can someone please help?
There are 2 models: archetypes and skirtpreferences, linked through a skirtpreferencing model.
Here are the models:
class Archetype < ActiveRecord::Base attr_accessible :occasion,
:skirt_partworth, :title, :skirtpreferencings_attributes
has_many :skirtpreferencings has_many :SkirtPreferences, :through
=> :skirtpreferencings accepts_nested_attributes_for :SkirtPreferences accepts_nested_attributes_for :skirtpreferencings
end
Blockquote
class Skirtpreferencing < ActiveRecord::Base attr_accessible
:archetype_id, :skirt_preference_id, :skirtpreferencing_attributes
belongs_to :archetype belongs_to :SkirtPreferences
accepts_nested_attributes_for :SkirtPreferences
end
class SkirtPreference < ActiveRecord::Base attr_accessible
:archetype_id, ....
has_many :skirtpreferencings has_many :archetypes, :through =>
:skirtpreferencings
end
The form looks like this and that is displaying just fine:
<%= form_for(#archetype) do |f| %> ...
<%= f.fields_for :skirtpreferencing do |preference_builder| %>
<%= preference_builder.fields_for :SkirtPreferences do |builder| %>
<%= render "skirt_preferences_field", :f => builder %>
<% end %> <% end %> ...
I imagine I hav to do something in the controllers, but I am not sure exactly what.
Thanks!
Adding the Controllers:
class ArchetypesController < ApplicationController
def new
#archetype = Archetype.new
#archetype.skirtpreferencings.build
end
# GET /archetypes/1/edit
def edit
#archetype = Archetype.find(params[:id])
end
def create
#archetype = Archetype.new(params[:archetype])
end
class SkirtPreferencesController < ApplicationController
def new
#skirt_preference = SkirtPreference.new
end
def edit
#skirt_preference = SkirtPreference.find(params[:id])
end
def create
#skirt_preference = SkirtPreference.new(params[:skirt_preference])
end
I am guessing this is many-to-many relationship between SkirtPreference and Archetype, while SkirtPreferencing is the association between SkirtPreference and Archetype?
Try changing from skirtpreferencing_attributes to skirtpreferences_attributes. That's my hunch. Because you are trying to add data to skritpreferences, not the skirtpreferencings which are just there to associate between skirtpreferences and archetypes.
I also think this is unusual.
has_many :SkirtPreference, :through => :skirtpreferencings
accepts_nested_attributes_for :SkirtPreference
...
belongs_to :SkirtPreference
accepts_nested_attributes_for :SkirtPreference
All these normally should be :skirtpreferences.
** EDIT 1 **
Assuming the forms are generated in new action in ArchetypesController...
You seem to be missing the part where you build a skirtpreferencing attribute out of archetype.
So in your new action in ArchetypesController
def new
#archetype = Archetype.new
#archetype.skirtpreferencings.build
...
end
** Edit 2 **
SkirtPreferences should be changed to skirtpreferences except for class name.
Can you try changing f.fields_for :skirtpreferencing do to f.fields_for :skirtpreferencings do

Creating Multi-Dimensional Hashes in Ruby From ActiveRecord Results

I am new to rails and am having troubles figuring out some things with ActiveRecord.
Right now, I have three models:
class Project < ActiveRecord::Base
attr_accessible :name
has_and_belongs_to_many :tags
has_many :tasks
end
class Task < ActiveRecord::Base
attr_accessible :todo
has_and_belongs_to_many :tags
has_many :tasks
end
class Tag < ActiveRecord::Base
attr_accesible :description
has_and_belongs_to_many :projects
has_and_belongs_to_many :tasks
end
I am trying to create a hash that returns tasks that belong to specific tags so that:
Project_Tasks = { 1 => { project.name, "tasks" => { "task 1", "task 2", "task 3" }
2 => { project.name, "tasks" => { "task 1", "task 2", "task 3" } }
I am not sure quite how to go about creating this. My first inclination is to create a method inside one of the classes (I have gone back and forth on which one... right now, I think it is best served under "tag") that loops through the projects that match the given tag, queries for tasks that match both and append them to the array.
To date, this hasn't worked. I'm completely stumped.
Any thoughts on how I can accomplish this? Is a method the appropriate way to go or is there a trick inside ActiveRecord to create a query that would get me at least close to this?
I have tried to fix your model definitions.
class Project < ActiveRecord::Base
attr_accessible :name
has_and_belongs_to_many :tags
has_many :tasks
end
class Task < ActiveRecord::Base
attr_accessible :todo
has_and_belongs_to_many :tags
belongs_to :project
end
class Tag < ActiveRecord::Base
attr_accesible :description
has_and_belongs_to_many :projects
has_and_belongs_to_many :tasks
end
Now you should be able to access your data(in controller) for a specific project as follows:
#project = Project.find_by_id(1) # Loaded a project
#tasks = #project.tasks # all task for this project in an array
To display it in the view:
<%= #project.name %><br />
<% #tasks.each do |task| %>
<%= task.todo %><br />
<% end %>
I hope this helps

Resources