Understanding Ruby script for cassandra database - ruby-on-rails

I am a Ruby novice. But due to some problem I have to handle the code as our ruby developer is not available. We are using cassandra database to get values from a Ruby (Sinatra) web service and put it into the Cassandra keyspace. But due to some problem , the data is failing to insert.
In the following code partners_daily , partner_monthly etc are column family (tables) in the stats keyspace(database).
if params and !partner_id.nil? and !activity_type.nil?
{
:partners_daily => "#{partner_id}_#{activity_type}_#{success == 1 ? 'sucess' : "failure:#{failure_code}"}_#{time.year}_#{time.month}_#{time.day}",
:partners_monthly => "#{partner_id}_#{activity_type}_#{success == 1 ? 'sucess' : "failure:#{failure_code}"}_#{time.year}_#{time.month}",
:partners_alltime => "#{partner_id}_#{activity_type}_#{success == 1 ? 'sucess' : "failure:#{failure_code}"}",
:channels_daily => "#{channel_id}_#{activity_type}_#{success == 1 ? 'sucess' : "failure:#{failure_code}"}_#{time.year}_#{time.month}_#{time.day}",
:channels_monthly => "#{channel_id}_#{activity_type}_#{success == 1 ? 'sucess' : "failure:#{failure_code}"}_#{time.year}_#{time.month}",
:channels_alltime => "#{channel_id}_#{activity_type}_#{success == 1 ? 'sucess' : "failure:#{failure_code}"}",
:countries_daily => "#{country}_#{activity_type}_#{success == 1 ? 'sucess' : "failure:#{failure_code}"}_#{time.year}_#{time.month}_#{time.day}",
:countries_monthly => "#{country}_#{activity_type}_#{success == 1 ? 'sucess' : "failure:#{failure_code}"}_#{time.year}_#{time.month}",
:countries_alltime => "#{country}_#{activity_type}_#{success == 1 ? 'sucess' : "failure:#{failure_code}"}"
}.each do |k,v|
stats.add(k, v, 1, 'count')
end
return "Activity stored in stats"
end
else
return "Error: client headers missing"
end
end
def count(table, key)
require 'cassandra-cql' # requiring this at the top was leading to error: unconfigured columnfamily
cqldb = CassandraCQL::Database.new('127.0.0.1:9160', {:keyspace => 'plystats'})
query = "update partners_daily set count = ? where key = ?"#"update #{table} set count = count+1 where key = ?;"
#return cqldb.execute(query, 0, 'sonia').inspect
return query
end
I want to know how the data inserting logic in it is being performed, and where ? Is it in stats.add(k, v, 1, 'count') ?
and is there any error in the inserting part because its failing.

