How do you mock params when testing a Rails model's setter? - ruby-on-rails

Given the code from the Complex Form part III how would you go about testing the virtual attribute?
def new_task_attributes=(task_attributes)
task_attributes.each do |attributes|
tasks.build(attributes)
end
end
I am currently trying to test it like this:
def test_adding_task_to_project
p = Project.new
params = {"new_tasks_attributes" => [{ "name" => "paint fence"}]}
p.new_tasks_attributes=(params)
p.save
assert p.tasks.length == 1
end
But I am getting the following error:
NoMethodError: undefined method `stringify_keys!' for "new_tasks_attributes":String
Any suggestions on improving this test would be greatly appreciated.

It looks as if new_task_attributes= is expecting an array of hashes, but you're passing it a hash. Try this:
def test_adding_task_to_project
p = Project.new
new_tasks_attributes = [{ "name" => "paint fence"}]
p.new_tasks_attributes = (new_tasks_attributes)
p.save
assert p.tasks.length == 1
end

Can we see the whole stack trace? Where does it think String#stringify_keys! is being called?
Also, params looks odd to me. Is tasks.build() expecting input like this: ["new_tasks_attribute", {"name" => "paint fence"}] ?
If not, maybe you actually want Hash#each_key() instead of Hash#each()?
Need more data. Also, you might consider a Ruby tag to accompany your Rails tag.

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.

Error deleting record in console ruby on rails

I am trying to delete a record using the console. I have a model for "User". I tried several methods in the console:
a = User.where(:id => '18')
a.destroy
a.delete
User.where(:id => '18').destroy
User.where(:id => '18').delete
Using all of these methods, I got the same error: "Wrong number of arguments (0 for 1)"
Does anyone know what I am doing wrong?
Thx!
Try:
a = User.find(18)
a.destroy
When we use where, result will be ActiveRecord::Relation, means multiple records, on which you can't call destroy directly. You will need to call destroy by iterating over the result.
users = User.where(:id => 18)
users.each do |user|
user.destroy
end
I can add something here, The issue with your code that you are passing string while it expects an integer 'Number'
Your code should be as the following:
a = User.where(:id => 18).first
a.destroy
Without using first array of object will be returned and you can't use destroy method directly on it, in case you don't want to add first then your code should be like:
a = User.where(:id => 18)
a.each do |obj|
obj.destroy
end

Problems querying JSON nested hash response in Ruby

Cannot seem to solve this problem:
I'm getting JSON nested hash responses from Lastfm and everything works fine when the response is structures as such:
{"topalbums" =>{"album" =>{"name =>"Friday Night in Dixie"}}}
However if the artist does not have a top album the response is structured this way and I get a NoMethodError undefined method '[]' for nil:NilClass.
{"topalbums" =>{"#text"=>"\n ", "artist"=>"Mark Chestnutt"}}
What I want to do is query the response so I do not keep getting this error.
Here is my method:
def get_albums
#albums = Array.new
#artistname.each do |name|
s = LastFM::Artist.get_top_albums(:artist => name, :limit => 1)
r = JSON.parse(s.to_json)['topalbums']['album']['name']
#albums.push(r)
end
end
which gives me exactly what I want if the artist has a top album, what I need to do is somehow add a condition to query the keys in the nested hash. However, I cannot seem to grasp how to do this as when I add this line of code to check key values:
s.each_key { |key, value| puts "#{key} is #{value}" }
the output I get is this:
topalbums is
so topalbums key does not have a value associated with it.
This is what I have tried so far:
def get_albums
#albums = Array.new
#artistname.each do |name|
s = LastFM::Artist.get_top_albums(:artist => name, :limit => 1)
if s.has_key?('album') #I know this won't work but how can I query this?
r = JSON.parse(s.to_json)['topalbums']['album']['name']
#albums.push r
else
#albums.push(name << "does not have a top album")
end
end
end
How can I fix this so I get 'Mark Chestnut does not have a top album' instead of the NoMethodError? Cheers
Use Hash#fetch default values, I would do as below:
No "album" key present
hash = {"topalbums" =>{"#text"=>"\n ", "artist"=>"Mark Chestnutt"}}
default_album = {"name" => "does not have a top album"}
hash["topalbums"].fetch("album", default_album)["name"]
#=> "does not have a top album"
"album" key present
hash = {"topalbums" =>{"#text"=>"\n ", "artist"=>"Mark Chestnutt", "album" => {"name" => "Foo"}}}
hash["topalbums"].fetch("album", default_album)["name"]
#=> "Foo"
So if the hash does not have an "album" key fetch defaults to default_album else it uses the key it find as in the second case

Add value to :params[]

What i have: (Action in Controller)
def create
#test = Test.new(params[:test])
#test.save
devicefiles = params[:devicefiles]
if devicefiles != nil
devicefiles.each do |attrs|
devicenote = Testdevicenote.new(attrs, :test_id => #test.id)
devicenote.save
end
end
end
This controller action does not show any error message and is rendering the view, but :test_id is not being saved in the database. How can i solve this?
EDIT: Ok whoops, I see it now...
Models only take one hash on initialize, not 2.
Testobjectnote.new(attrs.merge(:test_id => #test.id))
In short no one here has any clue, because that's not enough information. We dont know how your models are setup.
But when debugging models that "won't save" it's often good to use the bang version save, save!. save returns true or false letting you know if it was able to save the record. But save! will raise exceptions when the model can't be saved, and the exception will tell you why.
That exception will likely tell you why the record is not being saved.
Also, its usually better to use the associations, rather than manage the ids yourself.
def create
#test = Test.new(params[:test])
if params[:devicefiles]
params[:devicefiles].each do |attrs|
#test.testdevicenotes << Testdevicenotes(attrs)
end
end
#test.save
end
It's hard to say because you didn't post your view with the form that is posting to the create action, but if it's a typical Rails form, it should probably look like:
def create
#test = Test.new(params[:test])
#test.save
devicefiles = params[:test][:devicefiles]
if devicefiles != nil
devicefiles.each do |attrs|
devicenote = Testdevicenote.new(attrs, :test_id => #test.id)
devicenote.save
end
end
objectfiles = params[:test][:objectfiles]
if objectfiles != nil
objectfiles.each do |attrs|
objectnote = Testobjectnote.new(attrs, :test_id => #test.id)
objectnote.save
end
end
end
This assumes that :devicefiles and :objectfiles are inside the form :test

Why am I getting the error "undefined local variable or method `assigns'"?

I might be missing something basic here, but I'm stumped on this error:
model code:
class CachedStat < ActiveRecord::Base
def self.create_stats_days_ago(days_ago, human_id)
d = Date.today - days_ago.day
#prs = PageRequest.find(:all, :conditions => [ "owner_type = 'Human' and owner_id = ? and created_at = ?", human_id, d] )
end
end
spec code:
it "should create stats for the specified number of days in the past" do
CachedStat.create_stats_days_ago(1, Human.first.id)
assigns[:prs].should eql("foo")
end
The error is:
undefined local variable or method `assigns' for #<Spec::Rails::Example::ModelExampleGroup::Subclass_1:0x2fbac28>
I feel like I'm overlooking something obvious but it's invisible to me. Any suggestions?
Thanks very much!
-Jason
as neutrino said, assigns are only available in controllers/views specs, they're meaningless in a Model specs.
in your case example can look like
it "should create stats for the specified number of days in the past" do
CachedStat.create_stats_days_ago(1, Human.first.id).should eql("foo")
end
I could be wrong here, but assigns might be available only in controller specs.
Also, check your rspec version.

Resources