Rails: Chartkick cummulative user graph - ruby-on-rails

I'm using chartkick in an active admin dashboard and trying to track a cumulative user base over time.
Using groupdate to count by week I can successfully create a chart displaying point in time counts but struggling to display cumulative results.
Any suggestion on best way to approach as I have not found a similar question?
chart partial
<%= javascript_include_tag "https://www.gstatic.com/charts/loader.js" %>
<%= line_chart User.group_by_week(:created_at).count %>
dashboard.rb
column do
panel "Recent User Additions" do
table_for User.order("id desc").limit(10).each do |_user|
column(:email) { |user| link_to(user.email, admin_user_path(user)) }
column(:state_waters) { |user| link_to(user.state_waters, admin_user_path(user)) }
column(:state_waters) { |user| link_to(user.home_port, admin_user_path(user)) }
end
end

If I understand your question, you need to manipulate the hash of data.
In controller do somehing like this:
#data = User.group_by_week(:created_at).count
accumulator = 0
#data.transform_values! do |val|
val += accumulator
accumulator = val
end
Then show the chart in view as <%= line_chart #data %>
If you inspect the hash <%= User.group_by_week(:created_at).count.inspect %> it becomes clear.
In order to use accumulator over any hash, could be useful to add a custom method to Class hash.
module HashPatch
def accumulate_values!
accumulator = 0
transform_values! do |val|
val+= accumulator
accumulator = val
end
end
end
Hash.include HashPatch
points = {'x1' => 0, 'x2' => 10, 'x3' => 10, 'x4' => 10.1}
points.accumulate_values! #=> {"x1"=>0, "x2"=>10, "x3"=>20, "x4"=>30.1}
transform_values! since Ruby v4.2.1

Related

DRY and Elegant way to represent all search combinations without growing exponentially?

