Query to find "active" ads in Rails - sql

I'm building an ad manager that has the following Schema:
<Campaign id: nil, target_id: nil, date_view_based: nil, start_date: nil, end_date: nil, max_views: nil, cur_views: nil, link_to: nil, alt_data: nil, title_attr: nil, created_at: nil, updated_at: nil, user_id: nil, image_file_name: nil, image_content_type: nil, image_file_size: nil, image_updated_at: nil, link_clicked: nil, name: nil>
What I need is a way to create a method that will run like this...
Campaigns.current?
And output an array of something like this
Campaign.where('date_view_based =? AND end_date >= ? AND start_date <= ?', "Date", Date.today.strftime('%m/%d/%Y'), Date.today.strftime('%m/%d/%Y')).concat(Campaign.where('date_view_based = ? AND max_views >= ?, "View", :cur_views))
However, the Campaign.where('max_views >= ?, :cur_views) doesn't work, so how can I compare these two attributes? Surely there must be a better way to do this any suggestions would be appreciated.
Sorry I thought I had included the structure but I guess I didn't: Campaign.new returns:
#<Campaign id: nil, target_id: nil, date_view_based: nil, start_date: nil, end_date: nil, max_views: nil, cur_views: nil, created_at: nil, updated_at: nil, name: nil>

How about this?
class Campaign < ActiveRecord::Base
# ...
def self.current
date_view_based_enum = %W(Date View)
date = Date.today.strftime('%m/%d/%Y')
where("((start_date <= ? AND end_date >= ?) OR (max_views >= cur_views)) AND date_view_based IN (?)", date, date, date_view_based_enum)
end
end

Related

select vs distinct vs uniq?

I am confused by Ruby's ActiveRecord uniq method. I am using it to try to get back an array of complete objects, not just a particular field.
In my Padrino app script, which saves newspaper names and scores as Score objects, the uniq method by attribute on an ActiveRecord Relation is not working, and neither is distinct, with or without SQL syntax. Can anyone explain what is going on?
class Score < ActiveRecord::Base
def self.from_today
self.where('created_at > ?', Date.today)
end
end
scores = Score.from_today
scores.class
=> Score::ActiveRecord_Relation
scores.first
=> #<Score id: 123, score: -2.55, source: "Mail", created_at: "2016-08-11 04:29:24", updated_at: "2016-08-11 04:29:24">
scores.map(&:source)
=> ["Mail", "Guardian", "Telegraph", "Independent", "Express", "Mail"]
scores.uniq(:source).count
=> 6
scores.distinct(:source).count
=> 6
scores.select('distinct (SOURCE)').count
=> 5 #AHA, it works!
scores.select(:source).distinct
=> #<ActiveRecord::Relation [#<Score id: nil, source: "Telegraph">, #<Score id: nil, source: "Mail">, #<Score id: nil, source: "Independent">, #<Score id: nil, source: "Express">, #<Score id: nil, source: "Guardian">]>
#oops, no it doesn't
In Rails 5 distinct has no parameter. In Rails 4.2 the parameter is only true or false. When true, return distinct records, when false return none distinct records. uniq is in this case only an alias for distinct
So for Rails 4
scores.select(:source).distinct.count is what you want. This restricts distinct to column source

With ActionCable, is there a way to count how many subscribers from inside a channel?

For an app I'm building, I have a "lobby" page where people configure which area they'd like to join. Pretty basic.
I'd like to have a running total of active consumers that are currently subscribed to the channel for this page, so that users know whether or not there's other people around to interact with.
Is there an easy way to do this?
I defined a helper method:
app/channels/application_cable/channel.rb
module ApplicationCable
class Channel < ActionCable::Channel::Base
def connections_info
connections_array = []
connection.server.connections.each do |conn|
conn_hash = {}
conn_hash[:current_user] = conn.current_user
conn_hash[:subscriptions_identifiers] = conn.subscriptions.identifiers.map {|k| JSON.parse k}
connections_array << conn_hash
end
connections_array
end
end
end
Now you can call connections_info anywhere inside your derived channel. The method returns an informational array of data about all the available server socket connections, their respective current_users and all their current subscriptions.
Here is an example of my data connections_info returns:
[1] pry(#<ChatChannel>)> connections_info
=> [{:current_user=>"D8pg2frw5db9PyHzE6Aj8LRf",
:subscriptions_identifiers=>
[{"channel"=>"ChatChannel",
"secret_chat_token"=>"f5a6722dfe04fc883b59922bc99aef4b5ac266af"},
{"channel"=>"AppearanceChannel"}]},
{:current_user=>
#<User id: 2, email: "client1#example.com", created_at: "2017-03-27 13:22:14", updated_at: "2017-04-28 11:13:37", provider: "email", uid: "client1#example.com", first_name: "John", active: nil, last_name: nil, middle_name: nil, email_public: nil, phone: nil, experience: nil, qualification: nil, price: nil, university: nil, faculty: nil, dob_issue: nil, work: nil, staff: nil, dob: nil, balance: nil, online: true>,
:subscriptions_identifiers=>
[{"channel"=>"ChatChannel",
"secret_chat_token"=>"f5a6722dfe04fc883b59922bc99aef4b5ac266af"}]}]
You can then parse this structure the way you want and extract the desired data. You can distinguish your own connection in this list by the same current_user (the current_user method is available inside class Channel < ActionCable::Channel::Base).
If a user connects twice (or more times), then corresponding array elements just double.
Yup there is one :
In your app/channel/what_so_ever_you_called_it.rb:
class WhatSoEverYouCalledItChannel < ApplicationCable::Channel
def subscribed
stream_from "your_streamer_thingy"
#subscriber +=1 #<==== like this
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
#subscriber -=1 #<===== like this
end
def send_message(data)
your_message_mechanic
end
Setup a variable increasing in subscribed
and decreasing in unsubscribed.
You may want store the value in your 'lobby' model , in this case '#subscriber' may be called #lobby.connected_total, i dont know, make this fit your needs.
But this is a way to keep track of number of stream.
Njoy

How to let the SQL results display in Rails's log?

I don't know whether this is a reasonable request. I hope to see the result in the log file such as log/development.log, Now the ActiveRecord::Base.logger.level = 0 and there is only the SQL statements:
User Load (0.6ms) SELECT "users".* FROM "users" WHERE "users"."id" = 3 LIMIT 1
Excursion Load (0.3ms) SELECT "excursions".* FROM "excursions" WHERE "excursions"."id" = $1 LIMIT 1 [["id", "5"]]
Actor Load (0.5ms) SELECT "actors".* FROM "actors" WHERE "actors"."id" = 4 LIMIT 1
There is no results. I know in the 'rails console', when using ActiveRecord::Base.logger = Logger.new(STDOUT), I see the statements and corresponding results:
1.9.3-p551 :001 > ActiveRecord::Base.logger = Logger.new(STDOUT)
=> #<Logger:0x007f80441364d0 #progname=nil, #level=0, #default_formatter=#<Logger::Formatter:0x007f8044135c60 #datetime_format=nil>, #formatter=# <Logger::SimpleFormatter:0x007f8044134bd0 #datetime_format=nil>, #logdev=#<Logger::LogDevice:0x007f8044135440 #shift_size=nil, #shift_age=nil, #filename=nil, #dev=#<IO:<STDOUT>>, #mutex=# <Logger::LogDevice::LogDeviceMutex:0x007f8044135378 #mon_owner=nil, #mon_count=0, #mon_mutex=#<Mutex:0x007f8044134c98>>>>
1.9.3-p551 :002 > User.first
User Load (1.0ms) SELECT "users".* FROM "users" LIMIT 1
Actor Load (0.8ms) SELECT "actors".* FROM "actors" WHERE "actors"."id" = 2 LIMIT 1
ActivityObject Load (0.9ms) SELECT "activity_objects".* FROM "activity_objects" WHERE "activity_objects"."id" = 2 LIMIT 1
=> #<User id: 1, encrypted_password: "$2a$10$wFj.Q9XX0ua16BF0.SIps.VjnfVi8/6egicirc/SFxw3...", password_salt: nil, reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 0, current_sign_in_at: nil, last_sign_in_at: nil, current_sign_in_ip: nil, last_sign_in_ip: nil, authentication_token: nil, created_at: "2015-03-25 22:14:32", updated_at: "2015-03-25 22:14:32", actor_id: 2, language: nil, connected: false, status: "chat", chat_enabled: true, occupation: nil, invitation_token: nil, invitation_created_at: nil, invitation_sent_at: nil, invitation_accepted_at: nil, invitation_limit: nil, invited_by_id: nil, invited_by_type: nil>
What options I can set for this? Thanks in advance!
I researched some ActiveRecord (v3.2.21) code, It seems that there is no option about the SQL result. The above log User Load (1.0ms) SELECT "users".* FROM "users" LIMIT 1 is finished through callingActiveSupport::Notifications.instrument():
# query_cache.rb
def cache_sql(sql, binds)
result =
if #query_cache[sql].key?(binds)
ActiveSupport::Notifications.instrument("sql.active_record",
:sql => sql, :binds => binds, :name => "CACHE", :connection_id => object_id)
#query_cache[sql][binds]
else
#query_cache[sql][binds] = yield
end
result.collect { |row| row.dup }
end
So if I want to see the result in log I can add a log statement:
logger.info "#{result}"
result.collect { |row| row.dup }

Get rspec controller test to do a shallow comparison of BigDecimals

I have an rspec controller with the test:
it "assigns all rate_card_details as #rate_card_details" do
rate_card_detail = FactoryGirl.create(:rate_card_detail)
get :index, {}, valid_session
assigns(:rate_card_details).should eq([rate_card_detail])
end
For most models, this works fine. However, in this case, the rate field is a decimal. This causes the rspec comparison to (for some reason) compare 1 instance of BigDecimal with another, including its location in memory. Here is the error:
Failure/Error: assigns(:rate_card_details).should eq([rate_card_detail])
expected: [#<RateCardDetail rate_card_id: 1, item_id: 1, rate: #<BigDecimal:7f82dcdb0ae0,'0.6941E2',18(18)>, created_at: "2013-06-05 18:12:53", updated_at: "2013-06-05 18:12:53">]
got: [#<RateCardDetail rate_card_id: 1, item_id: 1, rate: #<BigDecimal:7f82dc9a74d0,'0.6941E2',18(18)>, created_at: "2013-06-05 18:12:53", updated_at: "2013-06-05 18:12:53">]
The 2 BigDecimals have the same value, but are different objects. Is there a way to get rspec to treat these as equal when doing a comparison?
it's not pretty but this works with me
it "assigns all rate_card_details as #rate_card_details" do
rate_card_detail = FactoryGirl.create(:rate_card_detail)
get :index, {}, valid_session
assigns(:rate_card_details).first.attributes.values.each_with_index do |rcd,i|
r_c_d = rate_card_detail[i]
if rcd.is_a?BigDecimal
rcd = rcd.to_s
r_c_d = r_c_d.to_s
end
expect(rcd).to eq(r_c_d)
end
end

How to write an rspec test to specify that invalid dates should not be valid for a Rails model?

View
<%= form_for(#new_credit_entry) do |f| %>
<%= f.date_select :created_on%>
I see that I am allowed to specify an invalid date. This results in a params hash as shown below
"credit"=>{"created_on(1i)"=>"2013",
"created_on(2i)"=>"2",
"created_on(3i)"=>"31"
This is of course an incorrect date. So I know my model needs to have validation for this - will probably use the validates_timeliness gem. What I need to know is how to simulate this in a spec
Here's some rails console output
irb(main):056:0> x = Credit.new(created_on: "2013-02-30")
=> #<Credit id: nil, description: nil, credit_category_id: nil, amount: nil, created_on: nil, created_at: nil, updated_at: nil>
irb(main):058:0> x.created_on_before_type_cast
=> "2013-02-30"
irb(main):060:0> x.created_on
=> nil
irb(main):057:0> y = Credit.new(created_on: "2013-03-03")
=> #<Credit id: nil, description: nil, credit_category_id: nil, amount: nil, created_on: "2013-03-03", created_at: nil, updated_at: nil>
irb(main):059:0> y.created_on_before_type_cast
=> "2013-03-03"
irb(main):061:0> y.created_on
=> Sun, 03 Mar 2013
irb(main):062:0> z = Credit.new("created_on(1i)"=>"2013",
irb(main):063:1* "created_on(2i)"=>"2",
irb(main):064:1* "created_on(3i)"=>"31")
=> #<Credit id: nil, description: nil, credit_category_id: nil, amount: nil, created_on: "2013-03-03", created_at: nil, updated_at: nil>
irb(main):065:0> z.created_on
=> Sun, 03 Mar 2013
irb(main):066:0> z.created_on_before_type_cast
=> Sun, 03 Mar 2013
If you use validates_timeliness, it will reject the invalid dates you're trying above. The model will not be valid and created_on will be nil. That includes the date wrapping issue you mention.
After installing validates_timeliness, all you need in your model is:
validates_date :created_on
Here's a spec that tests a variety of scenarios:
describe Credit do
it "rejects single digits" do
credit = Credit.new created_on: "3"
credit.should_not be_valid
credit.created_on.should be_nil
end
it "rejects bad dates" do
credit = Credit.new created_on: "2013-02-31"
credit.should_not be_valid
credit.created_on.should be_nil
end
it "rejects words" do
credit = Credit.new created_on: "some nonsense"
credit.should_not be_valid
credit.created_on.should be_nil
end
it "accepts good dates" do
date = "2013-02-28"
credit = Credit.new created_on: date
credit.should be_valid
credit.created_on.should == Date.parse(date)
end
end
You could probably validate this fairly easily even without the gem, but that looks like a handy gem to use. Don't forget to run the generator to complete installation.
If you do choose to roll your own validation, you might run into some issues.
One is that underlying databases behave somewhat differently when you try to stick a bad value into a date or datetime column; some are more flexible in the values they store and the automatic conversions they do. It's best to validate on the Rails side ahead of time.
A bigger issue is that by the time validations are run, ActiveRecord has already tried to cast the value into a type that matches the database field type. If you've got a date field in the database for created_on, ActiveRecord will convert every value assigned to created_on into a Date object. That will end up being nil for many malformed dates. In your validator, you'll want to look at created_on_before_type_cast, which will be the raw string. ActiveRecord creates a dynamic *_before_type_cast method for every DB column. You can check the format of that and reject bad values.
I mention these caveats to encourage you to stick with the gem. :)