Is there a way to save crawled information? - ruby-on-rails

The user of my application submits a request via a form and receives a set of numbers. The numbers constantly update throughout the day and I need a way to track the average of the last two numbers.
It'll work something like this:
How much experience do you have now?: 13000000
How much experience do you have now?: 13200000 (one hour later)
Then the user should be redirected to a page where the "experience per hour" is displayed: 200000
My application can currently only make the request for the number. Now I need a way to save that number and the time it was created, allow for a second number to be searched, and then perform a computation on them.
Here are the contents of my files.
home.html.erb with the form:
<h1>Welcome to xpTrack</h1>
<%= form_tag("/search", method: "get") do %>
<%= label_tag(:username, "Search for:") %>
<%= text_field_tag(:username) %>
<%= submit_tag("Search") %>
<% end %>
This form passes the username the user enters into params[:username] in the controller:
SearchController
class SearchController < ApplicationController
def home
require 'open-uri'
#username = params[:username]
#url = "http://hiscore.runescape.com/index_lite.ws?player=#{#username}"
#doc = Nokogiri::HTML(open(#url))
#stats = #doc.text.split(' ').map{|a| a.split(",") }
#skills = %w(overall, attack, defence, strength, constitution, ranged, prayer, magic, cooking, woodcutting, fletching, fishing, firemaking, crafting, smithing, mining, herblore, agility, thieving, slayer, farming, runecrafting, hunter, construction, summoning, dungeoneering, divination)
#max = 13034431
end
end
This view is rendered after searching:
<h1>
<%= #username.capitalize %>
</h1>
<table>
<tr>
<th>Skill</th>
<th>Rank</th>
<th>Level</th>
<th>Experience</th>
<th>Experience to 99</th>
</tr>
<% #skills.each_with_index do |skill, i| %>
<tr>
<td><%= skill.chomp(',').capitalize %></td>
<td><%= #stats[i][0] %></td>
<td><%= #stats[i][1] %></td>
<td><%= #stats[i][2] %></td>
<% if #stats[i][2].to_i < #max %>
<td><%= #max - (#stats[i][2]).to_i %></td>
<% end %>
</tr>
<% end %>
</table>
Link to the application at Heroku: http://runescapecalc.herokuapp.com/
When searching, use the username 'Brink'.

Related

Rails /(year)/(month)/(day) separate pages