I want to know how the data inserting logic in it is being performed, and where ? Is it in stats.add(k, v, 1, 'count') ?
Yes, that's where it should be happening. Between the {} are dictionary/hash values:
{
:partners_daily => # …
}.each do |k,v|
A loop is started with the each method, and each entry is decomposed and put into k and v, the key in k and the value in v. For example, the first record in the hash is:
:partners_daily => "#{partner_id}_#{activity_type}_#{success == 1 ? 'sucess' : "failure:#{failure_code}"}_#{time.year}_#{time.month}_#{time.day}",
This would then decompose within the each loop to:
k = :partners_daily
v = # The result of
"#{partner_id}_#{activity_type}_#{success == 1 ? 'sucess' : "failure:#{failure_code}"}_#{time.year}_#{time.month}_#{time.day}",
I don't now what the values are for partner_id etc, but making some up it'd look something like "123_sales_sucess_2013_6_01"
Notice there's a typo for the word success in there.
It's a bit confusing due to the multiple double quotes and braces, so I'd change this to:
[partner_id, activity_type, (success == 1 ? 'success' : "failure:#{failure_code}"), time.year, time.month, time.day].join("_")
But notice that there's a lot of repetition in there, so I'd change the whole hash to (at least):
success_string = success == 1 ?
'success' :
"failure:#{failure_code}"
data = {
:partners_daily => [partner_id, activity_type,success_string,time.year,time.month,time.day].join("_"),
:partners_monthly => [partner_id,activity_type,success_string,time.year,time.month].join("_"),
:partners_alltime => [partner_id,activity_type,success_string].join("_"),
:channels_daily => [channel_id,activity_type,success_string,time.year,time.month,time.day].join("_"),
:channels_monthly => [channel_id,activity_type,success_string,time.year,time.month].join("_"),
:channels_alltime => [channel_id,activity_type,success_string].join("_"),
:countries_daily => [country,activity_type,success_string,time.year,time.month,time.day].join("_"),
:countries_monthly => [country,activity_type,success_string,time.year,time.month].join("_"),
:countries_alltime => [country,activity_type,success_string].join("_")
}
data.each do |k,v|
# more code…
It starts to be easier to read and see the logic. Also, by putting the hash into the data variable instead of working on it immediately, it allows you to inspect it more easily, e.g.
warn "data = #{data.inspect}"
would output a representation of the data to the console, so at least you could get an idea of what the script is attempting to put in. At the top of this code, you could also add warn "script = #{script.inspect}" to check what the script object looks like.
If the script object is a Cassandra instance i.e. there's something like script = Cassandra.new "blah", "blahblah" that sets it up, then the add method is this one
The signature given is add(column_family, key, value, *columns_and_options) but that doesn't seem to match the call you have:
stats.add(k, v, 1, 'count')
should (probably) be:
stats.add('count', k, v, 1)
In fact, I'm not even sure that the concatenation in the data hash should happen and maybe all of that should just be passed to add, but it's your data so I can't be sure.
Feel free to comment below and I'll update this.
Trying it in IRB to check it for syntax errors:
success = 1
# => 1
partner_id = 123
# => 123
activity_type = "something"
# => "something"
time = Time.now
# => 2013-06-05 11:17:50 0100
channel_id = 456
# => 456
country = "UK"
# => "UK"
success_string = success == 1 ?
'success' :
"failure:#{failure_code}"
# => "success"
data = {
:partners_daily => [partner_id, activity_type,success_string,time.year,time.month,time.day].join("_"),
:partners_monthly => [partner_id,activity_type,success_string,time.year,time.month].join("_"),
:partners_alltime => [partner_id,activity_type,success_string].join("_"),
:channels_daily => [channel_id,activity_type,success_string,time.year,time.month,time.day].join("_"),
:channels_monthly => [channel_id,activity_type,success_string,time.year,time.month].join("_"),
:channels_alltime => [channel_id,activity_type,success_string].join("_"),
:countries_daily => [country,activity_type,success_string,time.year,time.month,time.day].join("_"),
:countries_monthly => [country,activity_type,success_string,time.year,time.month].join("_"),
:countries_alltime => [country,activity_type,success_string].join("_")
}
# => {:partners_daily=>"123_something_success_2013_6_5", :partners_monthly=>"123_something_success_2013_6", :partners_alltime=>"123_something_success", :channels_daily=>"456_something_success_2013_6_5", :channels_monthly=>"456_something_success_2013_6", :channels_alltime=>"456_something_success", :countries_daily=>"UK_something_success_2013_6_5", :countries_monthly=>"UK_something_success_2013_6", :countries_alltime=>"UK_something_success"}

Related

Create key if it doesn't exist in nested hashes

I've been trying to figure out how to write this ruby code more eloquently. Does someone have a better solution?
a[:new] = {} if a[:new].nil?
a[:new].merge!( { new_key => new_value } )
is there a way to write this in a more elegant way? I come across this a lot when dealing with nested hashes that need to check whether an key exist and if not, create it.
Write it as below taking the help of Hash#to_h and NilClass#to_h
a[:new] = a[:new].to_h.merge( { new_key => new_value } )
Example :
hsh1[:a] # => nil
hsh1[:a] = hsh1[:a].to_h.merge({1=>2})
hsh1[:a] # => {1=>2}
hsh2 = {:a => {'k' => 2}}
hsh2[:a] # => {"k"=>2}
hsh2[:a] = hsh2[:a].to_h.merge({1=>2})
hsh2 # => {:a=>{"k"=>2, 1=>2}}
Do this at the beginning:
a = Hash.new{|h, k| h[k] = {}}
then, without caring whether a has a key :new or not, do
a[:new].merge!(new_key => new_value)
or
a[:new][new_key] = new_value

Rails based on condition assign model to variable

