Is Create method or Update faster? - ruby-on-rails

I'm using rails 3.2.11 and ruby 1.9.3.
I have a slow page and I know I have many ways to optimize it. Currently I am focused on the method update_attributes.
Here is my code:
def create
#user = current_user
#demo = #user.demos.new
race_ethnicity_response = []
params[:race_ethnicity_response].each do |response, value|
race_ethnicity_response << response if value != '0'
end
params[:demo][:race_ethnicity_response] = race_ethnicity_response.join(', ')[0, 254]
#demo.update_attributes(params[:demo])
end
Or should I use something like build and save or create?
#demo = #user.demos.build
...
#demo.save!
Or
#users.demos.create!(params[demo])
I am curious which is faster. I know if it save 2ms then I should use the one which is more code correct/readable.

On a small operation like this you aren't going to see much of a performance difference. Go for readability + maintainability. The code above does seem a little scattered, particularly the middle block. Here is a straightforward approach although I may be missing something related to the params[:race_ethnicity_response] loop.
#demo = Demo.new(:params)
#demo.race_ethnicity_response = race_ethnicity_response.reject{|i| i == 0 }.join(', ')[0, 254]
current_user.demos << #demo

Related

Rails - submitting JSONs to database from controller

I am working on a Rails app, and I am attempting to insert attributes from JSONs as database entries. I'm running into a problem, though, and would appreciate some guidance.
I've been able to jam a few things together and come up with something that sort of works...
def create
#report_group = Array.new
#report_group.push({location:"home", comments:"Hello, database!"}, {location:"away", comments:"Goodbye, database!"})
#report_group.each do |x|
#new_report = Report.new(x)
#new_report.user_id = current_user.id
#new_report.save
end
end
private
def report_params(params)
params.permit(:user_id,:location,:comments)
end
This is a good first step - this commits two entries to my database, one for each of the hashes pushed into #report_group, but it is suffering from a problem - the create action does not reference the report_params whitelist.
I have built several Rails apps where entries are submitted one at a time via the standard Rails form helpers, but I have never done it with multiple JSONs like this before. Trying out the syntax I'd use in a typical form helper situation
#new_report = Report.new(report_params(x))
throws the expectable error undefined method permit' for #<Hash:0x007f966b35e270> but I am not sure what else to do here.
EDIT TO SHOW SOLUTION
Big thanks to #oreoluwa for pointing me in the right direction. Here's the solution that I came up with.
def create
#report_group = Array.new
#report_group.push({location:"home", comments:"Hello, database!"}, {location:"away", comments:"Goodbye, database!"})
#report_group.each do |x|
hash = ActionController::Parameters.new(x)
#new_report = Report.new(report_params(hash))
#new_report.user_id = current_user.id
#new_report.save
end
end
private
def report_params(params)
params.permit(:user_id,:location,:comments)
end
You're getting the error because a Hash is not the same as an ActionController::Parameters. In order to use the permit method with your Hash you may need to first convert it to ActionController::Parameters, as such:
hash = {location:"home", comments:"Hello, database!"}
parameter = ActionController::Parameters.new(hash)
parameter.permit(:user_id,:location,:comments)
I don't know if that is what you're looking for, but I thought to point you in the right direction.

Getting all the pages from an API

