Rails 3.1 create record rather than update? - ruby-on-rails-3

I have the following Nokogiri rake task in my Rails (3.1) application:
desc "Import incoming calls"
task :fetch_incomingcalls => :environment do
# Logs into manage.dial9.co.uk and retrieved list of incoming calls.
require 'rubygems'
require 'mechanize'
require 'logger'
# Create a new mechanize object
agent = Mechanize.new
# Load the dial9 website
page = agent.get("https://manage.dial9.co.uk/login")
# Select the first form
form = agent.page.forms.first
form.username = 'username
form.password = 'password'
# Submit the form
page = form.submit form.buttons.first
# Click on link called Call Logs
page = agent.page.link_with(:text => "Call Logs").click
# Click on link called Incoming Calls
page = agent.page.link_with(:text => "Incoming Calls").click
# Output results to file
# output = File.open("output.html", "w") { |file| file << page.search("tbody td").text.strip }
# Add each row to a new call record
page = agent.page.search("table tbody tr").each do |row|
next if (!row.at('td'))
time, source, destination, duration = row.search('td').map{ |td| td.text.strip }
Call.create!(:time => time, :source => source, :destination => destination, :duration => duration)
end
end
The time value is the first row in the table and is unique per call (as we can only receive one call at a time).
What I would like to do is use the time value as the unique identifier for my call logs.
So when scraping the screen, it will "update" the existing calls (which won't change but it's the only way I can think of only importing new calls).
If I set it to:
Call.find_all_by_time(nil).each do |call|
and then:
call.update_attribute(:time, time)
Then it will update the existing records, but I want it to import records that aren't already in our database - based on the time value.
Any help is appreciated!

Do you mean this?
# Add each row to a new call record
page = agent.page.search("table tbody tr").each do |row|
next if (!row.at('td'))
time, source, destination, duration = row.search('td').map{ |td| td.text.strip }
call = Call.find_or_create_by_time(time)
call.update_attributes({:time => time, :source => source, :destination => destination, :duration => duration})
end

Related

Need Help passing an instance variable to another method for insert in ruby on rails 4

I am new to ROR and spent most of the day trying to get this to work. I have tried using the before_filter and I cannot get my object to insert in another method.
The view is index, a file is selected in the view and then the button to validate file is clicked which calls a method 'evaluate_media' in this method, I look up values based on the file path and name selected and I can successfully insert the record with all the values in this method. The problem is I don't want an automatic save. When the 'evaluate_media' method is done it displays back with either a save button or an update button based on if the file submitted already exists in the database. Then the user can choose to save/update the record or now. This button calls the 'save_file' action. The problem is all the information save in 'evaluate_media' action for the file is not accessible to save_file or update_file actions. I believe session variables might be my answer but I could not find any good examples to get it setup correctly and working in my application. Can someone please tell me and show me the proper code to pass the value from 'evaluate_media' action to save_file or update_file actions?
Here is the code where I assign the values for a new record in my evaluaate_media method:
if #file_exists_flag == 'NEW'
# Assign Parameter values for new save
#file_alias_tfile = FileAliasTfile.new( {:src_location => #radio_button_value, :directory => #dir_path_choice, :full_path => #selected_filepath, :full_filename => #filepath, :file_ext => '',
:assigned_status => 'Unassigned', :file_status => 'Saved', :alias_code => #file_alias.to_s, :validate_status => #file_status.to_s, :error_msg => #file_msg.to_s,
:video_alias_match => #msg_dtl1.to_s, :audio_alias_match => #msg_dtl2.to_s, :video_format => #video_format.to_s, :video_bitrate => #video_bitrate.to_s,
:video_width => #video_width.to_s,
:video_height => #video_height.to_s, :video_framerate => #video_framerate.to_s, :video_aspect_ratio => #video_aspectratio.to_s, :video_scan_type => #video_scantype.to_s,
:video_scan_order => #video_scanorder.to_s, :video_alias_code => '', :audio_alias_code => '', :bus_prod_initiative_id => 0, :status => 'Active', :start_date => DateTime.now.to_date,
:end_date => '', :deleted_b => 0, :created_by => 'admin', :updated_by => 'admin'} )
end
Then if the user clicks the save, the save_file method is called and here is the code to save the
values from evaluate_media into the database:
def save_file
#file_alias_tfile = FileAliasTfile.create(#file_alias_tfile)
#file_alias_tfile.save
end
I figure the update will be the same as the save so I only included one case here.
Your help is appreciated. Thank you!
First of all,
def save_file
#file_alias_tfile = FileAliasTfile.create(#file_alias_tfile)
#file_alias_tfile.save
end
ModelName.create() is equivalent to ModelName.new(....).save. So the second line should be deleted.
You could do this:
if #file_exists_flag == 'NEW'
session[:my_file] = FileAliasTfile.new( .... )
def save_file
session[:my_file].save
end
However, generally it's not recommended to save model objects in the session. Instead, you should save the object in the database; then save the id of the object in the session. I know, "But I don't want to save the object in the database until the user confirms". Have you ever heard of the destroy() method? For instance,
FileAlias.destroy(session[:file_id])
which deletes the record in the file_aliases table with an id equal to session[:file_id].
When the 'evaluate_media' method is done it displays back with either
a save button or an update button based on if the file submitted
already exists in the database.
So, create() the record (remember that saves the record), and if the user clicks the save button, do nothing. If the user clicks the update button, then delete the record from the database and delete the id from the session. For example:
if #file_exists_flag == 'NEW'
my_file = FileAlias.create( .... )
session[:file_id] = my_file.id
def save_file
#render some page
end
def update_file
my_file = FileAlias.destroy(session[:id])
session.delete[:file_id]
#Do something with my_file?
end