I have the following method:
def self.get_rec(product, type)
if type == "A"
db = Pwvav
elsif type == "B"
db = Pwbab
elsif type == "C"
db = Pwvub
else type == "D"
db = Tic
db.find_by_id(product.id).recommendation.split(",").each do |rec|
r = Recommendation.find_by_id(rec)
pr = Model.new(:rating_set => rating_set.id, :recommendation_id => r.id, :product_id => product.id)
pr.save
end
end
end
When I run the method, the db.find is not working as I expect. If replace db.find_by_id(product.id).recommendation.split(",").each do |rec| with Pwvav.find_by_id(product.id).recommendation.split(",").each do |rec| for example, it works. How do I chose which model to call based on what the type equals?
your error, if I'm right, is with your structure. the find_by_id code is inside the else so it doesn't create other records when type is not 'D'. Try the following code which I think is more readable
def self.get_rec(product, type)
db = case type
when 'A' then Pwvav
when 'B' then Pwbab
when 'C' then Pwvub
when 'D' then Tic
end
db.find_by_id(product.id).recommendation.split(",").each do |rec|
r = Recommendation.find_by_id(rec)
pr = Model.new(rating_set: rating_set.id, recommendation_id: r.id, product_id => product.id)
pr.save
end
end
You haven't close your "if - elsif - else condition, then the last block with the find, find_by_id and your new is only executed when type == 'D'.
Insert an end after
db = Tic

How to compare two Hashes so to return true if both Hashes have same keys/values?

I am using Ruby on Rails 3.2.2 and Ruby 1.9.3. I would like to compare two Hashes (A and B) so to return true if a Hash (A) include all keys/values of the other Hash (B).
For example, given I have
params.inspect
# => { "action"=>"...", "controller"=>"...", "key_param1"=>"value_param1", , "key_param2"=>"value_param2", "key_param3"=>"value_param3", ... }
my_hash1.inspect
# => { "key_param1"=>"value_param1", "key_param2"=>"value_param2" }
my_hash2.inspect
# => { "key_param4"=>"value_param4", "key_param1"=>"value_param1" }
my_hash3.inspect
# => {}
Then I am looking for a method (or something like that) in order to make
params.has_same_keys_and_values_as?(my_hash1)
# => true
params.has_same_keys_and_values_as?(my_hash2)
# => false
params.has_same_keys_and_values_as?(my_hash3)
# => true
Assuming that Hash#keys and Hash#values return values in the same order:
params.values_at(*my_hash.keys) == my_hash.values
I think you can use:
a.slice(*b.keys) == b
where a and b are your hashes. note that slice is a rails method and not ruby.
in plain ruby you can write:
a.keep_if{|k, v| b[k]} == b
class Hash
def >=(b)
eq = true
b.each { |k, v| eq &= !(self.include? k) ? false : ( ( ((self[k]&&v).is_a? Hash) && !((v||self[k]).empty?) ) ? self[k]>=v : true)}
return eq
end
end
params = { "action"=>"...", "controller"=>"...", "key_param1"=>"value_param1", "key_param2"=>"value_param2", "key_param3"=>"value_param3" }
my_hash1 = { "key_param1"=>"value_param1", "key_param2"=>"value_param2" }
my_hash2 = { "key_param4"=>"value_param4", "key_param1"=>"value_param1" }
my_hash3 = {}
p params >= my_hash1 #true
p params >= my_hash2 #false
p params >= my_hash3 #true
It'll work with "deep" hashes as well:
b = {1 => {2 => {} }, 4 => {} }
a = {1 => {2 => {3 => {} }}, 4 => {}, 5 => "123" }
p a >= b #true
p b >= a #false
P.S.
Whether one hash includes another hash
EDIT: This is assuming that the values/keys are not in the same order in both hashes.
You could iterate over each key in hash1 and use has_key? on hash2. Keep in mind this is naive solution that could be slow for large datasets.
Checkout has_key? and has_value? here: http://www.ruby-doc.org/core-1.9.3/Hash.html#method-i-has_key-3F
You could loop as follows:
hash1.each_key { |key|
if hash2.has_key?(key)
do whatever
endif
}
better way, there's an active support method for this, hash.diff, wrap it with .empty? to check if they are the same
{:one => 1}.diff({:one => 1}).empty?
=> true
{:one => 1}.diff({:one => 2}).empty?
=> false
http://as.rubyonrails.org/classes/ActiveSupport/CoreExtensions/Hash/Diff.html

Rails 3 ActiveRecord: "OR-ing" together multiple bools

