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

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.

Related

Rails Postgresql JSONB column check if key value was changed?

Given I have data_config as jsonb column
I want to check the value of specific key if was changed before saving.
Test Data:
data_config: {
"status"=>true,
"text"=>"sample"
}
Rails Model:
attr_json_config(default_container_attribute: :data_config)
attr_json :status, :boolean, default: true
before_save :check_config
def check_config
if self.status_changed? # => want to have something like this to check if value changed?
# ... do something
end
end
This works in my case, but I believe there's a better way.
status_val = self.attr_json_changes.changes_to_save.try(:[], "status")
# above returns [true, false] (old & new value)
if status_val.try(:first) != status_val.try(:last)
...do something
end
Why don't you compare the params with the current value?
Because you give very little information, I'll assume you want to change other key in jsonb if the status changed. In this case, you should make your record immutable before the updated value is ready to commit.
So in your controller, it should be like this:
class MyController < ApplicationController
def update
if params[:data_config][:status] != #model.['data_config']['status'] # check if value change
# ... do something
new_params = do_something
end
if #model.update(new_params)
# render success
else
# render failed
end
end
end
I hope this approach can help you.

How to get push value key in Firebase Ruby REST wrapper

I am working on a project to do CRUD Operations to firebase. I made use of this to help facilitate and link my ruby project to firebase.
Functions:
def delete_firebase(event_params,rootpath="Events/")
query = init_firebase.delete(rootpath,event_params)
end
def new_firebase(event_params,rootpath="Events")
query = init_firebase.push(rootpath,event_params)
end
def init_firebase # Inits firebase project with URL and secret
firebaseURL = "myfirebaseprojecturl"
firebaseSecret = "myfirebasesecret"
firebase = Firebase::Client.new(firebaseURL, firebaseSecret)
end
Event params consist of my event parameters as shown below
def event_params
params.require(:event).permit(:eventID, :eventName, :attachment, :eventNoOfPpl, :eventAdminEmail, {eventpics: []})
end
I encountered an issue. When I push with push() into firebase, there is a random key like -LSFOklvcdmfPOWrxgBo. In such case, the structure of the document would look like this:
But I cannot delete anything from -LSFOklvcdmfPOWrxgBo as I do not have the value. I used delete() from Oscar's firebase-ruby gem. I would appreciate any help with this issue.
I re-read the gem docs, and got some help from my friends and came up with two solutions
The body's response has response.body # => { 'name' => "-INOQPH-aV_psbk3ZXEX" } and thus, you're able to find out the name if you'd like
Change the index, and don't use .push, instead I made use of .set and did a random number for every event
Final solution
def load_firebase(root_path = "Events")
firebase_json = init_firebase.get(root_path)
if valid_json?(firebase_json.raw_body)
#json_object = JSON.parse(firebase_json.raw_body)
end
end
def update_firebase(event_params, root_path = "Events/")
init_firebase.update("#{root_path}#{event_params["eventID"]}", event_params)
end
def delete_firebase(event_params, root_path = "Events/")
init_firebase.delete("#{root_path}#{event_params["eventID"]}")
end
def save_firebase(event_params, root_path = "Events/")
init_firebase.set("#{root_path}#{event_params["eventID"]}", event_params)
end

what var type to dynamically access Model's attribute from another controller? (Rails 4.2)

