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
Related
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
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
I have a User model and a List model in my app.
pages_controller.rb
class PagesController < ApplicationController
def home
if user_signed_in?
#lists = current_user.lists
# raise #lists.inspect
#new_list = current_user.lists.build
end
end
end
pages/home.html.erb
<%= raise #lists.inspect %>
Now, my current user has no lists associated with him .
When I uncomment the 3rd line in "Pages#home" raise #lists.inspect I get the output like so : []
But, when I comment that line out, then the exception inside home.html.erb is raised , and its output is like so : [#<List id: nil, name: nil, description: nil, user_id: 1, created_at: nil, updated_at: nil>]
Why is there a difference in output for the same #lists.inspect line ?
EDIT : When I use #lists = current_user.lists.all instead of #lists = current_user.lists then I get an empty array at both places . Why the difference in behavior between the 2 codes ?
Because you build lists in the controller after the first raise:
#new_list = current_user.lists.build
It's the same code, but the data is different, because you did something to it.
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. :)
I want to retrieve the maxmimum lenght validation of a ActiveRecord field in one of my views.
The following works fine in rails console and returns the correct value :
irb(main):046:0> micropost = Micropost.new
=> #<Micropost id: nil, content: nil, user_id: nil, created_at: nil, updated_at: nil>
irb(main):047:0> micropost._validators[:content][1].options[:maximum].to_s
=> "140"
However, when I use the same code in my controller it returns nil :
class PagesController < ApplicationController
def home
#title = "Home"
if signed_in?
#micropost = Micropost.new
#feed_items = current_user.feed.paginate(:page => params[:page])
#content_max = #micropost._validators[:content][1].options[:maximum].to_s
end
end
...
end
I also tried to include a method in my ApplicationHelper, which also returns nil ;-(
def content_max
Micropost._validators[:content][1].options[:maximum].to_s
end
What am I doing wrong?
The _validators array might not be in the same order whether you're in the console or in a web request.
Micropost._validators[:content].find {|v| v.class == ActiveModel::Validations::LengthValidator} .options[:maximum].to_s
should do what you want.
IMHO, a better solution would be to store the length in a constant (I somehow doubt the _validators array is part of the official API) :
class Micropost < ActiveRecord::Base
MAX_CONTENT_LENGTH = 140
validates :content, :length => {:maximum => MAX_CONTENT_LENGTH}
# Rest of the code...
end
and get the length with :
Micropost::MAX_CONTENT_LENGTH