Rails 3 STI load derived partial - ruby-on-rails-3.1

Ok, so what I want to do, is have a template in a parent class, that loads a partial in the default template. That partial though should be specific to the derived class.
def class DataItem < ActiveRecord::Base
def value
# do something fancy here
end
end
def class FooDataItem < DataItem
def value
"foo"
end
end
def class BarDataItem < DataItem
def value
"bar"
end
end
Then, we have the following view structure:
app/views/data_item/index.html.erb
app/views/data_item/_col_info.html.erb
app/views/foo_data_item/_col_info.html.erb
app/views/bar_data_item/_col_info.html.erb
In the index.html.erb we have something like this:
<table>
<thead>
<tr>
<th>Key</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<% #dataItems.each do |item| %>
<tr>
<td><%= render :partial, item + "/col_info", :locals => { :item => item } %>
<td><%= item.value %>
</tr>
<% end %>
</tbody>
</table>
So, I know I can load a partial for just the item object, but what I was is a partial that is not just for the object, but a sub partial.
One option is to create a single partial in views/data_item/_col_info.html.erb that just has an if/else block to then load a separate partial, but I was hoping there was a cleaner way to do that with STI.
Also note, I can't use item.type as a way to generate the path since I have underscores in the view directory structure (type will be foodataitem, but I need foo_data_item).

I realize that you can't use the name of the class alone to generate the path, but you could do something like this:
item.class.name.underscore
That would be more convenient then having to add a template_path method to each class. The underscore method comes from ActiveRecord and it will convert your camel-case class name to the correct format.

Related

Using will_paginate with array routing issue

My project is currently configured to only have one view, import.html, that allows the user to upload and view contents of a CSV file (after uploading).
Here is the controller class import method:
class UploadController < ApplicationController
require "CSV"
require 'will_paginate/array'
def import
return if params[:file] == nil
file = params[:file]
#table = []
rowi = 0
CSV.foreach(file.path) do |row|
if rowi == 0 #and headers (for later)
#headers = row
else
#table << row.join("~")
end
rowi = rowi + 1
end
#table = #table.paginate(page: params[:page], :per_page => 20)
session
end
end
Here is the view:
<h1>Upload#import</h1>
<h4>UPLOAD A CSV FILE:</h4>
<%= form_tag({:action => "import"}, multipart: true) do %>
<%= file_field_tag :file %>
<%= submit_tag( "Import" ) %>
<% end %>
<% if #headers and #table %>
<h1>RESULTS</h1>
<table>
<thead>
<tr>
<% #headers.each do |column| %>
<td><%= column %></td>
<% end %>
</tr>
</thead>
<tbody>
<% #table.each do |row| %>
<tr>
<% row.split("~").each do |cell| %>
<td><%= cell %></td>
<% end %>
</tr>
<% end %>
</tbody>
</table>
<%= will_paginate #table, inner_window: 1, outer_window: 0%>
<% end %>
Here are the routes:
Rails.application.routes.draw do
root 'upload#import'
post "/" => "upload#import"
end
When interacting with my table to go to a different page, my routes have no specified instruction for:
GET "/?page=(pagenumber)"
I don't know what to tell it to do in order for the correct page to show.
It just defaults to redirecting to the import_page which clears all the data that has been imported. How should I fix this?
In a nutshell, you'll likely want to use a singular resource for this.
So you'd need the following:
In routes.rb:
resource :uploader, only: [:show, :create]
(N.B. this is a different use case to the more commonly-seen resources plural.)
This will give you the following routes:
POST /upload uploads#create # create the new upload
GET /upload uploads#show # display the one and only upload resource
In your controller:
class UploadController < ApplicationController
require "csv"
require 'will_paginate/array'
def create
return if params[:file] == nil
file = params[:file]
#table = []
rowi = 0
CSV.foreach(file.path) do |row|
if rowi == 0 #and headers (for later)
#headers = row
else
#table << row.join("~")
end
rowi = rowi + 1
end
# save the file to the db
# redirect to upload_path
end
def show
# Find the relevant table
#table = Table.last.paginate(page: params[:page], :per_page => 20)
end
end
Then you'll want to rework the form in your view to use #table, something like:
...
<%= form_for #table, multipart: true) do %>
<%= f.file_field :file %>
<%= f.submit "Import" %>
<% end %>
...
This is a very basic summary of how your project should work. The crux of it is using the correct, separate actions to create and show the table.
You'll likely want to look at using strong params and other Rails conventions.
Finally, if it helps, have a look at a generated Rails controller (i.e. rails generate controller my_example_controller) - this will have the create and show actions pre-built, and you can look to merge your code into something similar.
Hope this helps - give me a shout if you've any questions.