I'm very new to rails and am having some trouble. I have a model called BusinessDates that consists of two tables, calendar_date and seasonality (you can ignore seasonality). What I'm trying to achieve is to be able to move through them easily like folders.
It took me a few solid days of google-foo, but I was able to have the index display a list of each unique year in order as links with friendly_ids. From here I want to click on it and have it link to a new view that displays a list of each unique month in that particular year in order as links with friendly_ids. Then (as you could guess) have it display on another new view a list of all the days in the selected month in the selected year. I want the url to be business_dates/2016/5/21 or in other words business_dates/(year)/(month)/(day).
My issue: I don't know where to go from here. I can't even seem to find any info on making a second level deep non-static url without either making each year month and day separate models (want to avoid that), or what looks like a rube goldberg machine to kinda get there but without views for each page (You'd have to just type the full date into the url).
Please help a beginner who feels very lost!
Controller:
def index
#years = BusinessDate.pluck(:calendar_date).map{|x| x.year}.uniq
end
index.erb.html
<table>
<tr>
<th>Year</th>
</tr>
<% #years.sort.each do |year| %>
<tr>
<td><%= link_to year, business_date_path(year) %></td>
</tr>
<% end %>
</table>
You can create a custom route. Since I don't know the exact controller actions etc that you are using, I will give you a general answer. You can route like (will hit BusinessDatesController's :show_full_date action):
get 'business_dates/:year/:month/:day', to: 'business_dates#show_full_date'
You can link to it like (run rake routes to check correct path):
<%= link_to your_date, full_date_business_dates_path('1985','5','21') %>
The important thing to understand here is that the path helper is in the end just a method that can take arguments. What it can accept is defined in the routes.rb. So, in our case, it will :year, :month and :day parameters.
Once you click this link and hit the :show_full_date action, you can extract the year, month, date using params[:year], params[:month], params[:day] and do with them whatever you need to do. You can similarly define routes for just the year or the month. Hope this helps.
EDIT: You can also give the as: option in the route definition to give a specific name to the path, like as: 'my_funky_name'.
Also, I should add that you should keep such custom routes to a minimum. When it is necessary, then do it. Otherwise stick to the defaults.
I finally figured it out and got it working, so I'll share my answer. big props to arunt for the help! It's a bit messy and probably not the right way I should be doing this, but my first goal was to get it to work and learn how it works along the way. Now I'm looking to tidy up and learn best practices.
Routes
Rails.application.routes.draw do
resources :business_dates, except: :show
get '/business_dates/:year/:month/:day', to: 'business_dates#edit_date', as: 'edit_date'
get '/business_dates/:year/:month', to: 'business_dates#by_days', as: 'by_days'
get '/business_dates/:year', to: 'business_dates#by_months', as: 'by_months'
delete '/business_dates/:year/:month/:day', to: 'business_dates#delete_date', as: 'delete_date'
Controller
class BusinessDatesController < ApplicationController
def index
#business_dates = BusinessDate.all
#years = BusinessDate.pluck(:calendar_date).map{|x| x.year}.uniq
end
def new
#date = BusinessDate.new
end
def by_months
#months = BusinessDate.where("strftime('%Y', calendar_date) = ?", params[:year])
#months = #months.pluck(:calendar_date).map{|x| x.strftime('%m')}.uniq
end
def by_days
#days = BusinessDate.where("cast(strftime('%Y', calendar_date) as int) = ? AND cast(strftime('%m', calendar_date) as int) = ?", params[:year], params[:month])
end
def edit_date
set_business_date
end
def create
#date = BusinessDate.new(date_params)
if #date.valid?
#date.save
redirect_to by_days_path(#date.calendar_date.year, "%02d" % #date.calendar_date.month)
else
flash.now[:alert] = "New business date could not be saved"
render action: "new"
end
end
def update
#set_business_date
#date = BusinessDate.find(params[:id])
#date.update(date_params)
redirect_to by_days_path(#date.calendar_date.year, "%02d" % #date.calendar_date.month)
end
def delete_date
set_business_date
#date.destroy
redirect_to by_days_path(params[:year], params[:month])
end
private
def set_business_date
#date = BusinessDate.where("cast(strftime('%Y', calendar_date) as int) = ? AND cast(strftime('%m', calendar_date) as int) = ? AND cast(strftime('%d', calendar_date) as int) = ?", params[:year], params[:month], params[:day]).first
end
def date_params
params.require(:business_date).permit(:calendar_date, :seasonality)
end
end
index.html.erb
<h1>Business Dates by Year</h1>
<table>
<tr>
<th>Calendar Date</th>
</tr>
<% #years.sort.each do |year| %>
<tr>
<td><%= link_to year, business_date_path(year) %></td>
</tr>
<% end %>
</table>
<br>
<%= link_to 'Create New Business Date', new_business_date_path %>
by_months.html.erb
<h1><%= params[:year] %></h1>
<table>
<tr>
<th>Months</th>
</tr>
<% #months.sort.each do |month| %>
<tr>
<td><%= link_to Date::MONTHNAMES[month.to_i], by_days_path(params[:year], month) %></td>
</tr>
<% end %>
</table>
<br>
<%= link_to 'Create New Business Date', new_business_date_path %>
<br>
<%= link_to 'Back to Years', business_dates_path %>
by_days.html.erb
<h1><%= Date::MONTHNAMES[params[:month].to_i] %>, <%= params[:year] %> <%= params[:id] %></h1>
<table>
<tr>
<th>Date</th>
<th>Seasonality</th>
<th>ID</th>
</tr>
<% #days.sort.each do |day| %>
<tr>
<td><%= day.calendar_date.day %></td>
<td><%= day.seasonality %></td>
<% %>
<td><%= day.id %></td>
<td><%= link_to 'Edit', edit_date_path(day.calendar_date.year, "%02d" % day.calendar_date.month, day.calendar_date.day) %></td>
<td><%= link_to 'Delete', delete_date_path(day.calendar_date.year, "%02d" % day.calendar_date.month, day.calendar_date.day), method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</table>
<br>
<%= link_to 'Create New Business Date', new_business_date_path %>
<br>
<%= link_to 'Back to Months', by_months_path %>
<br>
<%= link_to 'Back to Years', business_dates_path %>
It's all working fine, but I wish I could figure out my :id issue. Not sure how to find a record by :id while using the :year/:month/:day url convention. It's not an issue for this app, considering there should never be more than one of the same date, but it'd be helpful and I'm sure it would cut down on having to search for the record by params[:year],[:month], and [:day]. This thing was a holy terror, but I certainly learned a lot about the differences between arrays, hashes, symbols, attributes, models, methods, and instance variables along the way!

Removing conditional logic from a shared partial view or alternative solution

For a current project, I have duplicate code between views, and I'm not sure of the best route to refactor it.
I appear to be in a position where I can have duplicate code across various .html.erb files, or I could put identical code into a partial and use conditionals. I've always heard logic should stay out of views. Neither option seems ideal, and I don't currently know of alternatives.
To illustrate my question, I created a simple rails app called animals. I scaffolded for two models: one for cat and one for dog. Images display their corresponding attributes:
Displaying #cats and #dogs is pretty much the same. Cats just have a column for meows while Dogs have a column for barks, and a dog has the additional attribute column of plays_catch.
Lets say we choose to reduce the duplicate code for displaying cats and dogs by making a shared view partial:
#views/shared/_animal.html.erb
<tr>
<td><%= animal.name %></td>
<td><%= animal.age %> </td>
<% if animal.class == Cat %>
<td><%= animal.meows %> </td>
<% end %>
<% if animal.class == Dog %>
<td><%= animal.barks %> </td>
<td><%= animal.plays_catch %> </td>
<% end %>
</tr>
Then to render #cats = Cat.all:
<%= render partial: "shared/animal", collection: #cats %>
Then to render #dogs = Dog.all:
<%= render partial: "shared/animal", collection: #dogs %>
Obviously it would be overkill to do something like this for this specific example, but the real world project I'm applying it to would not be overkill.
The overall question is: how do you remove nearly identical code that iterates over collections, where the only difference is adding/removing a column of information? It just doesn't feel right to put that logic in the view itself, and leaving the duplication feels wrong.
You could use decorators and add methods that return the extra column(s):
class DogDecorator < Draper::Decorator
def extra_columns
[:barks, plays_catch]
end
end
class CatDecorator < Draper::Decorator
def extra_columns
[:meows]
end
end
...
<% animal.extra_columns.each do |column| %>
<td><%= animal.attributes[column.to_s] %>
<% end %>
...
<% #cats = CatDecorator.decorate_collection(Cat.all)
<%= render partial: "shared/animal", collection: #cats %>
You can use respond_to? to solve the problem more generically. The view logic doesn't feel so wrong when it's more generic.
<% [:meows, :barks, :plays_catch].each do |method| %>
<% if animal.respond_to?(method) %>
<td><%= animal.send(method) %> </td>
<% end %>
<% end %>
You can add a method of the same name to both Cat and Dog classes which would return the specific instance attributes names and values. I'd recommend returning two arrays (one with the names of the fields, other with the fields' values, or vice-versa) since hashes are not exactly ordered. This way you can control the order in which they'll appear in the view.
For example:
#models/cat.rb
def fields_and_attributes
fields = ["Name","Age","Meows"]
attributes = [self.name, self.age]
if self.meows
attributes.push("Yes")
else
attributes.push("No")
end
[fields,attributes] # make sure each attribute is positioned in the same index of its corresponding field
end
#models/dog.rb
def fields_and_attributes
fields = ["Name","Age","Plays catch"]
attributes = [self.name, self.age]
if self.plays_catch
attributes.push("Yes")
else
attributes.push("No")
end
[fields,attributes] # make sure each attribute is positioned in the same index of its corresponding field
end
#controllers/animals_controller.rb
def display_animals
#animals = Cat.all + Dog.all # an array containing the different animals
end
#views/display_animals.html.erb
for i in (0...#animals.size)
fields_and_attributes = #animals[i].fields_and_attributes
for f in (0...fields_and_attributes[0].size)
<p><%= fields_and_attributes[0][f] %> : <%= fields_and_attributes[1][f] %></p>
end
end
Here, we first iterate over all of the animals and call the .fields_and_attributes method of that specific record; we then iterate over the results of calling that method, displaying fields and attributes in the same order as the one defined within the method and also guaranteeing that the code will display every field and every attribute regardless of the difference in the total number of fields for each different animal.
I don't know of any canonical way to accomplish this, but I would use one partial for this in the following way:
<tr>
<% animal.attributes.each do |_, value| %>
<td><%= value %></td>
<% end %>
</tr>
You can get rid of repeated attributes calls by providing in the partial a local variable with pre-obtained model attributes.
EDIT: if you only want to display some attributes.
# Declare whitelist of attributes
# (you can also declare a blacklist and just calculate the difference between two array: all_attributes - blacklist_attributes):
<% whitelist = [:name, :age, :barks] %>
<%= render partial: 'shared/animal',
collection: #dogs,
locals: {attrs: (#dogs.first.attributes.keys.map(&:to_sym) & whitelist)} %>
views/shared/_animal.html.erb:
<tr>
<% attrs.each do |attr| %>
<td><%= animal[attr] %></td>
<% end %>
</tr>
Below is my answer after reviewing posted answers. Basically:
I left the differences within each scaffold model's index page
I made shared partials for common table headers and table data
code below:
#app/views/cats/index.html.erb
<h1>Listing Cats</h1>
<table>
<thead>
<tr>
<%= render partial: "shared/cat_dog_table_headers" %>
<th>Meows</th>
</tr>
</thead>
<tbody>
<% #cats.each do |cat| %>
<tr>
<%= render partial: "shared/cat_dog_table_data", locals: {animal: cat} %>
<td><%= cat.meows %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New Cat', new_cat_path %>
And for the dogs:
#app/views/dogs/index.html.erb
<h1>Listing Dogs</h1>
<table>
<thead>
<tr>
<%= render partial: "shared/cat_dog_table_headers" %>
<th>Barks</th>
<th>Plays catch</th>
</tr>
</thead>
<tbody>
<% #dogs.each do |dog| %>
<tr>
<%= render partial: "shared/cat_dog_table_data", locals: {animal: dog} %>
<td><%= dog.barks %></td>
<td><%= dog.plays_catch %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New Dog', new_dog_path %>
The shared table headers for cats and dogs:
#app/views/shared/_cat_dog_table_headers
<td><%= Name %></td>
<td><%= Age %></td>
The shared table data for cats and dogs:
#app/views/shared/_cat_dog_table_data_headers
<td><%= animal.name %></td>
<td><%= animal.age %></td>

Show attribute value using its id

In my application, I am grouping my objects by an ID. At the moment, I can only display the ID, but I would like to display the attribute value.
A Fixture belongs_to a tournament and a tournament has_many fixtures.
Controller
def index
#fixtures = Fixture.all
#tournament_fixture = #fixtures.group_by {|f| f.tournament_id}
end
View
<% #tournament_fixture.sort.each do |tourn_name, fixture| %>
<%= tourn_name %>
<% fixture.each do |f| %>
<td><%= f.home_team %></td>
<td><%= f.away_team %></td>
<td><%= f.kickoff_time %></td>
</tr>
<% end %>
<% end %>
How can I get
<%= tourn_name %>
to display its corresponding value that is in its :name column?
At the moment in my view for example i get this returned
<tbody>
2
<tr>
<td>Tournament Name</td>
<td>Team 1</td>
<td>Team 2</td>
<td>2000-01-01 14:00:00 UTC</td>
<td><a class="btn btn-success" href="/fixtures/1">view</a></td>
</tr>
</tbody>
The 2 needs to be the value in the :name column
I'd recommend grouping by tournament instead:
#tournament_fixture = #fixtures.group_by(&:tournament)
And then iterate using:
<% #tournament_fixture.sort.each do |tournament, fixture| %>
<%= tournament.name %>
...
<% end %>
You can access the whole object much like you can get the id like this:
def index
#fixtures = Fixture.includes(:tournaments).all
#tournament_fixture = #fixtures.group_by {|f| f.tournament.name}
end
The id is still available as either f.tournament_id or f.tournament.id, should you still need it but I just figured you'd rather group by its name directly. I simply added an includes statement to also load the referenced Tournament objects with your fixtures in one go. Otherwise, Rails would load the tournaments only when you access them one by one.
As an alternative, you could load the Tournaments, including all their the fixtures instead and iterate over the tournaments like this:
Controller
def index
#tournaments = Tournament.includes(:fixtures).all
end
View
<% #tournaments.each do |tournament| %>
<%= tournament.name %>
<% tournament.fixtures.each do |f| %>
<td><%= f.home_team %></td>
<td><%= f.away_team %></td>
<td><%= f.kickoff_time %></td>
</tr>
<% end %>
<% end %>
It seems a bit more natural to me and you don't need to iterate over all fixtures to map them by their tournament.
You can load the fixtures in the right order. There is no need to group then in memory. Remember to include the tournaments to avoid N+1 queries.
# controller
def index
#fixtures = Fixture.order(:tournament_id).includes(:tournaments).all
end
Loading in the right order in the controller makes the view simpler. For the tournament's name just use the association between Fixture and Tournament.
# view
<% #fixtures.each do |fixture| %>
<tr>
<td><%= fixture.tournament.name %></td>
<td><%= fixture.home_team %></td>
<td><%= fixture.away_team %></td>
<td><%= fixture.kickoff_time %></td>
</tr>
<% end %>

serial number with pagination rails

I am using pagination for my index page where I list all the users information like name, email etc. In the table I want to display the serial number in the order [1,2,3...]. If I user the user_id and if I delete the user the number will be missing out of sequence. I use the following code in my view
<% #user.each_with_index do |d, i| %>
<tr>
<td><%= i+1 %></td>
<% if d.profile.present? %>
<td><%= link_to d.profile.first_name+ " "+d.profile.last_name, posts_individualpostlink_path(:id => d.id) %> </td>
<% else %>
<td><%= "No Profile" %></td>
<% end %>
<td><%= d.email %></td>
<% if d.profile.present? %>
<td><%= d.profile.date_of_birth %> </td>
<% else %>
<td><%= "No Profile" %></td>
<% end %>
</tr>
<% end %>
</table>
<%= will_paginate #user %>
when I am going to the second page again the serial number starts with [1,2,....]. Per Page if i am giving 10 users, the second page should show [11, 12, 13,..... in the table.
Can anyone help me to do this. Thanks
Try with
<%
count = ((params[:page] || 1).to_i - 1) * 10
#user.each_with_index do |d, i| %>
<tr>
<td><%= count + i %></td>
Before answering question, small emotional note: stop using single letter variables in Your code. It makes it completely unreadable.
Why not use <% #user.each_with_index do |user, idx| %> ? Now in You code block it's easy to understand that You always refer to user.
Now the answer. Will paginate add page parameter to the paging links. So in You controller You should be able to do this:
#page = params[:page] || 1
After that use it to calculate correct number in Your view:
<td><%= (#page - 1) * number_of_items_on_page + i+1 %></td>

Rails yield - content_for problem

I have the following requirement.
Ex: There is a transaction table where it has columns say, transaction_name and amount. I want to loop through the transactions and display their details (transaction_name and amount) and finally I want to display the total amount (sum of all the amounts) in the head (before the loop) section of my page. (Think about it as a summary display)
Example page structure would be like
Sum of all the transactions - 200
transaction amount
trn1 100
trn2 50
trn3 50
And I tried to use yield and content_for tag but no luck.
my code is as follows (i'm calling inside my erb file.)
<%= yield :transaction_summary %>
<table>
<% total_amount = 0%>
<%for transaction in #transactions%>
<tr>
<td><%= transaction.transaction_name %></td>
<td><%= transaction.amount %></td>
<% total_amount += transaction.amount %>
</tr>
<%end%>
</table>
<% content_for :transaction_summary do %>
<h1>
Sum of all the transactions - <%= total_amount %>
</h1>
<% end %>
And
I'm using with inside a view (not inside a layout)
I'm using rails 2.2.2
Please help me and let me know if there is a better way
thanks in advance
cheers
sameera
EDIT:
Actually what I want to do is , Display some details before a particular loop where those details can be collected after the loop
Ex: If i have an array of transaction objects, I want to show a count of pass and failed transactions before the transactions loop in my view
thanks
In other cases where you really need to reuse content_for, the following may be useful.
In Rails 4, you can pass :flush => true as an option to content_for:
<% content_for :example, flush: true do %>
<h1>Deletes previous content</h1>
<% end %>
In Rails 3.1.1 (approx) you can delete the content from the view_flow when you yield by defining and using the following (eg in application_helper):
def yield_and_flush!(content_key)
view_flow.content.delete(content_key)
end
I think you have the wrong idea about content_for and yield. :) http://guides.rubyonrails.org/layouts_and_rendering.html
<h1>
<%= #transactions.collect(&:amount).sum -%>
</h1>
<table>
<%for transaction in #transactions%>
<tr>
<td><%= transaction.transaction_name %></td>
<td><%= transaction.amount %></td>
</tr>
<%end%>
</table>
edit -
Regarding collecting data, I suggest you put them in helper methods:
#transactions_helper.rb
def transactions_total transactions
#transactions_total ||= #transactions.collect(&:amount).sum
end
def passed_transactions transactions
#passed_transactions ||= #transactions.collect{|transaction| transaction.passed == true}
end
def failed_transactions transactions
#failed_transactions ||= transactions - passed_transactions(transactions)
end
Just noticed your comment to theTRON. The whole dry principle doesn't really apply to executing tiny logic such as looping through a array.
I would write a helper method which calculates the total separately, perhaps something along the lines of:
# app/helpers/transactions_helper.rb
def calculate_total(transactions)
total = 0
transactions.each {|transaction| total += transaction.amount}
total
end
Then you can display it in your view wherever you like, with:
<%= calculate_total(#transactions) %>
In Rails 3 you can yield after content_for; so your code will become:
<% content_for :transaction_table do %>
<table>
<% total_amount = 0%>
<% for transaction in #transactions do %>
<tr>
<td><%= transaction.transaction_name %></td>
<td><%= transaction.amount %></td>
<% total_amount += transaction.amount %>
</tr>
<%end%>
</table>
<% content_for :transaction_summary do %>
<h1>
Sum of all the transactions - <%= total_amount %>
</h1>
<% end %>
<% end %> <!- End content_for :transaction_table -->
<%= yield :transaction_summary %>
<%= yield :transaction_table %>
Note:
<% content_for :transaction_summary do %>
doesn't have to be inside of
<% content_for :transaction_table do %>
, but for some more complex cases it could.

Resources