Mongoid dynamic query

This must be an easy one, but I'm stuck...
So I'm using Rails#3 with Mongoid and want to dynamically build query that would depend upon passed parameters and then execute find().
Something like
def select_posts
query = :all # pseudo-code here
if (params.has_key?(:author))
query += where(:author => params[:author]) # this is pseudo-code again
end
if (params.has_key?(:post_date))
query += where(:create_date => params[:post_date]) # stay with me
end
#post_bodies = []
Post.find(query).each do |post| # last one
#post_bodies << post.body
end
respond_to do |format|
format.html
format.json { render :json => #post_bodies }
end
end
You have a few different options to go with here - depending on how complex your actual application is going to get. Using your example directly - you could end up with something like:
query = Post.all
query = query.where(:author => params[:author]) if params.has_key?(:author)
query = query.where(:create_date => params[:post_date]) if params.has_key?(:post_date)
#post_bodies = query.map{|post| post.body}
Which works because queries (Criteria) in Mongoid are chainable.
Alternatively, if you're going to have lots more fields that you wish to leverage, you could do the following:
query = Post.all
fields = {:author => :author, :post_date => :create_date}
fields.each do |params_field, model_field|
query = query.where(model_field => params[params_field]) if params.has_key?(params_field)
end
#post_bodies = query.map{|post| post.body}
And finally, you can take it one level further and properly nest your form parameters, and name the parameters so that they match with your model, so that your params object looks something like this:
params[:post] = {:author => "John Smith", :create_date => "1/1/1970", :another_field => "Lorem ipsum"}
Then you could just do:
#post_bodies = Post.where(params[:post]).map{|post| post.body}
Of course, with that final example, you'd want to sanitize the input fields - to prevent malicious users from tampering with the behaviour.

Rails 3: Controller params default value

I am using a remote form_for for my show action to retrieve content based on the params passed by this form.
= form_tag modelname_path(#modelname), :id=>"select_content_form", :remote => true, :method => 'get' do
= text_field_tag :content_type, params[:content_type], :id=>"select_content_type"
= submit_tag "submit", :name => nil, :id=>"select_content_submit"
And I alter the content in controller as follows:
# Default params to "type1" for initial load
if params[:content_type]
#content_type = params[:content_type];
else
#content_type = "type1"
end
case #content_type
when "type1"
# get the content
#model_content = ...
when "type1"
# get the content
#model_content = ...
My question is, whether the above approach is the only we can set defaults for params or can we do it in a better manner. This works but I would like to know if this is the right approach.
UPDATE
Based on the suggestion below, I used the following and got an error on defaults.merge line:
defaults = {:content_type=>"type1"}
params = defaults.merge(params)
#content_type = params[:content_type]
A good way of setting default options is to have them in a hash, and merge your incoming options onto it. In the code below, defaults.merge(params) will overwrite any values from the params hash over the default ones.
def controller_method
defaults = {:content=>"Default Content", :content_type=>"type1"}
params = defaults.merge(params)
# now any blank params have default values
#content_type = params[:content_type]
case #content_type
when "type1"
#model_content = "Type One Content"
when "type2"
#etc etc etc
end
end
If there is a static list of types you could make it a dropdown box and just don't include a blank option so that something is always selected. But if you're stuck with a textbox you could clean up the controller action by using a before filter:
class FoosController < ActionController::Base
before_filter :set_content_type, :only => [:foo_action]
def foo_action
...
end
protected
def set_content_type
params[:content_type] ||= "type1"
end
end
I wanted to add to this discussion a working way to set default params:
defaults = { foo: 'a', bar: 'b' }
params.replace(defaults.merge(params))
This avoids assigning a local variable via "params =".

Where to set session default value?

guys!
Prior to asking i should mention, that i`m working without ActiveRecord or any self-hosted-database. So thats why i have to store some values in the session.
From the very begining i desided to set session value of the users city in the layout. - i supposed it would be loaded before anything else. So i`ve done something like this:
<% session[:city] ||= {:name => 'City-Name', :lat => '40', :lng => '40'}%>
But when i`m loading directly to inner page it occurs that session[:city is nil *(
How should i set the session properely, so that it wouldn`t be nil???
I had similar needs in one of the applications I worked on. It needed the users data to be loaded on sign-in and stored in the session. So, wrote a module called session_helpers.rb with the following:
module SessionHelpers
def get_value(key)
session[key.to_sym]
end
protected
def store_data(*objects)
objects.each do |object|
if object.is_a?(Hash)
object.each do |key, value|
session[key.to_sym] = value
end
end
end
end
def remove_data(*objects)
objects.each do |object|
if object.is_a?(String)
key = to_id(object)
else
key = to_id(object.class.name)
end
session[key] = nil
end
end
def update_data(key, value)
session[key.to_sym] = value
end
private
def to_id(name)
"#{name.parameterize('_').foreign_key}".to_sym
end
end
You can make any or all the methods available to views as well:
# application_controller.rb
helper_method :get_value
From the model I would retrieve a hash of the data that needs to be put up in the session about the user:
def common_data
#data = Hash.new
#data.merge!( { 'news' => self.news.count } )
...
#data
end
As I wanted to do this after sign-in I overrode the devise method to do this:
def after_sign_in_path_for(resource_or_scope)
store_data( '_count', current_user.common_data )
dashboard_path
end
This way I was able to load important data about the user on sign-in and store it in the session and retrieve whenever I wanted. Hope this helps.

Rake db:seed working on local, heroku rake db:seed trying to insert null into id column

For some reason my db:seed is not working when I try to do it via heroku. It works fine when I do it locally.
paul#paul-laptop:~/rails_projects/foglift$ heroku rake db:seed
rake aborted!
PGError: ERROR: null value in column "id" violates not-null constraint
: INSERT INTO "metric_records" ("metric_id", "id", "created_at", "updated_at", "value", "school_id", "date") VALUES (1, NULL, '2011-03-18 20:26:39.792164', '2011-03-18 20:26:39.792164', 463866000.0, 1, '2009-01-01') RETURNING "id"
(See full trace by running task with --trace)
(in /app/5aca24ae-c5a7-4805-a3a1-b2632a5cf558/home)
I'm not sure why it's attempting to put a null into the id column, it doesn't make any sense to me.
Here's my seeds.rb file.
# This file should contain all the record creation needed to seed the database with its default values.
# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
directory = "db/seed_data/"
# Pre-load All Schools
School.delete_all
path = File.join(directory, "schools.txt")
open(path) do |schools|
schools.read.each_line do |school|
name, city, state, website, tuition, debt = school.chomp.split("|")
School.create!(:name => name, :city => city, :state => state, :website => website, :current_tuition => tuition, :current_avg_debt => debt)
end
end
# Pre-load All Dashboards
Dashboard.delete_all
path = File.join(directory, "dashboards.txt")
open(path) do |dashboards|
dashboards.read.each_line do |dashboard|
name, extra = dashboard.chomp.split("|")
Dashboard.create!(:name => name)
end
end
# Pre-load All Metrics
Metric.delete_all
path = File.join(directory, "metrics.txt")
open(path) do |metrics|
metrics.read.each_line do |metric|
name, dashboard_id, display, source = metric.chomp.split("|")
Metric.create!(:name => name, :dashboard_id => dashboard_id, :display => display, :source => source)
end
end
# Pre-load All Metric Records
MetricRecord.delete_all
path = File.join(directory, "metric_records.txt")
open(path) do |values|
values.read.each_line do |value|
metric_id, value, date_string, school_id = value.chomp.split("|")
date_string = '01/01/' << date_string
date = Date.strptime(date_string, "%d/%m/%Y") unless date_string.nil?
MetricRecord.create!(:metric_id => metric_id, :value => value, :date => date, :school_id => school_id)
end
end
Is there a possibility that your DB migration did not create a sequence for your metric_records ID column on the Heroku database?
You might be able to fix it with the following:
CREATE SEQUENCE metric_records_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE metric_records_id_seq OWNED BY metric_records.id;