Goal: dynamically update another Model's properties (Tracker) from Controller (cards_controller.rb), when cards_controller is running the def update action.
Error I receive : NameError in CardsController#update, and it calls out the 2nd last line in the
def update_tracker(card_attribute) :
updated_array = #tracker.instance_variable_get("#{string_tracker_column}")[Time.zone.now, #card.(eval(card_attribute.to_s))]
Perceived problem: I have everything working except that I don't know the appropriate way to 'call' the attribute of Tracker correctly, when using dynamic attributes.
The attribute of the Tracker is an array (using PG as db works fine), I want to
figure out what property has been changed (works)
read the corresponding property array from Tracker's model, and make a local var from it. (works I think, )
push() a new array to the local var. This new array contains the datetime (of now) and, a string (with the value of the updated string of the Card) (works)
updated the Tracker with the correct attribute.
With the following code from the cards_controller.rb
it's the if #card.deck.tracked in the update method that makes the process start
cards_controller.rb
...
def update
#card = Card.find(params[:id])
if #card.deck.tracked
detect_changes
end
if #card.update_attributes(card_params)
if #card.deck.tracked
prop_changed?
end
flash[:success] = "Card info updated."
respond_to do |format|
format.html { render 'show' }
end
else
render 'edit'
end
end
...
private
def detect_changes
#changed = []
#changed << :front if #card.front != params[:card][:front]
#changed << :hint if #card.hint != params[:card][:hint]
#changed << :back if #card.back != params[:card][:back]
end
def prop_changed?
#changed.each do |check|
#changed.include? check
puts "Following property has been changed : #{check}"
update_tracker(check)
end
end
def update_tracker(card_attribute)
tracker_attribute = case card_attribute
when :front; :front_changed
when :back; :back_changed
when :hint; :hint_changed
end
string_tracker_column = tracker_attribute.to_s
#tracker ||= Tracker.find_by(card_id: #card.id)
updated_array = #tracker.instance_variable_get("#{string_tracker_column}")[Time.zone.now, #card.(eval(card_attribute.to_s))]
#tracker.update_attribute(tracker_attribute, updated_array)
end
Edit: For clarity here's the app/models/tracker.rb:
class Tracker < ActiveRecord::Base
belongs_to :card
end
Your use of instance_variable_get has been corrected, however this approach is destined to fail because ActiveRecord column values aren't stored as individual instance variables.
You can use
#tracker[string_column_changed]
#card[card_attribute]
To retrieve attribute values by name. If you want to get an association, use public_send. The latter is also useful if there is some accessor wrapping the column value (eg carrierwave)
From your error it seem your issue is this:
#tracker.instance_variable_get("#{string_tracker_column}")
evaluates to this after string interpolation:
#tracker.instance_variable_get("front_changed")
which is incorrect use of instance_variable_get. It needs an # prepended:
#tracker.instance_variable_get("#front_changed")
Seems like using instance_variable_get is unnecessary, though, if you set attr_reader :front_changed on the Tracker model.

Ruby: Formatting table data to JSON

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.

How to format values before saving to database in rails 3

I have a User model with Profit field. Profit field is a DECIMAL (11,0) type. I have a masked input on the form which allows user to input something like $1,000. I want to format that value and remove everything except numbers from it so i will have 1000 saved. Here is what i have so far:
class User < ActiveRecord::Base
before_save :format_values
private
def format_values
self.profit.to_s.delete!('^0-9') unless self.profit.nil?
end
end
But it keeps saving 0 in database. Looks like it is converting it to decimal before my formatting function.
Try this:
def profit=(new_profit)
self[:profit] = new_profit.gsub(/[^0-9]/, '')
end
First of all, this:
def format_values
self.profit.to_s.delete!('^0-9') unless self.profit.nil?
end
is pretty much the same as this:
def format_values
return if(self.profit.nil?)
p = self.profit
s = p.to_s
s.delete!('^0-9')
end
So there's no reason to expect your format_values method to have any effect whatsoever on self.profit.
You could of course change format_values to assign the processed string to self.profit but that won't help because your cleansing logic is in the wrong place and it will be executed after '$1,000' has been turned into a zero.
When you assign a value to a property, ActiveRecord will apply some type conversions along the way. What happens when you try to convert '$1,000' to a number? You get zero of course. You can watch this happening if you play around in the console:
> a = M.find(id)
> puts a.some_number
11
> a.some_number = 'pancakes'
=> "pancakes"
> puts a.some_number
0
> a.some_number = '$1,000'
=> "1,000"
> puts a.some_number
0
> a.some_number = '1000'
=> "1000"
> puts a.some_number
1000
So, your data cleanup has to take place before the data goes into the model instance because as soon as AR gets its hands on the value, your '$1,000' will become 0 and all is lost. I'd put the logic in the controller, the controller's job is to mediate between the outside world and the models and data formatting and mangling certainly counts as mediation. So you could have something like this in your controller:
def some_controller
fix_numbers_in(:profit)
# assign from params as usual...
end
private
def fix_numbers_in(*which)
which.select { |p| params.has_key?(p) }.each do |p|
params[p] = params[p].gsub(/\D/, '') # Or whatever works for you
end
end
Then everything would be clean before ActiveRecord gets its grubby little hands on your data and makes a mess of things.
You could do similar things by overriding the profit= method in your model but that's really not the model's job.
class User < ActiveRecord::Base
before_save :format_values
private
def format_values
self.profit = profit.to_s.gsub(/\D/,'') if profit
end
end
def format_values
self.profit.to_d!
end
I recommend you to write custom setter for this particular instance variable #profit:
class User
attr_accessor :profit
def profit= value
#profit = value.gsub(/\D/,'')
end
end
u = User.new
u.profit = "$1,000"
p u.profit # => "1000"
I would suggest using the rails helper of number with precision. Below is some code.
Generic Example:
number_with_precision(111.2345, :precision => 1, :significant => true) # => 100
Rails code Example:
def profit=(new_profit)
number_with_precision(self[:profit], :precision => 1, :significant => true)
end

Resources