I have an ActiveRecord object RaceCarDriver. Three of the fields are boolean: is_from_texas, is_from_arkansas, and is_from_indiana. In the user search interface, the user could select neither to see all results, select is_from_texas to see only drivers from Texas, or select is_from_texas and is_from_indiana to see all drivers from one of those states.
Now, I know this example is a bit contrived, but I wanted to avoid the complexity of the actual app.
My best attempt is along these lines:
#drivers = RaceCarDriver.select('name').
where(params[:check_texas] == 0 || :is_from_texas => params[:check_texas]).
where(params[:check_arkansas] == 0 || :is_from_arkansas => params[:check_arkansas]).
where(params[:check_indiana] == 0 || :is_from_indiana => params[:check_indiana])
However, this ANDS the chained where clauses together, making it so that if Texas and Indiana were both checked a driver would have to be from both states.
Again, I know this is contrived. Any help is appreciated.
RaceCarDriver.select('name').
where("is_from_texas = ? OR is_from_arkansas = ? OR is_from_indiana = ?",params[:is_from_texas],params[:is_from_arkansas],params[:is_from_indiana])
#drivers = RaceCarDriver.select('name')
#drivers = #drivers.where(:is_from_texas => params[:check_texas]) if params[:check_texas]
#drivers = #drivers.where(:is_from_arkansas => params[:check_arkansas]) if params[:check_arkansas]
#drivers = #drivers.where(:is_from_indiana => params[:check_indiana]) if params[:check_indiana]
EDIT
Solution 1:
where_options = { :is_from_texas => params[:check_texas],
:is_from_arkansas => params[:check_arkansas],
:is_from_indiana => params[:check_indiana] }.select{|k,v| v.present? }
where_conditions = where_options.map{|k,v| "#{k} = #{v}"}.join(" OR ")
#drivers = RaceCarDriver.select('name').where(where_conditions)
Solution 2:
#scoped_drivers = RaceCarDriver.select('name')
#drivers = []
#drivers << #scoped_drivers.where(:is_from_texas => params[:check_texas]) if params[:check_texas]
#drivers << #scoped_drivers.where(:is_from_arkansas => params[:check_arkansas]) if params[:check_arkansas]
#drivers << #scoped_drivers.where(:is_from_indiana => params[:check_indiana]) if params[:check_indiana]
#drivers.flatten!

Ruby way to loop and check subsequent values against each other

I have an array that contains dates and values. An example of how it might look:
[
{'1/1/2010' => 'aa'},
{'1/1/2010' => 'bb'},
{'1/2/2010' => 'cc'},
{'1/2/2010' => 'dd'},
{'1/3/2010' => 'ee'}
]
Notice that some of the dates repeat. I'm trying to output this in a table format and I only want to show unique dates. So I loop through it with the following code to get my desired output.
prev_date = nil
#reading_schedule.reading_plans.each do |plan|
use_date = nil
if plan.assigned_date != prev_date
use_date = plan.assigned_date
end
prev_date = plan.assigned_date
plan.assigned_date = use_date
end
The resulting table will then look something like this
1/1/2010 aa
bb
1/2/2010 cc
dd
1/3/2010 ee
This work fine but I am new to ruby and was wondering if there was a better way to do this.
Enumerable.group_by is a good starting point:
require 'pp'
asdf = [
{'1/1/2010' => 'aa'},
{'1/1/2010' => 'bb'},
{'1/2/2010' => 'cc'},
{'1/2/2010' => 'dd'},
{'1/3/2010' => 'ee'}
]
pp asdf.group_by { |n| n.keys.first }.map{ |a,b| { a => b.map { |c| c.to_a.last.last } } }
# >> [{"1/1/2010"=>["aa", "bb"]}, {"1/2/2010"=>["cc", "dd"]}, {"1/3/2010"=>["ee"]}]
Which should be a data structure you can bend to your will.
I don't know as though it's better, but you could group the values by date using (e.g.) Enumerable#reduce (requires Ruby >= 1.8.7; before that, you have Enumerable#inject).
arr.reduce({}) { |memo, obj|
obj.each_pair { |key, value|
memo[key] = [] if ! memo.has_key?(key);
memo[key] << value
}
memo
}.sort
=> [["1/1/2010", ["aa", "bb"]], ["1/2/2010", ["cc", "dd"]], ["1/3/2010", ["ee"]]]
You could also use Array#each to similar effect.
This is totally a job for a hash.
Create a hash and use the date as the hashkey and an empty array as the hashvalue.
Then accumulate the values from the original array in the hashvalue array

Resources