Ruby on Rails: Call controller method on specific model from view

I am new to Ruby and Rails.
I have a view that displays a list of course offerings.
<% if defined?#offerings %>
.....
<% #offerings.each do |offering| %>
<tr>
<td><%=offering.semester %></td>
<td><%=offering.location %></td>
</tr>
<%end %>
I need to add an enroll button that is associated with each offering. This enroll button should call an offerings controller method that decrements the specific offerings capacity attribute.
How can I bind each button to its respective offering? Or alternatively how can I pass the correct offering as a parameter to the controller method?
Add another <td>, like this :
<td><%= link_to "Enroll", your_method_path(offering) %></td>
You can use the button_to method, which will submit a POST request by default, like this:
<td><%= button_to "Enroll", enroll_offering_path(offering) %></td>
For enroll_offering_path to work you need:
A PUT /offerings/:id/enroll route:
# config/routes.rb`
resources :offerings do
post 'enroll', on: :member
end
and #enroll controller action:
# app/controllers/offerings_controller.rb
class OfferingsController < ApplicationController
...
def enroll
# Here you would do something like...
#offering = Offering.find(params[:id])
#offering.decrement!(:capacity)
end
end
You can read more about how all of this works here.

using rspec to test that a URL exists for a sinatra app

I'm trying to link my people_controller.rb file with my index.erb file so that a user can click on a name from the /people page and go to a unique page through the people/:id route. This works in the browser, but the app keeps failing the spec test I was given. I'm thinking the spec file I was given is incorrect and isn't actually testing for the existence of the link.
This is my people_controller.rb file:
get "/people" do
#people = Person.all
erb :"/people/index"
end
get "/people/:id" do
#person = Person.find(params[:id])
birthdate_string = #person.birthdate.strftime("%m%d%Y")
birth_path_num = Person.get_birth_path_num(birthdate_string)
#message = Person.get_message(birth_path_num)
erb :"/people/show"
end
This is my index.erb file:
<h1>People</h1>
<table>
<thead>
<th>Name</th>
<th>Birthdate</th>
</thead>
<tbody>
<% #people.each do |person| %>
<tr>
<td>
<a href="<%="people/#{person.id}" %>">
<%= "#{person.first_name} #{person.last_name}" %>
</a>
</td>
<td>
<%= "#{person.birthdate}" %>
</td>
</tr>
<% end %>
</tbody>
</table>
This is my spec file:
require 'spec_helper'
describe "Our Person Index Route" do
include SpecHelper
before (:all) do
#person = Person.create(first_name: "Miss", last_name: "Piggy", birthdate: DateTime.now - 40.years )
end
after (:all) do
#person.delete
end
it "displays a link to a person's show page on the index view" do
get("/people")
expect(last_response.body.include?("/people/#{#person.id}")).to be(true)
end
end
This is the failure message I get when I try to run rspec with the spec file:
Failure/Error: expect(last_response.body.include?("/people/#{#person.id}")).to be(true)
expected true
got false
# ./spec/people_show_link_spec.rb:16:in 'block (2 levels) in <top (required)>'
Is that expect method actually checking for the existence of the link or only checking to see if there is a text string "/people/#{#person.id}" on the people page? Shouldn't it include "a href" in some way (or some other keyword indicating a link) if it is actually checking for the link?
It only checks that there is the text string "/people/#{#person.id}".
A better expectation might be:
expect( page ).to have_css "a[href='/people/#{#person.id}']"
or
expect( page ).to have_link "#{person.first_name} #{person.last_name}"

Record from database to array