This is something I struggle with, or whenever I do it it seems to be messy.
I'm going to ask the question in a very generic way as it's not a single problem I'm really trying to solve.
I have an API that I want to consume some data from, e.g. via:
def get_api_results(page)
results = HTTParty.get("api.api.com?page=#{page}")
end
When I call it I can retrieve a total.
results["total"] = 237
The API limits the number of records I can retrieve in one call, say 20. So I need to call it a few more times.
I want to do something like the following, ideally breaking it into pieces so I can use things like delayed_job..etc
def get_all_api_pages
results = get_api_results(1)
total = get_api_results(1)["total"]
until page*20 > total do |p|
results += get_api_results(p)
end
end
I always feel like I'm writing rubbish whenever I try and solve this (and I've tried to solve it in a number of ways).
The above, for example, leaves me at the mercy of an error with the API, which knocks out all my collected results if I hit an error at any point.
Wondering if there is just a generally good, clean way of dealing with this situation.
I don't think you can have that much cleaner...because you only receive the total once you called the API.
Have you tried to build your own enum for this. It encapsulates the ugly part. Here is a bit of sample code with a "mocked" API:
class AllRecords
PER_PAGE = 50
def each
return enum_for(:each) unless block_given?
current_page = 0
total = nil
while total.nil? || current_page * PER_PAGE < total
current_page += 1
page = load_page(current_page)
total = page[:total]
page[:items].each do |item|
yield(item)
end
end
end
private
def load_page(page)
if page == 5
{items: Array.new(37) { rand(100) }, total: 237}
else
{items: Array.new(50) { rand(100) }, total: 237}
end
end
end
AllRecords.new.each.each_with_index do |item, index|
p index
end
You can surely clean that out a bit but i think that this is nice because it does not collect all the items first.

Ruby on Rails -- NameError '#' not allowed as an instance variable name

I am fairly new to Ruby on Rails and have been stuck with this bug for quite sometime now. I am hoping that someone could give me some useful information to fix this bug. I have a feeling I am overlooking something trivial. Anyhow, I have included below my code that is drawing the Error (that is in the View) and also my code that is in my controller. While I have done my research before posting on here, I may have to put my instance variable in my controller -- however, I am unsure how that process goes and then how to call it from my View. I would appreciate any help! Thanks in advance :)
The error I get:
NameError in Search#indx
'#' is not allowed as an instance variable name
Here is my line of code that is drawing the error in my view (apps/views/search/index.html.erb):
<% #search = instance_variable_get("\##{params[:model].to_s.downcase.pluralize}")%>
Here is my code in my controller (apps/controllers/search_controller.rb):
class SearchController < ApplicationController
def index
#containers = Container.search(params[:q])
#cpus = Cpu.search(params[:q])
#disks = Disk.search(params[:q])
#firmwares = Firmware.search(params[:q])
#hardwares = Hardware.search(params[:q])
#hosts = Host.search(params[:q])
#interfaces = Interface.search(params[:q])
#lans = Lan.search(params[:q])
#licenses = License.search(params[:q])
#rams = Memory.search(params[:q])
#networks = Network.search(params[:q])
#products = Product.search(params[:q])
#roles = Role.search(params[:q])
#sites = Site.search(params[:q])
#vmpools = Vmpool.search(params[:q])
#trackings = Tracking.search(params[:q])
end
end
The problem is that params[:model] is nil, so you're essentially doing instance_variable_get("#").
Take a look at the Rails log and see if the params you think are being received are actually being received.
Edit: The below is not directly related to the problem you're having (the previous two sentences explain why you're having that problem), but rather a suggestion for improving the way your code is organized.
Doing instance_variable_get in a view is a pretty bad code smell. Really, doing instance_variable_get at all is a code smell. There's almost certainly a better way to do what you're trying to do. For example:
def index
query = search_params[:q]
#search_results = {
containers: Container.search(query),
cpus: Cpu.search(query),
disks: Disk.search(query),
# ...
}
model = search_params[:model].to_s.downcase.pluralize # <-- this is also pretty smelly
#search = #search_results[model]
end
private
def search_params
params.require(:q, :model)
end
Then in your view you can just access #search.

Iterating through every record in a database - Ruby on Rails / ActiveRecord

n00b question. I'm trying to loop through every User record in my database. The pseudo code might look a little something like this:
def send_notifications
render :nothing => true
# Randomly select Message record from DB
#message = Message.offset(rand(Message.count)).first
random_message = #message.content
#user = User.all.entries.each do
#user = User.find(:id)
number_to_text = ""
#user.number = number_to_text #number is a User's phone number
puts #user.number
end
end
Can someone fill me in on the best approach for doing this? A little help with the syntax would be great too :)
Here is the correct syntax to iterate over all User :
User.all.each do |user|
#the code here is called once for each user
# user is accessible by 'user' variable
# WARNING: User.all performs poorly with large datasets
end
To improve performance and decrease load, use User.find_each (see doc) instead of User.all. Note that using find_each loses the ability to sort.
Also a possible one-liner for same purpose:
User.all.map { |u| u.number = ""; puts u.number }

