Rails console vs module - ruby-on-rails

I have an array of hashes (data)
sort_column = "Me.You.Them"
I need to sort the array by the sort_column.
When I do data.sort_by { |h| h.dig(sort_column.split('.').map(&:to_s))} in the rails console, I get the data returned sorted, but when I place the exact same method in my helper module, it returns nil.
Anyone knows why?
Here are my methods:
My helper file:
module DatatablesHelper
def fetch_data(data, sort_column, sort_direction)
if sort_column.present?
if sort_direction == "desc"
sorted_leads = data.sort_by { |h| h.dig(sort_column.split('.').map(&:to_s))}
else
sorted_leads = data.sort_by { |h| -h.dig(sort_column.split('.').map(&:to_s))}
end
else
sorted_results = data
end
return sorted_results
end
end
My controller and action
class DataController < ApplicationController
include DatatablesHelper
def get_data
data = [
{},
{}
] # Here imagine we have a large array of hashes returned from an api
sort_column = params['order']["0"] # => "Me.You.Them"
final = fetch_data(data, sort_column, "desc")
render json: {
recordsTotal: data.count,
recordsFiltered: final.count,
data: final
}
end
end

Related

Ruby on Rails APi call from Controller with too much logic. Where and how can I make it simpler?

I was asked to fetch the Pokemon API from Ruby on Rails. But I'm struggling because I have too much logic in the controller, which is not recommended. Where and how can I make the call so I have less logic inside my controller. I have this:
def index
pokemons = []
response = HTTParty.get("https://pokeapi.co/api/v2/pokemon?limit=6&offset=1")
response = JSON.parse(response.body)
response.each do |k, value|
if k == "results"
value.each do |key, val|
response = HTTParty.get(key["url"])
response = JSON.parse(response.body)
pokemons.push(response)
end
end
end
#poke_json = pokemons.map do |poke|
Pokemon.new(img:poke['sprites']['other']['dream_world']["front_default"], name: poke['forms'][0]['name'], weight: poke['weight'], poke_type: poke['types'][0]['type']['name'], poke_ability: poke['abilities'][0]['ability']['name'], poke_id: poke['id'])
end
render json: { pokemons: #poke_json }
end ```
As a first step you could extract the API client depending code into a class on its own:
# in app/models/pokemon_api.rb
class PokemonApi
def self.to_json
pokemons = []
response = HTTParty.get("https://pokeapi.co/api/v2/pokemon?limit=6&offset=1")
response = JSON.parse(response.body)
response.each do |k, value|
if k == "results"
value.each do |key, val|
response = HTTParty.get(key["url"])
response = JSON.parse(response.body)
pokemons.push(response)
end
end
end
pokemons.map do |poke|
Pokemon.new(
img:poke['sprites']['other']['dream_world']["front_default"],
name: poke['forms'][0]['name'], weight: poke['weight'],
poke_type: poke['types'][0]['type']['name'],
poke_ability: poke['abilities'][0]['ability']['name'],
poke_id: poke['id']
)
end
end
end
and then just call that method in your controller
def index
render json: { pokemons: PokemonApi.to_json }
end

How to add item to json object ruby on rails?

I want to join items from 2 tables. There output:
"costs":[
{
"id":2,
"cost_name":"rent office",
"user_id":2,
"fix":true,
"amount":300300,
"created_at":"2018-11-05T18:36:19.108+06:00",
"updated_at":"2018-11-05T18:36:19.108+06:00"
},
{
"id":3,
"cost_name":"new computer",
"user_id":2,
"fix":false,
"amount":350000,
"created_at":"2018-11-06T14:44:49.805+06:00",
"updated_at":"2018-11-06T14:44:49.805+06:00"
}
],
"users":[
[
"Vi_Ok",
2
]
]
}
I want to add parameter of users (user name which is "Vi_Ok") add to every cost. How you noticed there in both table exist userId. Now code looks:
def index
#costs = Cost.all
#user_name = #costs.pluck(:user_id)
#user_name = User.find(#user_name).pluck(:name, :id)
# #costs.push("name" => #user_name.pluck(:name) this one just try to add
render json: {costs: #costs, name: #user_name}
end
You can write a custom method in your model and call it in index action, which returns all the costs with the username like below:
def self.list_costs
cost_list = []
costs = Cost.all
costs.each do |cost|
cost_info = cost.attributes
cost_info[:user_name] = cost.user.name
cost_list << cost_info
end
cost_list
end
class CostsController < ApplicationController
def index
render json: {costs: Cost.cost_list }
end
end
Supposing User has_many Costs,
What you provided,
hash = {"costs"=>[{"id"=>2, "cost_name"=>"rent office", "user_id"=>2, "fix"=>true, "amount"=>300300, "created_at"=>"2018-11-05T18:36:19.108+06:00", "updated_at"=>"2018-11-05T18:36:19.108+06:00"}, {"id"=>3, "cost_name"=>"new computer", "user_id"=>2, "fix"=>false, "amount"=>350000, "created_at"=>"2018-11-06T14:44:49.805+06:00", "updated_at"=>"2018-11-06T14:44:49.805+06:00"}], "users"=>[["Vi_Ok", 2]]}
Proceed,
costs, users = hash['costs'], hash['users']
costs.each { |c| c['user_name'] = users.detect { |u| u[1] == c['user_id'] }[0] }
Above will add user_name in each cost hash.

Efficient way to convert collection to array of hashes for render json

Is there a more efficient way to map a collection of objects to an array of hashes?
def list
#photos = []
current_user.photos.each do |p|
#photos << {
id: p.id, label: p.name, url: p.uploaded_image.url(:small),
size: p.uploaded_image_file_size
}
end
render json: { results: #photos }.to_json
end
This seems a bit verbose but the structure it returns is required by the frontend.
Update
So .map is the preferred method then?
Don't do it in the contoller
Don't generate the json response with map, let's as_json(*) method do that for you.
Don't use # variable in the json render.
Don't use {}.to_json the render json: {} do it under the hood.
in the photo model.
def as_json(*)
super(only: [:id], methods: [:label, :url, :size])
end
alias label name
def size
uploaded_image_file_size
end
def url
uploaded_image.url(:small)
end
controller code.
def list
render json: { results: current_user.photos }
end
Please try
def list
#photos = current_user.photos.map { |p| { id: p.id, label: p.name, url: p.uploaded_image.url(:small), size: p.uploaded_image_file_size } }
render json: { results: #photos }
end
As a starting point, it should be closer to:
def list
#photos = current_user.photos.map do |p|
{
id: p.id, label: p.name, url: p.uploaded_image.url(:small),
size: p.uploaded_image_file_size
}
end
render json: { results: #photos }
end
Just for fun, you could do something like:
class PhotoDecorator < SimpleDelegator
def attributes
%w(id label url size).each_with_object({}) do |attr, hsh|
hsh[attr.to_sym] = send(attr)
end
end
def label
name
end
def url
uploaded_image.url(:small)
end
def size
uploaded_image_file_size
end
end
And then:
> #photos = current_user.photos.map{ |photo| PhotoDecorator.new(photo).attributes }
=> [{:id=>1, :label=>"some name", :url=>"http://www.my_photos.com/123", :size=>"256x256"}, {:id=>2, :label=>"some other name", :url=>"http://www.my_photos/243", :size=>"256x256"}]
You can also do that like this
def array_list
#photos = current_user.photos.map { |p| { id:
p.id, label: p.name, url: p.uploaded_image.url(:small), size: p.uploaded_image_file_size } }
render json: { results: #photos }
end

How does Rails params parse hash from string

I'm learning Ruby on Rails and got curious how the params method works. I understand what it does, but how?
Is there a built-in method that takes a hash string like so
"cat[name]"
and translates it to
{ :cat => { :name => <assigned_value> } }
?
I have attempted to write the params method myself but am not sure how to write this functionality in ruby.
The GET parameters are set from ActionDispatch::Request#GET, which extends Rack::Request#GET, which uses Rack::QueryParser#parse_nested_query.
The POST parameters are set from ActionDispatch::Request#POST, which extends Rack::Request#POST, which uses Rack::Multipart#parse_multipart. That splays through several more files in lib/rack/multipart.
Here is a reproduction of the functionality of the method (note: this is NOT how the method works). Helper methods of interest: #array_to_hash and #handle_nested_hash_array
require 'uri'
class Params
def initialize(req, route_params = {})
#params = {}
route_params.keys.each do |key|
handle_nested_hash_array([{key => route_params[key]}])
end
parse_www_encoded_form(req.query_string) if req.query_string
parse_www_encoded_form(req.body) if req.body
end
def [](key)
#params[key.to_sym] || #params[key.to_s]
end
def to_s
#params.to_s
end
class AttributeNotFoundError < ArgumentError; end;
private
def parse_www_encoded_form(www_encoded_form)
params_array = URI::decode_www_form(www_encoded_form).map do |k, v|
[parse_key(k), v]
end
params_array.map! do |sub_array|
array_to_hash(sub_array.flatten)
end
handle_nested_hash_array(params_array)
end
def handle_nested_hash_array(params_array)
params_array.each do |working_hash|
params = #params
while true
if params.keys.include?(working_hash.keys[0])
params = params[working_hash.keys[0]]
working_hash = working_hash[working_hash.keys[0]]
else
break
end
break if !working_hash.values[0].is_a?(Hash)
break if !params.values[0].is_a?(Hash)
end
params.merge!(working_hash)
end
end
def array_to_hash(params_array)
return params_array.join if params_array.length == 1
hash = {}
hash[params_array[0]] = array_to_hash(params_array.drop(1))
hash
end
def parse_key(key)
key.split(/\]\[|\[|\]/)
end
end

Setup fake data just once

I'm working on a rails application with no models. I have a class in lib, FakeMaker, that builds up a bunch of fake entities for display purposed.
I want to test out deletion functionality but the problem is that my fake data set re-initializes every time I hit the controller.
I'd like to run the data test creator only once so that I only have one set of fake data.
I have tried using ||=, before_filter, class methods in FakeMaker, sessions storage but they all seem to have the issue of reinitializing the data set everytime the controller is hit.
Controller code:
class HomeController < ApplicationController
include FakeMaker
before_filter :set_up_fake_data
def index
#workstations = #data[:workstations]
#data_sources = #data[:data_sources]
end
private
def set_fake_data
#data ||= session[:fake_data]
end
def initialize_data
session[:fake_data] = set_up_fake_data
end
end
FakeMaker in lib:
module FakeMaker
include ActionView::Helpers::NumberHelper
SOURCE_CIDNE = "CIDNE"
SOURCE_DCGS = "DCGS"
TYPES_CIDNE = Faker::Lorem.words(num = 10)
TYPES_DCGS = Faker::Lorem.words(num = 4)
def set_up_fake_data
#data ||= { workstations: fake_maker("workstation", 8), data_sources: fake_maker("data_source", 2) }
end
def fake_maker(type, count)
fake = []
case type
when "report"
count.times { fake << fake_report }
when "workstation"
count.times { fake << fake_workstation }
when "data_source"
fake = fake_data_source
end
fake
end
def fake_report
report = { source: [SOURCE_CIDNE, SOURCE_DCGS].sample,
count: number_with_delimiter(Faker::Number.number(5), :delimiter => ',') }
report[:type] = report[:source] == SOURCE_CIDNE ? TYPES_CIDNE.sample : TYPES_DCGS.sample.capitalize
report
end
def fake_workstation
{ name: Faker::Lorem.word,
downloaded: number_with_delimiter(Faker::Number.number(3), :delimiter => ','),
available: number_with_delimiter(Faker::Number.number(5), :delimiter => ','),
last_connect: fake_time,
queueJSON: fake_queueJSON,
historyJSON: fake_historyJSON }
end
def fake_data_source
data_sources = []
["CIDNE", "DCGS"].each do |source|
data_sources << { type: source,
name: Faker::Internet.url,
status: ["UP", "DOWN"].sample }
end
data_sources
end
def fake_historyJSON
history = []
12.times { history << fake_history }
history
end
def fake_queueJSON
queue = []
35.times { queue << fake_queue }
queue
end
def fake_history
{ sentTimestamp: fake_time,
reportID: Faker::Number.number(5)}
end
def fake_queue
{ priority: Faker::Number.number(3),
queuedTimestamp: fake_time,
reportID: Faker::Number.number(5)}
end
def fake_time
Random.rand(10.weeks).ago.strftime("%Y-%m-%d %H:%M:%S")
end
end

Resources