I want to retrieve a specific record from the database via a button on an index page and store that object in a temp Array and then display the atributes of an object on a specific view. But the record from find method isn't stored in a variable and i don't know why ... I am getting error for nil:NilClass.(In my project I want to be able to make a list of ordered patients for a day and display them in a view )
Here is my code :
patient_index.html.erb (from scaffold)
<tbody>
<% #patients.each do |patient| %>
<tr>
<td><%= patient.name %></td>
<td><%= patient.last_name %></td>
<td><%= patient.id %></td>
<td><%= link_to 'Show', patient %></td>
<td><%= link_to 'Edit', edit_patient_path(patient) %></td>
<td><%= link_to 'Destroy', patient, method: :delete, data: { confirm: 'Are you sure?' } %></td>
<td> <%= button_to 'Order', order_path(id: patient)%></td>
<th><%= #temp[0].name %></th> #testing if it shows the value here
</tr>
<% end %>
routes.rb
post 'order', to: 'patients#order'
patients_controller.rb
def index
#patients = Patient.all
end
def show
end
def edit ... #generated by scaffold
def destroy ...
def update ...
def order
#temp = []
#the_patient = Patient.find(params[:id])
#temp << #the_patient
redirect_to patients_url
end
error image link http://imgur.com/nJdyR2E (slightly different names ..i got the code and variables in my language )
Thanks for help and advise :)
Not enough information to tell but are you maybe doing
redirect_to patients_url
in your #order action but you mean:
redirect_to patient_url
Which would be your #show action. I get that you are using a different language. Maybe the problem is a pluralization error?
If not, here's my answer:
You are trying to build an index page without #temp defined. Think about it, you call for Index, so it builds the index page. what is #temp at that time? It doesn't get set until you click on the button, which can't exist yet because #index does not define it. But even after you define it in #order you then redirect to #index where it gets reset to []. You need to restructure your app. A simple hack would be to redirect to a new page that has it's own code that included #temp and take any reference to #temp out of #index.
Along the lines of:
patients_url -> index action -> patients_url with button press ->
order action -> order_display_url

Referencing controller variables in bootstrap tooltip

If a tweet contains any phrase that belongs to the current user's blockedshow, it receives the redacted div. I want the tooltip to tell the user which specific phrase the tweet contains, when it hovers over the tweet. Right now Im using #phrases and it's not working. Any ideas?
View
<% if is_redacted? tweet %>
<!-- Tweet contains at least one blocked phrase. -->
<a href="https://www.twitter.com/#{tweet.user.screen_name}"
data-toggle="tooltip" title="This tweet contains the phrase:<%=#phrases%> ">
<%= check_if_redacted(tweet.text)%>
</a>
<% end %>
Controller
class TwitterController < ApplicationController
def index
end
def login
end
def tweet
text = params[:my_tweet]
Client.update(text) unless text==nil
end
private
def is_redacted? tweet
#phrases ||= current_user.blockedshows.map(&:phrases).flatten.map(&:text)
#phrases.any? { |phrase| tweet.text.include? phrase }
end
helper_method :is_redacted?
end
The instance variable #phrases is defined in a helper method. I don't think it is visible in the view scope. You need to initate in the controller action instead. If this is in the index:
def index
#phrases = current_user.blockedshows.map(&:phrases).flatten.map(&:text)
end
I am a bit confused by your tweet method. Is this a controller action or just a helper method. I looks like a helper method since you are referring to it in the view (unless there is more code not seen here). I would put tweet in an instance variable also and maybe even is_redacted. Seomthing like:
def index
#phrases = current_user.blockedshows.map(&:phrases).flatten.map(&:text)
#tweet = Client.update(params[:my_tweet]) unless text==nil
#isredacted = #phrases.any? { |phrase| tweet.text.include? phrase }
end
and then use it in the view:
<% if #isredacted %>
<!-- Tweet contains at least one blocked phrase. -->
<a href="https://www.twitter.com/#{#tweet.user.screen_name}"
data-toggle="tooltip" title="This tweet contains the phrase:<%=#phrases%> ">
<%= check_if_redacted(#tweet.text)%>
</a>
<% end %>

Resources