Right now I can search the following
1) leaving_from location
2) going_to location
3) leaving_from &
going_to location
if params[:leaving_from].present? && params[:going_to].present?
#flights = Flight.where(:source => params[:leaving_from]).where(:destination => params[:going_to])
elsif params[:leaving_from].present?
#flights = Flight.where(:source => params[:leaving_from])
elsif params[:going_to].present?
#flights = Flight.where(:destination => params[:going_to])
end
Is there a dry way to represent this code above? Basically its a for search function compromised of 2 drop down search boxes. One for leaving from location and another for going to location. With the option of narrowing it down by both locations or just one location.
It works fine now but it isn't very scalable. If I added more search parameters say price and time, it would grow exponentially in order to be able to represent all the states.
For example if I added price my new combinations would be
1) leaving_from location
2) going_to location
3) leaving_from &
going_to location
4) price
5) leaving_from location & price
6) going_to location & price
7) leaving_from location & going_to location & price
I need help to figure out a better way to represent this, or else it would make my controller incredibly bloated.
EDIT FORM CODE --
=form_tag '/flights', :method => :get
%h4
Leaving From:
=select_tag 'leaving_from', content_tag(:option,'select one...',:value=>"")+options_for_select(#flights_source, 'source'), { :class => 'form-control' }
%h4
Going To:
=select_tag 'going_to', content_tag(:option,'select one...',:value=>"")+options_for_select(#flights_destination, 'destination'), { :class => 'form-control' }
%h4=submit_tag "Search", :name => nil, :class => 'btn btn-success btn-md btn-block'
In place of using leaving_from or going_to use source and destination instead and Move all the required parameters under a key, e.g., this solution will work for any no. of keys
'required' => { 'source' => value, 'destination' => value, 'price' => value }
Now in the controller define this method in private
def get_flights(params)
possible_combination = []
conditions = {}
key_array = params['required'].keys
1.upto(key_array.length) { |i| possible_combination + key_array.combination(i).to_a }
possible_combination.reverse.each do |comb|
if comb.collect{ |key| params['required'][key].present? }.inject(:&)
comb.map { |key| conditions[key] = params['required'][key] }
break
end
end
Flight.where(conditions)
end
Call this method from any action
#flights = get_flights(params)
Hope this works! Its an overall idea to make this thing dynamic, you can refactor the code according to your need!
First things first: your code does not do what you think it does, since there is no way for it to execute the third if (every time the third if is true, the first if is as well). On to your question:
#flights = Flight
#flights = #flights.where(:source => params[:leaving_from]) if params[:leaving_from].present?
#flights = #flights.where(:destination => params[:going_to]) if params[:going_to].present?
Or
conditions = {}
conditions[:source] = params[:leaving_from] if params[:leaving_from].present?
conditions[:destination] = params[:going_to] if params[:going_to].present?
#flights = Flight.where(conditions)
How about using ransack which adds your rails to search function very easily.
You just write below, if you use ransack.
# View (Search Form)
<%= search_form_for #q do |f| %>
From: <%= f.text_field :leaving_from_cont %>
To : <%= f.text_field :going_to_cont %>
Price:
<%= f.text_field :price_gteq %> 〜 <%= f.text_field :price_lteq %>
<%= f.submit %>
<% end %>
# Controller
def index
#q = Flight.ransack(parmas[:q])
#flights = #q.result(distinct: true)
end
If a user don't input any fields, ransack don't use the non-input fields value. It means don't add WHERE conditions in DB.
column_name_cont means contain (Like in DB).
column_name_eq means equal (== in DB).
column_name_gteq means greater than equal (<= in DB).
column_name_lteq means less than equal (>= in DB).
etc...
Also you can sort the search result easily by using sort_link methods of ransack.
Please look in ransack.
I was not able to get #RSB's code to work but I was able to use his example to create a method that did work. I call the below code in my action.
#flights = get_flights(search_params)
The search_params method is as follows:
def search_params
params.permit(:leaving_from, :going_to)
params_hash = {'required' => { 'source' => params[:leaving_from], 'destination' => params[:going_to]}}
end
And finally the get_flights method is:
def get_flights(params)
possible_combination = []
conditions = {}
key_array = params['required'].keys
possible_combination = (possible_combination + key_array.combination(key_array.length).to_a).flatten
possible_combination.each do |comb|
conditions[comb] = params['required'][comb] if params['required'][comb].present?
end
Flight.where(conditions)
end
I am still pretty new to ruby and rails so any feedback or suggestions for improvements would be greatly welcome. Thanks!

Rails - how to return just text of an ActiveRecord record

I have a model of cities and one of the attributes is the city 'name'.
I've done a helper method to return 'n' number of cities and the implementation is below:
helper method:
def list_cities(start, stop)
cities = City.find(:all, order: "name asc", limit: stop-start, select: "name")
cities.each do |city|
"<li> #{city.name} </li>"
end
end
view code:
<%= list_cities(1,22) %>
However, it returns the following in the view:
[#<City name: "Abilene">]
How do I get just the text of the city name and get rid of the rest of the query?
The problem is that your method list_cities does not return the string you think it does. It just returns an array of city objects because that's what the method each does. It looks like you're mixing controller and view logic into a helper. What I'd do is:
Set a #cities instance variable in the controller:
#cities = City.find(:all, order: "name asc", limit: stop-start, select: "name")
In the view:
<% #cities.each do |city| %>
<li><%= city.name %></li>
<% end %>
This way you keep your controller and view logic separated.
What Erez answered is the best way to approach this issue. But I'd like to share what you can do to your method to achieve what you want with minimal changes. You need to change your helper to the following
def list_cities(start, stop)
cities = City.all(order: 'name ASC', limit: stop - start, select: 'name')
cities.map { |city| "<li> #{city.name} </li>" }.join.html_safe
end
The other changes are just small refactors to reduce code. Without knowing the rails version you're using, I didn't change the query but if you're using 3.2, you should have access to pluck which is faster since it won't create ActiveRecord objects.
Try:
def list_cities(start, stop)
content = ''
cities = City.find(:all, order: "name asc", limit: stop-start, select: "name")
cities.each do |city|
content << "<li> #{city.name} </li>\n"
end
content
end

Rails : can't convert Regexp into Integer

I have the following code in my rails controller:
#main_mastertest = $connection.execute("SELECT * FROM builds;")
#l2_tmp_mastertest = Array.new
#l2_tmp_mastertest = #main_mastertest.map {|y|[y[0]]}
#l2_mastertest = Array.new
#l2_mastertest = #l2_tmp_mastertest.inject(Hash.new(0)) { |hash,element|
hash[element] +=1
hash }
#l2_mastertest = #l2_mastertest.sort_by { |x, _| x }.reverse
After that I try to do something like this in my view :
<% #l2_mastertest.each_with_index do |row1, index1| %>
<% #l2_mastertest.each_with_index do |row2, index2| %>
<% if row2[0][ /(\d+\.\d+)/ ].to_s == row1[0].to_s %> # LINE X
..................
<% end %>
<% end %>
<% end %>
But it gives me an error on line X saying : can't convert Regexp into Integer
If I simulate what I think is going on with the data structures in your question, I get this:
#l2_mastertest = { '3.1.4' => 7, '1.2.3' => 8 }
=> {"3.1.4"=>7, "1.2.3"=>8}
#l2_mastertest.each_with_index { |row2,index| p row2,index }
["3.1.4", 7]
0
["1.2.3", 8]
1
So a structure like row2[0][ /(\d+\.\d+)/ ] is the same as e.g. "3.1.4"[ /(\d+\.\d+)/ ]
Edit: Removed my previous "answer", because actually "3.1.4"[ /(\d+\.\d+)/ ] => "3.1", which is fine in Ruby (and I learned something today :-). Something else (possibly in code not shown) is making the #l2_mastertest hash not behave as expected.
Potentially this is a database/model issue, and as commenters have suggested, row[0] does not contain a string.
I would suggest instead that SELECT * FROM builds; is risky, since you are relying on database to return columns in particular order. You should change it to fetch the column data you need, in order e.g.
SELECT version, result, build_id FROM builds;
so that you are certain that later processing is working with the column that you think it is. Simply re-building or transferring the database could change the order that the RDBMS returns the columns in a SELECT * and make previously-working code break.

How to show a serialized Array attribute for a Rails ActiveRecord Model in a form?

We're using the "serialize" feature of ActiveRecord in Rails like this:
class User < ActiveRecord::Base
serialize :favorite_colors, Array
....
end
So we can have
u = User.last
u.favorite_colors = [ 'blue', 'red', 'grey' ]
u.save!
So basically ActiveRecord is serializing the array above and stores it in one database field called favorite_colors.
My question is: How do you allow a user to enter his favorite colors in a form?
Do you use a series of textfields? And once they're entered, how do you show them in a form for him to edit?
This is a question related to Rails Form Helpers for serialized array attribute.
Thanks
If you want multi-select HTML field, try:
= form_for #user do |f|
= f.select :favorite_colors, %w[full colors list], {}, :multiple => true
If you're using simple_form gem, you can present the options as check boxes easily:
= simple_form_for #user do |f|
= f.input :favorite_colors, as: :check_boxes, collection: %w[full colors list]
I have solved this problem by 'flattening' the array in the view and
reconstituting the array in the controller.
Some changes are needed in the model too, see below.
class User < ActiveRecord::Base
serialize :favorite_colors, Array
def self.create_virtual_attributes (*args)
args.each do |method_name|
10.times do |key|
define_method "#{method_name}_#{key}" do
end
define_method "#{method_name}_#{key}=" do
end
end
end
end
create_virtual_attributes :favorite_colors
end
If you don't define methods like the above, Rails would complain about the form element's
names in the view, such as "favorite_colors_0" (see below).
In the view, I dynamically create 10 text fields, favorite_colors_0, favorite_colors_1, etc.
<% 10.times do |key| %>
<%= form.label :favorite_color %>
<%= form.text_field "favorite_colors_#{key}", :value => #user.favorite_colors[key] %>
<% end %>
In the controller, I have to merge the favorite_colors_* text fields into an array BEFORE calling
save or update_attributes:
unless params[:user].select{|k,v| k =~ /^favorite_colors_/}.empty?
params[:user][:favorite_colors] = params[:user].select{|k,v| k =~ /^favorite_colors_/}.values.reject{|v| v.empty?}
params[:user].reject! {|k,v| k=~ /^favorite_colors_/}
end
One thing I'm doing is to hard-code 10, which limits how many elements you can have in the favorite_colors array. In the form, it also outputs 10 text fields. We can change 10 to 100 easily. But we will still have a limit. Your suggestion on how to remove this limit is welcome.
Hope you find this post useful.
To allow access to AR attributes, you have to grant them like this:
class User < ActiveRecord::Base
serialize :favorite_colors, Array
attr_accessible :favorite_colors
....
end

Passing parameter back to Model to refine Random action

I'm creating an application that'll display a random picture based upon a defined letter in a word.
Images are attached to a Pictures model (containing another "letter" field) using Paperclip, and will be iterated through in an each block.
How would I go about passing the letter back from the each block to the model for random selection.
This is what I've come up with so far, but it's throwing the following error.
undefined method `%' for {:letter=>"e"}:Hash
Model:
def self.random(letter)
if (c = count) != 0
find(:first, :conditions => [:letter => letter], :offset =>rand(c))
end
end
View:
<% #letters.each do |a| %>
<%= Picture.random(a).image(:thumb) %>
<% end %>
Thanks
One problem is your conditions has a syntax error. The hash notation is wrong:
:conditions => [:letter => letter]
should be
:conditions => {:letter => letter}
Also, it seems to me that your random scope will always exclude the first Picture if you don't allow an offset of 0. Besides that, do you really want to return nil if the random number was 0?
Picture.random(a).image(:thumb) would throw "undefined method 'image' for nil:NilClass" exception every time c==0. Can probably just use:
def self.random(letter)
find(:first, :conditions => {:letter => letter}, :offset =>rand(count))
end
EDIT: You'll either need to guarantee that your db has images for all letters, or tell the user no image exists for a given letter.
<% #letters.each do |a| %>
<% if pic = Picture.random(a).image(:thumb) %>
<%= pic.image(:thumb) %>
<% else %>
No image available for <%= a %>
<% end %>
<% end %>
Or the like...
EDIT: Actually I don't think your offset strategy will work. One other approach would be to return the set of images available for the given letter and randomly select from that collection, something like:
def self.random(letter)
pics = find(:all, :conditions => {:letter => letter})
pics[rand(pics.size)] if !pics.blank?
end

Resources