Chartkick and impressionist gem render not working
EDIT 1
Here is a screenshot of the current graph
Original Post
I'm using both chartkick and the impressionist gem and I seem to be running into it's not working in regards the current implementation I am trying to show the impressions per day in the graph.
I have a dashboard, with the raffles built in and I'm trying to get the impressions from the raffles to show in a graph on the Dashboard index using an _overview.html.erb partial.
Here is how I have the code so far.
_overview.html.erb
<%= area_chart charts_impressions_path, colors: ["#7123D1"] %>
raffles_controller.rb
class Dashboard::RafflesController < ApplicationController
before_action :set_dashboard_raffle, only: [:show, :edit, :update, :destroy]
def show
@dashboard_raffle = Dashboard::Raffle.find(params[:id])
impressionist(@dashboard_raffle)
end
def new
@dashboard_raffle = Dashboard::Raffle.new
end
def edit
end
def create
@dashboard_raffle = Dashboard::Raffle.new(dashboard_raffle_params)
if @dashboard_raffle.save
redirect_to @dashboard_raffle, notice: 'Raffle was successfully created.'
else
render :new
end
end
def update
if @dashboard_raffle.update(dashboard_raffle_params)
redirect_to @dashboard_raffle, notice: 'Raffle was successfully updated.'
else
render :edit
end
end
def destroy
@dashboard_raffle.destroy
redirect_to dashboard_raffles_url, notice: 'Raffle was successfully destroyed.'
end
private
def set_dashboard_raffle
@dashboard_raffle = Dashboard::Raffle.find(params[:id])
end
def dashboard_raffle_params
params.require(:dashboard_raffle).permit(:title, :organisation, :prizes, :raise_limit, :deadline, :max_entires, :ticket_price, :raffle_limit)
end
end
raffle.rb
class Dashboard::Raffle < ApplicationRecord
is_impressionable
belongs_to :user
end
charts_controller.rb
class ChartsController < ApplicationController
def impressions
render json: impressions = Dashboard::Raffle.all.map{ |r| r.impressions }.flatten
end
end
dashboard.rb
module Dashboard
def self.table_name_prefix
'dashboard_'
end
end
dashboard_controller.rb
class DashboardController < ApplicationController
before_action :authenticate_user!
def index
@dashboard_raffles = Dashboard::Raffle.all
end
end
Here is the
Any help here is appreciated.
Hey Ben,
Did you run the counter cache migrations?
https://github.com/charlotte-ruby/impressionist#adding-a-counter-cache
https://github.com/charlotte-ruby/impressionist#adding-column-to-model
Hey Jacob,
So I have added a cached impressions_count
counter and column, here is the full schema file
ActiveRecord::Schema.define(version: 20171207012830) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "dashboard_raffles", force: :cascade do |t|
t.string "title"
t.string "organisation"
t.integer "prizes"
t.float "raise_limit"
t.datetime "deadline"
t.integer "max_entires"
t.float "ticket_price"
t.integer "raffle_limit"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "impressions_count"
end
create_table "impressions", force: :cascade do |t|
t.string "impressionable_type"
t.integer "impressionable_id"
t.integer "user_id"
t.string "controller_name"
t.string "action_name"
t.string "view_name"
t.string "request_hash"
t.string "ip_address"
t.string "session_hash"
t.text "message"
t.text "referrer"
t.text "params"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["controller_name", "action_name", "ip_address"], name: "controlleraction_ip_index"
t.index ["controller_name", "action_name", "request_hash"], name: "controlleraction_request_index"
t.index ["controller_name", "action_name", "session_hash"], name: "controlleraction_session_index"
t.index ["impressionable_type", "impressionable_id", "ip_address"], name: "poly_ip_index"
t.index ["impressionable_type", "impressionable_id", "params"], name: "poly_params_request_index"
t.index ["impressionable_type", "impressionable_id", "request_hash"], name: "poly_request_index"
t.index ["impressionable_type", "impressionable_id", "session_hash"], name: "poly_session_index"
t.index ["impressionable_type", "message", "impressionable_id"], name: "impressionable_type_message_index"
t.index ["user_id"], name: "index_impressions_on_user_id"
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.inet "current_sign_in_ip"
t.inet "last_sign_in_ip"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
end
Here is the migration
class AddImpressionsCountToDashboardRaffle < ActiveRecord::Migration[5.1]
def change
add_column :dashboard_raffles, :impressions_count, :int, default: 0
end
end
The counter goes up on refresh so the counter works.
Here is the raffle.rb
model
class Dashboard::Raffle < ApplicationRecord
is_impressionable :counter_cache => true, :column_name => :impressions_count
end
I still have the same code in the charts_controller.rb
so I'm not sure if I need to change anything here now. The impressons are still not showing in the graph.
Ok great, so you can check impressionist off your list now, sounds like it's working as expected.
So for the chart, can you show your view that has your chartkick code in it?
Here is the _overview.html.erb
file
<section class="content active" id="overview">
<section class="grid text-center">
<section class="grid__col grid__col--1-of-4 box">
<h3>Total Payments</h3>
<span>600</span>
</section>
<section class="grid__col grid__col--1-of-4 box">
<h3>Total Raised (All raffles)</h3>
<span>£2,500.00</span>
</section>
<section class="grid__col grid__col--1-of-4 box">
<h3>Total Entries</h3>
<span>142</span>
</section>
</section> <!-- end three grid -->
<section class="grid text-center">
<section class="grid__col grid__col--1-of-2">
<h3>Overall Impressions</h3>
<%= area_chart charts_impressions_path, colors: ["#7123D1"] %>
</section>
<section class="grid__col grid__col--1-of-2">
<h3>Total Impressions</h3>
<%= area_chart charts_impressions_path %>
</section>
</section>
</section>
I have two graphs here doing the same thing, I will change the second graph but I do want to get at least the impressions data flowing.
Also here is a screenshot of the file structure
Scratch that - you're using the chartkick gem. I haven't used that before, so I'll have to read up on it some, but check to make sure the chart is getting data. Does anything show up in your inspect console in your browser?
Here is everything
charts_controller.rb
class ChartsController < ApplicationController
def impressions
# render json: Dashboard::Raffle.all.impressions_count.group_by_day(:created_at).count
render json: impressions = Dashboard::Raffle.all.map{ |r| r.impressions }.flatten
end
end
raffle.rb
class Dashboard::Raffle < ApplicationRecord
is_impressionable :counter_cache => true, :column_name => :impressions_count, :unique => :all
end
raffles_controller.rb
class Dashboard::RafflesController < ApplicationController
before_action :set_dashboard_raffle, only: [:show, :edit, :update, :destroy]
def show
@dashboard_raffle = Dashboard::Raffle.find(params[:id])
impressionist(@dashboard_raffle)
end
def new
@dashboard_raffle = Dashboard::Raffle.new
end
def edit
end
def create
@dashboard_raffle = Dashboard::Raffle.new(dashboard_raffle_params)
if @dashboard_raffle.save
redirect_to @dashboard_raffle, notice: 'Raffle was successfully created.'
else
render :new
end
end
def update
if @dashboard_raffle.update(dashboard_raffle_params)
redirect_to @dashboard_raffle, notice: 'Raffle was successfully updated.'
else
render :edit
end
end
def destroy
@dashboard_raffle.destroy
redirect_to dashboard_raffles_url, notice: 'Raffle was successfully destroyed.'
end
private
def set_dashboard_raffle
@dashboard_raffle = Dashboard::Raffle.find(params[:id])
end
def dashboard_raffle_params
params.require(:dashboard_raffle).permit(:title, :organisation, :prizes, :raise_limit, :deadline, :max_entires, :ticket_price, :raffle_limit)
end
end
dashboard_controller.rb
class DashboardController < ApplicationController
before_action :authenticate_user!
def index
@dashboard_raffles = Dashboard::Raffle.all
end
end
routes.rb
Rails.application.routes.draw do
namespace :dashboard do
resources :raffles
end
devise_for :users
root 'welcome#index'
get 'about', to: 'pages#about', as: 'about'
# Dashboard Routes
get 'dashboard', to: 'dashboard#index', as: 'dashboard'
namespace :charts do
get 'impressions'
end
end
Hopefully somewhere in that file outputs is what you need.
I think your impressions
method needs to look like this
class ChartsController < ApplicationController
def impressions
render json: Dashboard::Raffle.all.map(&:impressionist_count)
end
end
Check your console in your browser and see if there are any errors being thrown
You're going to have to do some debugging, looks like it's an error with the way the data is being presented to chartkick. You'll have to dig into how chartkick is expecting the data to be presented, then verify that your json is structured that way.
Check out this SO: https://stackoverflow.com/questions/24601985/morris-js-uncaught-typeerror-cannot-read-property-match-of-undefined
It's for MorrisJS, but net effect should be the same.
If you can throw together an example repo that produces the same results I can take a better look.
So I had a play around with the documentation.
I got this to work
def impressions
render json: Dashboard::Raffle.all.group(:impressions_count).count
end
If I do
render json: Dashboard::Raffle.all.group_by_day(:impressions_count).count
it doesn't work, so I'm guessing something group_by_day
doesn't work even though I have the gem 'groupdate'
installed, strange.
I need to have a play around and make the graph more readable, with the labels etc.
You'll want to check how the chartkick is expecting the date to be formatted.
Check: https://github.com/ankane/chartkick#data to make sure your date being passed by group_by_date
matches, you may need to do some manipulation of the time before sending to chartkick.
Got it
using the following line
<%= area_chart Dashboard::Raffle.group(:title).group_by_hour(:created_at).maximum(:impressions_count), min: nil, refresh: 10, colors: ["#62518C", "#F7AE07"] %>
Thank you for all your help.
Hey,
Quick question.
I'm trying to target group_by_minute
or group_by_hour
and it just seems to be going on the same line in the graph.
Here is the line of code
<%= area_chart Dashboard::Raffle.group(:title).group_by_minute(:created_at).maximum(:impressions_count), colors: ["#62518C", "#F7AE07"] %>
I get this screenshot see how the value is at 180?
I have increased the counter to 211 screenshot but everything is staying on the one line and it's showing the wrong time, so the time or anything like that isn't been taken into account like so screenshot.
Any tips?
As far as I can tell, to accomplish that your query is going to be quite a bit more complex.
What you end up needing is to provide a hash to chartkick that is similar to this:
[{name: "Test Raffle 2", data: [["8am-9am", 34], ["9am-10am", 12], ["10am-11am", 45], ["11am-12pm", 89]]}]
Which would give you:
In order to achieve this, you have to use the impressionists date search query. The lines below would give you the impression count for the first Raffle
that occured within the last hour.
start_time = Time.now - 1.hour
end_time = Time.now
Dashboard::Raffle.first.impressionist_count( start_date: start_time, end_date: end_time)
You're going to have to make a function that queries each range of time within the date range you want, in the desired intervals (minutes, hours, days, etc) to build the array above.
So I have tried this
<%= area_chart Dashboard::Raffle.first.impressionist_count(start_date: Time.now - 1.hour, end_date: Time.now), refresh: 60, colors: ["#62518C", "#F7AE07"] %>
and I get the following screenshot no data is passing, I would like to eventually pass all raffles impressions into the graph by hour.
Any ideas.
Right, because that's just a single query for a single point in time. Go into your rails console and type in your query and look at the output
Dashboard::Raffle.first.impressionist_count(start_date: Time.now - 1.hour, end_date: Time.now)
You'll see it only gives you a single value. So you have to make a function that will iterate over a range of time to eventually build up a hash that looks like this:
[{name: "Test Raffle 2", data: [["8am-9am", 34], ["9am-10am", 12], ["10am-11am", 45], ["11am-12pm", 89]]}]
This is what I get
irb(main):001:0> Dashboard::Raffle.first.impressionist_count(start_date: Time.now - 1.hour, end_date: Time.now)
Dashboard::Raffle Load (0.4ms) SELECT "dashboard_raffles".* FROM "dashboard_raffles" ORDER BY "dashboard_raffles"."id" ASC LIMIT $1 [["LIMIT", 1]]
(1.5ms) SELECT COUNT(DISTINCT "impressions"."request_hash") FROM "impressions" WHERE "impressions"."impressionable_id" = $1 AND "impressions"."impressionable_type" = $2 AND (created_at >= '2017-12-08 13:07:55.321801' and created_at <= '2017-12-08 14:07:55.321866') [["impressionable_id", 1], ["impressionable_type", "Dashboard::Raffle"]]
=> 0
Yes, the last thing you see there is the return value - 0
So that's one value for one time range for one raffle. Now you need to create a function to build the hash like I showed so you end up with a range of time values along with their respective impression counts for each raffle.
So basically, what you're needing is something similar to:
edits made
raffles = Dashboard::Raffle.all
# time_ranges you need another function that you can give it a start_time and an end_time,
# and then have it spit out an array like you see below that is an array of grouped time spans.
# Example below shows two time groups that span one hour each.
time_ranges = [['2017-12-08 08:00:00 -0600', '2017-12-08 09:00:00 -0600'], ['2017-12-08 09:00:00 -0600', '2017-12-08 10:00:00 -0600']]
raffle_hash = {}
raffles.each do |raffle|
impressions = []
time_ranges.each do |time|
impressions << [[ time[0], time[1] ], raffle.impressionist_count(start_date: time[0], end_date: time[1]) ]
end
raffle_hash[raffle.name] = impressions
end
return raffle_hash
This would give you an output like this:
[{name: "Test Raffle 2", data: [[['2017-12-08 08:00:00 -0600', '2017-12-08 09:00:00 -0600'], 34], [['2017-12-08 09:00:00 -0600', '2017-12-08 10:00:00 -0600'], 12]]}]
This is just an example of the basic steps you're going to have to take to get your output.
Oops, noticed a mistake - that's what I get for trying to write this out in a comment box :)
raffles = Dashboard::Raffle.all
time_ranges = [['2017-12-08 08:00:00 -0600', '2017-12-08 09:00:00 -0600'], ['2017-12-08 09:00:00 -0600', '2017-12-08 10:00:00 -0600']]
raffles_array = [] # don't need raffles_hash, instead just an array to load
raffles.each do |raffle|
impressions = []
time_ranges.each do |time|
impressions << [[ time[0], time[1] ], raffle.impressionist_count(start_date: time[0], end_date: time[1]) ]
end
raffles_array << { name => raffle.name, data => impressions }
end
return raffles_array
You may need to do some more playing but I think that's pretty close.
Ok so how would I go about getting the data into the area chart, do I put that code into a controller or how to I go about testing and using that code?
PORO's are your friend here
So I've had a play around and I have something like this
<%= area_chart raffle_impressions, refresh: 60, colors: ["#62518C", "#F7AE07"] %>
raffle.rb
class Dashboard::Raffle < ApplicationRecord
is_impressionable :counter_cache => true, :column_name => :impressions_count, :unique => :all
has_many :users
def raffle_impressions_count
RaffleImpressions.raffle_impressions_count(self)
end
end
module RaffleImpressions
def raffle_impressions_count(Dashboard::Raffle)
raffles = Dashboard::Raffle.all
time_ranges = [['2017-12-08 08:00:00 -0600', '2017-12-08 09:00:00 -0600'], ['2017-12-08 09:00:00 -0600', '2017-12-08 10:00:00 -0600']]
raffles_array = [] # don't need raffles_hash, instead just an array to load
raffles.each do |raffle|
impressions = []
time_ranges.each do |time|
impressions << [[ time[0], time[1] ], raffle.impressionist_count(start_date: time[0], end_date: time[1]) ]
end
raffles_array << { name => raffle.title, data => impressions }
end
return raffles_array
end
end
But I am recieveing an error screenshot error
That's not how you setup a method to receive params
I would suggest some reading to get a better understanding
- https://launchschool.com/books/ruby/read/methods
- https://en.wikibooks.org/wiki/Ruby_Programming/Syntax/Method_Calls
- http://ruby.bastardsbook.com/chapters/methods
And really my example was only to illustrate the general concept you're after. So with your setup, you'd be looking for something more like:
raffle.rb
class Dashboard::Raffle < ApplicationRecord
is_impressionable :counter_cache => true, :column_name => :impressions_count, :unique => :all
has_many :users
include 'RaffleImpressions'
end
raffle_impressions.rb
module RaffleImpressions
def raffle_impressions_count
time_ranges = [['2017-12-08 08:00:00 -0600', '2017-12-08 09:00:00 -0600'], ['2017-12-08 09:00:00 -0600', '2017-12-08 10:00:00 -0600']]
impressions = []
time_ranges.each do |time|
impressions << [[ time[0], time[1] ], self.impressionist_count(start_date: time[0], end_date: time[1]) ]
end
return { name => self.title, data => impressions }
end
end
Then you should be able to call Dashboard::Raffle.first.raffle_impressions_count
to get an output for a single raffle. If you wanted multiple, you'd have to iterate over each Raffle
, calling raffle_impressions_count
on each iteration and load that into an array that you'd then pass to chartkick.
So I've set this up but I get the following error
Here is the file struture with the code in the files above screenshot
Try this:
raffles.rb
class Dashboard::Raffle < ApplicationRecord
is_impressionable :counter_cache => true, :column_name => :impressions_count, :unique => :all
has_many :users
include RaffleImpressions
end
app/models/concerns/raffle_impressions.rb
module RaffleImpressions
extend ActiveSupport::Concern
def raffle_impressions_count
time_ranges = [['2017-12-08 08:00:00 -0600', '2017-12-08 09:00:00 -0600'], ['2017-12-08 09:00:00 -0600', '2017-12-08 10:00:00 -0600']]
impressions = []
time_ranges.each do |time|
impressions << [[ time[0], time[1] ], self.impressionist_count(start_date: time[0], end_date: time[1]) ]
end
return { :name => self.name, :data => impressions }
end
end
This is using concerns, so I had to add extend ActiveSupport::Concern
to the raffle_impressions.rb
. Testing the execution real quick I noticed I had the return hash wrong, I forgot to make name and data symbols... so be sure to update that in yours.
Also, to help you with debugging, be sure to watch Chris' video on it: https://gorails.com/episodes/debugging-with-better-errors
So I have updated the code to the above and now a few errors are being flagged
I think were getting close.
Did you put raffle_impressions.rb
in app/models/concerns/raffle_impressions.rb
and restart your console / rails server?
Forgot to restart the server, oops.
Ok I get this screenshot the impressions are been show as 2017, not the actual count, plus I'm not seeing the time actually being shown in the axis.
So for example showing the amount of impressions on that raffle per raffle or on all raffles by the hour.
I can't seem to be able to use .goup(:title)
or anything after Dashboard::Raffle.first.raffle_impressions_count
Clone this - https://github.com/nanosplit/deleteme
bundle install
rake db:create
rake db:migrate
rake db:seed
rails s
http://lvh.me:3000
Take a look at the concern and controller:
https://github.com/nanosplit/deleteme/blob/master/app/models/concerns/raffle_impressions.rb
https://github.com/nanosplit/deleteme/blob/master/app/controllers/raffles_controller.rb
This isn't a copy/paste, you're going to have to take a look at what's actually happening, try to understand what is going on at each step, and then work out how to replicate the result in code. Google will be your best friend here. Most of the structure is there for you, you just have to figure out how to get your time ranges sorted out for the given time period you want to record, and then feed that into the raffle_impressions_count
method.
Yeah so I still can't get this working, the example provided is if the raffles were a seperate entity to the dashboard and not actually attached to anything.
I don't believe that passing in the data that I'm trying to pass needs x amount of various other files in need for this to work, as the data is already there and can be shown in the graph.
screenshot of data showing the 222 impressions is spread across the week and not just on the one day.
It's not a diffult thing I'm trying to do here, I can get the data to pass but not actually take into account the day, hour etc i.e. 20 impressions on Thurday and 40 impressions on Friday, they just go into the one bar, not multiple as shown in the Chartkick documentation. I have tried their examples, nothing.
I've followed the chartkick documentation and nothing seems to be working here.
Anything else you can provide here, would be appricated, if not I may as well discontinue the project until a later date or all together.
I can get the data to pass but not actually take into account the day, hour etc i.e. 20 impressions on Thurday and 40 impressions on Friday
If you work out what I gave you, it will do just what you're wanting... to where it will give you a range of impressions for your raffle based on a given time range.
the example provided is if the raffles were a seperate entity to the dashboard and not actually attached to anything
That doesn't matter at all for what you're trying to do.
It's not a diffult thing I'm trying to do here
Just because it's easy to explain doesn't necessariliy mean it's easy to do in code. I've done these exact kind of gigs plenty of times, so trust me, what I provided you will work. However, I can't do the project for you. I got you 3/4 of the way and gave you guidance to complete the other 1/4.
Best of luck.
This is a personal project and not a paid gig.
So I'll give it a shot, if not I'll think of something else.