Ruby: Formatting table data to JSON - ruby-on-rails

I'm trying to make a timeline for an the bugs and updates for an open source project. I'm new to ruby, but I'm getting some experience gradually.
I've created a table called historical_gems, with the following code in the model:
class HistoricalGem < ActiveRecord::Base
attr_accessible :build_date, :version
belongs_to :ruby_gem, :foreign_key => :gem_id
end
I'm using a JS Plugin (http://almende.github.com/chap-links-library/js/timeline/doc) that requires objects with two field names ('start' for the date and 'content' for the title) in the JSON Array to display the timeline using JS.
I believe I have to do something like this in the controller which defines my timeline method to render the JSON:
def timelinem
#name = params[:id]
#rpm = AbcTable.find_by_name(#name)
respond_to do |format|
format.json { render :json => #rpm.json_timelines }
end
end
Then I probably would have to define a 'json_timelines' method inside my model, maybe something like:
def json_timelines(gems = [])
dates = []
gem_id.each { |p|
gems << p
dates << p.build_date(gems)
end
}
end
I'm only starting out with RoR, and even after hours with guides and tutorials and debugging, I'm not able to put together this code. Can anyone help me out, please? I don't think I'm doing it right.
btw, don't be too harsh if I overlooked something obvious, I'm only 16 :)

The render :json => ... in your code should work fine (but with HistoricalGem instead of AbcTable) as long as json_timelines returns an object that's serializable as JSON (e.g., an Array or a Hash).
Try something like this for your method definition:
def json_timelines(gems = [])
gems.map do |g|
{
:content => g.title,
:date => g.build_date
}
end
end
The above snippet assumes your "historical_gems" table has "title" and "build_date" columns; if not, adjust the code to reflect the fields you actually want represented in your JSON.

Related

Why is my object getting serialized into a string not text in my Rails ActiveRecord DB?