Rails NoMethodError in loop when method exists

Good day all.
I'm running into a bit of a problem getting a script running on my production environment, even though it works just fine on my dev box. I've verified that all the requisite gems and such are the same version.
I should mention that the script is intended to be run with the script/runner command.
Here is a super-condensed version of what I'm trying to do, centered around the part that's broken:
def currentDeal
marketTime = self.convertToTimeZone(Time.new)
deal = Deal.find(:first, :conditions => ["start_time ? AND market_id = ? AND published = ?", marketTime, marketTime, self.id, 1])
return deal
end
markets = Market.find(all)
markets.each do |market|
deal = market.currentDeal
puts deal.subject
end
Now convertToTimeZone is a method attached to the model. So, this code works just fine on my dev machine, as stated. However, attempting to run it on my production machine results in:
undefined method `subject' for nil:NilClass (NoMethodError)
If, however, I go into the console on the production box and do this:
def currentDeal
marketTime = self.convertToTimeZone(Time.new)
deal = Deal.find(:first, :conditions => ["start_time ? AND market_id = ? AND published = ?", marketTime, marketTime, self.id, 1])
return deal
end
market = Market.find(1)
deal = market.currentDeal
puts deal.subject
It returns the correct value, no problem. So what is going on?
This is on rails v 2.3.5, on both machines.
Thanks for any help
You are looping though all Markets on your production code, but your test snippet is only looking for one. The problem is that one of your Markets in your database has a currentDeal of nil (it has no object associated with it).
Run this on your production console instead.
markets = Market.find(all)
markets.each do |market|
deal = market.currentDeal
if deal
puts deal.subject
else
puts "NO currentDeal for Market with id: #{market.id}"
end
end
This will tell you exactly which Market record is exploding without a currentDeal.
So the question is how to fix it? Either all Markets are expected to have a currentDeal, or sometimes they don't and that's ok. If Market's should always have a currentDeal, then you need to adjust your validations to now allow a Market to be saved without a currentDeal. But given that the currentDeal is a time based thing, I would be that there is times when no deal is scheduled and therefore currentDeal will return nil.
So, more likely, you need to allow for the current deal to be nil. Your test code doesn't do this. It asks the market for the deal, and then the deal for it's subject. If the market return a nil deal, then you immediately ask nil for it's subject and you get the exception because nil does not have a method named subject. A few simple ways to nil protect you code:
deal = market.currentDeal
# simple if
if deal
puts deal.subject
end
# rails try method returns nil if the receiver is nil
# or executes the method if the object supports it
puts deal.try(:subject)
# ternary
puts deal ? deal.subject : "NO DEAL!"
# conditional execution
puts deal && deal.subject
Lastly, a ruby tip. This method is more complicated than it needs to be.
def currentDeal
marketTime = self.convertToTimeZone(Time.new)
deal = Deal.find(:first, :conditions => ["start_time ? AND market_id = ? AND published = ?", marketTime, marketTime, self.id, 1])
return deal
end
Ruby will always return the last expression's result in a method, and a has based conditions finder will clean up that query quite a bit.
def currentDeal
marketTime = self.convertToTimeZone(Time.new)
Deal.find(:first, :conditions => ["start_time > ? AND market_id = ? AND published = ?", marketTime, marketTime, id, true])
end
But this looks more like an association anyway. So you may want to use the association methods to clean this up further.
Clearly you are calling nil.subject, so Deal.find is returning nil in the production code. Your test case is only looking at one specific Market object, but the general case loops through Market objects. Your code needs to handle not finding a currentDeal for a Market object

Resources