I am currently trying to store a user object accessed from the Soundcloud API into my local rails ActiveRecord database. The object acts like a hash.
This is what the object/hash looks like:
#<SoundCloud::HashResponseWrapper avatar_url="https://i1.sndcdn.com/avatars-000296065573-ewlbh2-large.jpg" city="Brooklyn" country="United States" description="Raising The Bar Since 2007" discogs_name=nil first_name="Fool's" followers_count=7993682 followings_count=84 full_name="Fool's Gold" id=5636679 kind="user" last_modified="2018/03/09 19:40:55 +0000" last_name="Gold" myspace_name=nil online=false permalink="foolsgoldrecs" permalink_url="http://soundcloud.com/foolsgoldrecs" plan="Pro Plus" playlist_count=250 public_favorites_count=808 reposts_count=449 subscriptions=#<Hashie::Array [#<SoundCloud::HashResponseWrapper product=#<SoundCloud::HashResponseWrapper id="creator-pro-unlimited" name="Pro Unlimited">>]> track_count=1037 uri="https://api.soundcloud.com/users/5636679" username="Fool's Gold Records" website="http://smarturl.it/FoolsGoldSpotify" website_title="Spotify">
My ActiveRecord Schema.rb
ActiveRecord::Schema.define(version: 20180323143520) do
create_table "soundcloud_users", force: :cascade do |t|
t.string "user_name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.text "user_hash"
end
end
What I have tried:
Researching how to serialize objects in Ruby I found that I was supposed to make the field-to-be-serialized of type 'text'. I have made two different attempts at serializing then de-serializing the data, all of which have failed. Here is what I did:
1) Serialize as a Hash.
Code I tried:
class SoundcloudUser < ApplicationRecord
serialize :user_hash, Hash
end
I then get this Error:
TypeError in SoundcloudQueriesController#create
allocator undefined for Proc
2) Serialize using YAML:
Code I tried:
class SoundcloudUser < ApplicationRecord
YAML::dump(:user_hash)
end
This time I don't get an error. When I try to access the hash I wrote this code in my index def just to display it quickly:
def index
#user = SoundcloudUser.find(2)
#user_hash = YAML::load(#user.user_hash)
end
However, I for some reason cannot access the parameters the way I expect i.e. #user_hash.followers_count.
So I decided to go into rails console and see what was going on:
> #user = SoundcloudUser.find(6)
> #user_hash = #user.user_hash
> #user_hash.class
=> String
So for some reason, even though the field in active record is text, it is getting stored as a string? Or maybe the read back is converting it to a string? I really don't know what's going on, but am a bit lost. It's my first time serializing data, and the Soundcloud API has always returned weird objects that never work the way I expect. SUPER appreciate any help or advice!
EDIT #2:
Trying with JSON as per suggestion
When I changed my code as you prescribed, I still get the same issue:
SoundCloudQueriesController.rb
def index
end
def create
#user_url = params[:soundcloud_url]
#soundcloud_user = SoundcloudUser.new
#query = Query.new
#user = #query.query_user(#user_url)
#soundcloud_user.user_name = #user.username
#soundcloud_user.user_hash = JSON.parse(#user.to_json).symbolize_keys
#soundcloud_user.save
end
SoundcloudUser.rb (model)
class SoundcloudUser < ApplicationRecord
end
Output (querying the database in rails console):
Typing in the following commands into rails console:
#user = SoundcloudUser.find(7)
#user_hash = #user.user_hash
This gives the following output of the hash (which still seems to be weirdly formatted with the "\" that weren't there before):
=>
"{:id=>8, :kind=>\"user\", :permalink=>\"alex\", :username=>\"🔥𝔞𝔩𝔢𝔵 🔥\", :last_modified=>\"2018/03/15 18:15:56 +0000\", :uri=>\"https://api.soundcloud.com/users/8\", :permalink_url=>\"http://soundcloud.com/alex\", :avatar_url=>\"https://i1.sndcdn.com/avatars-000208970259-vngd3i-large.jpg\", :country=>\"Germany\", :first_name=>\"Alexander\", :last_name=>\"Ljung\", :full_name=>\"Alexander Ljung\", :description=>\"Hi, my name is Alex. I like Sound. Founder & Chairman, SoundCloud.\", :city=>\"Berlin/NYC\", :discogs_name=>nil, :myspace_name=>nil, :website=>nil, :website_title=>nil, :track_count=>268, :playlist_count=>34, :online=>false, :plan=>\"Pro Plus\", :public_favorites_count=>1601, :followers_count=>185322, :followings_count=>1697, :subscriptions=>[{\"product\"=>{\"id\"=>\"creator-pro-unlimited\", \"name\"=>\"Pro Unlimited\"}}], :reposts_count=>132}"
When I try to access the hash as you prescribed:
> #user_hash[:followers_count]
I get the following message:
TypeError: no implicit conversion of Symbol into Integer
from (irb):6:in `[]'
from (irb):6
Weirdly enough, it gives the same error even for fields that are not integer values like :followers_count (:username for example).
I think you are right, this is not a hash. So now I am super lost at how to store this in my database haha. I tried just storing it with no serialization, but I get the same issue of it being seemingly converted to a string.
I see in your source code:
class Query
def initialize
#client = Soundcloud.new(:client_id => API_KEY)
end
def query_user(user_url)
#user_url = user_url
#user = #client.get('/resolve', :url => #user_url)
end
end
At this point #user is a still a SoundCloud::HashResponseWrapper object.
If you wanna store the data as string text you'll need to first do:
#user.to_json
To save it with symbolized keys in the db:
data = JSON.parse(#user.to_json).symbolize_keys
sc_user = SoundcloudUser.new(user_hash: data)
But you still can't call .key on a hash. If you want to call the value of the key you'll need to do:
sc_user.user_hash[:username] #for example
There are ways to extend your model but that's out of scope for this question.
UPDATE: Here's your updated controller:
class SoundcloudQueriesController < ApplicationController
def new
end
def index
#user = SoundcloudUser.find(3)
#user_hash = #user.user_hash # but do you even need this?
end
def create
sc_user_data = Query.new.get_user params[:username]
#soundcloud_user = SoundcloudUser.create({
user_name: sc_user_data[:username],
user_hash: JSON.parse(sc_user_data.to_json).symbolize_keys
})
end
def show
end
end
You'll also need to modify your Query class to be able to build a user from the souncloud username. It will look like this:
class Query
BASE_RESOLVE_URL="http://soundcloud.com/"
def initialize
#client = Soundcloud.new :client_id => ENV['SC_CLIENT_ID']
end
def query_user(user_url)
#user_url = user_url
#user = #client.get('/resolve', :url => #user_url)
end
def get_user(username)
#client.get('/resolve', url: BASE_RESOLVE_URL+username)
end
end
I'm not exactly sure what you want to use the query_user method for, or if you can just remove it and use get_user instead. In any case I forked your repo, got the controller create test passing. Will send PR.

Call 'structure' to save in postgres using rails

I have a 'publication' data structure, and my table name in postgres is 'publications'. I get a tweet from streaming and parse it according in class TweetFetcher. How do I call publication controller to save it to the database? PublicationController has the standard scrum abilities (new, create, show, edit...), where create is:
def create
#publication = Publications.new(params[:publication])
if #publication.save
redirect_to :action => 'list'
else
#subjects = Subject.find(:all)
render :action => 'new'
end
end
and parte of my twitter code is
class TweetFetcher
def saveTweet(parsedTweet)
pT = JSON.parse(parsedTweet)
#save here. like this?
#PublicationController.create(parsedTweet)
end
end
You shouldn't be using your controller in this situation, instead you can just build your record like this:
class TweetFetcher
def saveTweet(parsedTweet)
pT = JSON.parse(parsedTweet)
publication = Publications.new
publication.sample_field = pT.sample_field
... # set additional attributes
publication.save
end
end
Additionally, here is some advice/information that is not directly related to your question, but are things you should know:
The class name of your models should be singular, so instead of Publications, it should be Publication.
Method and variable names should be in snake case rather than camel case (i.e. save_tweet instead of saveTweet or parsed_tweet instead of parsedTweet).
The new hash syntax is prefered, unless of course you are using a Ruby version below 1.9. This looks like { key: value } instead of { key => value }.
Indentation for Ruby code is typically 2 spaces.
That being said, I would change your code like this:
PublicationsController#create
def create
#publication = Publication.new(params[:publication])
if #publication.save
redirect_to action: 'list'
else
#subjects = Subject.find(:all)
render action: 'new'
end
end
TweeFetcher
class TweetFetcher
def save_tweet(tweet)
parsed_tweet = JSON.parse(tweet)
publication = Publication.new
publication.sample_field = parsed_tweet.sample_field
... # set additional attributes
publication.save
end
end

Ruby on Rails 4 fields_for number of repetitions

I would like to display a form with four nested fieldsets for associated objects. The only way I've found is to override the initialize method and define four associations:
RUBY
def initialize(attributes = {})
super
4.times { items << Item.new }
end
and then display nested fields normally:
HAML
= f.fields_for :items do |item|
= render 'item_fields', f: item
This is not working when I try to edit objects that already exist and have fewer number of associated items.
Any help will be appreciated.
MORE INFO:
Order has_many items
OrderSet has_many orders
Orders are added through the cocoon gem (there is at least one order in each set)
There should always be four items for each order. But when there are less items I don't want to save empty records, instead I would like to just display remaining items as empty.
The initialize is not the place as it is executed every time a new Order instance is created, this means: also when retrieving an existing order from the database.
Imho the view is also not the optimal place.
I would solve this in the controller:
def new
#order = Order.new
4.times { #order.items.build }
end
and then you can just leave your model/view as they were originally.
If you always want to show 4 nested items, you can do something similar in the edit action (to fill up to 4)
def edit
#order = Order.find(params[:id])
(#order.items.length...4).each { #order.items.build }
end
In my personal opinion this is cleaner then doing it in the view.
[EDIT: apparently it is a double nested form]
So, in your comment you clarified that it is a double-nested form, in that case, I would use the :wrap_object option as follows (it gets a bit hard to write a decent example here, without you giving more details, so I keep it short and hope it is clear). I am guessing you have a form for "something", with a link_to_add_association for :orders, and that order needs to have several (4) items, so you could do something like:
= link_to_add_association('add order', f, :orders,
:wrap_object => Proc.new { |order| 4.times { order.items.build}; order })
Before your f.fields_for in your view, or even in your controller, you can check the length of .items() and create new objects as required:
(o.items.length...4).each { f.object.items << Item.new}
= f.fields_for :items do |item|
= render 'item_fields', f: item

Sorting in rails using helper

I'm a novice in ruby-on-rails.
I have an applications counting distance between metro station and a ATM.
There's two models with many-to-many relation: Station, Cashpoint. And there's a controller SHOW, that should get the station and show ATMs in order there's proximity to the station.
class StationsController < ApplicationController
def show
#station = Station.find(params[:id])
#cashpoints = #station.cashpoints.find(:all)
respond_to do |format|
format.html
end
end
end
Also there's a helper that counts distance using Google Directions API.
module StationsHelper
def count_distance(origin,destination)
...
return {:text => ... # 1 min
, :value => ... # 60 (seconds)
}
end
end
All this works properly.
But I'm wondering how to order ATMs by :value returned by StationsHelper?
I tried to write something in controller similar to:
#cashpoints = #station.cashpoints.find(:all,
:order => count_distance(#station.address, cashpoint.address)[:value])
But it's evidently doesn't work 'cause I have know idea how to link single cashpoint object
to count_distance method parameter.
May be you can help me, it appears that my project structure is wrong to do this.
Try this:
#cashpoints = #station.cashpoints.find(:all).sort_by { |c| count_distance(c.address, #station.address) }

Rails 3 displaying tasks from partials

My Tasks belongs to different models but are always assigned to a company and/or a user. I am trying to narrow what gets displayed by grouping them by there due_at date without doing to many queries.
Have a application helper
def current_tasks
if user_signed_in? && !current_company.blank?
#tasks = Task.where("assigned_company = ? OR assigned_to = ?", current_company, current_user)
#current_tasks = #tasks
else
#current_tasks = nil
end
end
Then in my Main view I have
<%= render :partial => "common/tasks_show", :locals => { :tasks => current_tasks }%>
My problem is that in my task class I have what you see below. I have the same as a scope just named due_today. when I try current_tasks.due_today it works if I try current_tasks.select_due_today I get a undefined method "select_due_tomorrow" for #<ActiveRecord::Relation:0x66a7ee8>
def select_due_today
self.to_a.select{|task|task.due_at < Time.now.midnight || !task.due_at.blank?}
end
If you want to call current_tasks.select_due_today then it'll have to be a class method, something like this (translating your Ruby into SQL):
def self.select_due_today
select( 'due_at < ? OR due_at IS NOT NULL', Time.now.midnight )
end
Or, you could have pretty much the same thing as a scope - but put it in a lambda so that Time.now.midnight is called when you call the scope, not when you define it.
[edited to switch IS NULL to IS NOT NULL - this mirrors the Ruby in the question, but makes no sense because it will negate the left of the ORs meaning]

Resources