I would like to save query result into redis using JSON serialization and query it back.
Getting query results to json is pretty easy:
JSON.generate(Model.all.collect {|item| item.attributes})
However I did not find a proper way to deserialize it back to ActiveRecord. The most straight-forward way:
JSON.parse(#json_string).collect {|item| Model.new.from_json(item)}
Gives me an error:
WARNING: Can't mass-assign protected attributes: id
So id gets empty. I thought of just using OpenStruct for the views instead of ActiveRecord but I am sure there is a better way.
You could instantiate the new object from JSON and then assign the id afterwards. Probably best to create your own method for this:
class Model
def self.from_json_with_id(params = {})
params = JSON.parse(params)
model = new(params.reject {|k,v| k == "id"})
model.id = params["id"]
model
end
end
Or maybe just override the from_json() method.
Why not like this:
JSON.parse(#json_string).each do |item|
item.delete(:id) # I tested it in my case it also works without this line
object=Model.create(item)
end
If the host that created the JSON adds a JSON root you might have to use item[1] instead of item.
Related
I was under the impression that we could give Rails a model (anything responding to to_param or cache_key) to Rails.cache.fetch and it would create a key and cache the response of the block.
I have this code:
class ResultsPresenter
def initialize(project)
#project = project
end
def results
results = Rails.cache.fetch("#{#project}/results") do
sleep 3
a = long_running_query
b = another_long_query
c = a + b
end
end
end
# called
project = Project.find(params[:project_id]_
presenter = ResultsPresenter.new(project)
presenter.results
#project was passed to ResultsPresenter and is an ActiveRecord model. When I specify "#{#project.to_param}/results" or "#{#project.cache_key}/results" everything works just fine. I also checked if the #project was being updated but it's not.
Anyone know why it does not take an ActiveRecord model?
You want your cache key to be an array, probably Rails.cache.fetch([#project, 'results']).
This will give a cache key along the lines of "project/5-20190812000000/results". The format for the model is "model_name/model_id-updated_at" with the rest of the array values joined with /.
If you were to look at the key generated from your example, it would look something like "#<Project:0x007fbceaadbbc90>/results". This is happening because you are baking the value of #project.to_s into the value of the key you are passing into fetch.
I'm having problems with weird behaviour in RoR. I'm having a Hash that i'm converting to json using to_json() like so:
data = Hash.new
# ...
data = data.to_json()
This code appears inside a model class. Basically, I'm converting the hash to JSON when saving to database. The problem is, the string gets saved to database with its surrounding quotes. For example, saving an empty hash results in: "{}". This quoted string fails to parse when loading from the database.
How do I get rid of the quotes?
The code is:
def do_before_save
#_data = self.data
self.data = self.data.to_json()
end
EDIT:
Due to confusions, I'm showing my entire model class
require 'json'
class User::User < ActiveRecord::Base
after_find { |user|
user.data = JSON.parse(user.data)
}
after_initialize { |user|
self.data = Hash.new unless self.data
}
before_save :do_before_save
after_save :do_after_save
private
def do_before_save
#_data = self.data
self.data = self.data.to_json()
end
def do_after_save
self.data = #_data
end
end
The data field is TEXT in mysql.
I'm willing to bet money that this is the result of you calling .to_json on the same data twice (without parsing it in between). I've had a fair share of these problems before I devised a rule: "don't mutate data in a lossy way like this".
If your original data was {}, then first .to_json would produce "{}". But if you were to jsonify it again, you'd get "\"{}\"" because a string is a valid json data type.
I suggest that you put a breakpoint in your before_save filter and see who's calling it the second time and why.
Update
"call .to_json twice" is a general description and can also mean that there are two subsequent saves on the same object, and since self.data is reassigned, this leads to data corruption. (thanks, #mudasobwa)
It depends on your model's database field type.
If the field is string type (like VARCHAR or TEXT) it should be stored as string (no need to get rid of the quotes - they are fine). Make sure calling to_json once.
If the field is Postgres JSON type, then you can just assign a hash to the model's field, no need to call to_json at all.
If you are saving hash as a JSON string in a varchar column you can use serialize to handle marshalling/unmarshaling the data:
class Thing < ActiveRecord::Base
serialize :foo, JSON
end
Knowing exactly when to convert the data in the lifecycle of a record is actually quite a bit harder than your naive implementation. So don't reinvent the wheel.
However a huge drawback is that the data cannot be queried in the DB*. If you are using Postgres or MySQL you can instead use a JSON or JSONB (postgres only) column type which allows querying. This example is from the Rails guide docs:
# db/migrate/20131220144913_create_events.rb
create_table :events do |t|
t.json 'payload'
end
# app/models/event.rb
class Event < ApplicationRecord
end
# Usage
Event.create(payload: { kind: "user_renamed", change: ["jack", "john"]})
event = Event.first
event.payload # => {"kind"=>"user_renamed", "change"=>["jack", "john"]}
## Query based on JSON document
# The -> operator returns the original JSON type (which might be an object), whereas ->> returns text
Event.where("payload->>'kind' = ?", "user_renamed")
use {}.as_json instead of {}.to_json
ex:
a = {}
a.as_json # => {}
a.to_json # => "{}"
http://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html#method-i-as_json
To solve my problem, I set fictitious Car Model below:
Car Model has
3 attributes(id, car_name, owner_name) and
2 methods which return integers(riders, passengers).
I want to get ONE HASH which has the values of 2 attributes and 2 methods from all of cars. To solve this problem, my temporary solution is below:
json_format = Car.all.to_json(only: [:car_name, :owner_name], methods: [:riders, :passengers])
final_hash = ActiveSupport::JSON.decode(json_format)
This works, but this is bad because I use 'to_json' method only for its optional function.
Is their any other choice to getting the one hash directly from Car Model via its own optional function?
Use as_json. It's what to_json uses under the hood, and accepts all the same options but returns a Hash.
I feel the same as you that using the json commands for a method unrelated to json is poor practice.
If you don't mind being verbose, you could do something like this:
formatted_cars = Array.new
Car.all.each do |c|
formatted_cars.push( car_name: c.car_name,
owner_name: c.owner_name,
riders: c.riders,
passengers: c.passengers )
end
I find when hitting both attributes and methods it's cleanest just to specify the assignments like this. This is also the technique I would use in passing an object with its virtual attributes to javascript.
I agree that turning it into Json and back is pretty stupid. I would do this like so.
Car.all.collect{|car| hash = {}; [:car_name, :owner_name, :riders, :passengers].each{|key| hash[key] = car.send(key)}; hash}
it would be cleaner to put this into a method in the object
#in Car class
def stats_hash
hash = {}
[:car_name, :owner_name, :riders, :passengers].each do |key|
hash[key] = self.send(key)
end
hash
end
then do
Car.all.collect(&:stats_hash)
I am having problems accessing the attributes of my JSON data. Instead of accessing the JSON data it thinks it is a function.
#response = HTTParty.get('http://localhost:4000/test')
#json = JSON.parse(#response.body)
#json.each do |pet|
MyModel.create(pet) ! WORKS
puts "my object #{pet}" ! WORKS
puts "my object attribute #{pet.myattribute}" ! DOES NOT WORK
end
With no MethodError myattribute.
Thank you for any help!
You may be used to JavaScript, where both object.some_key and object["some_key"] do the same thing. In Ruby, a hash is just a hash, so you have to access values via object["some_key"]. A Struct in Ruby is similar to a JavaScript object, in that you can access values both ways, but the keys have to be pre-defined.
#json = JSON.parse(#response.body) returns a hash, so you would need to do
puts "my object attributes #{pet['id']}, #{pet['title']}"
you might want to convert to HashWithIndifferentAccess so you can use symbols instead of quoted strings, i.e.
#json = JSON.parse(#response.body).with_indifferent_access
# ...
puts "my object attributes #{pet[:id]}, #{pet[:title]}"
I have an array with model attributes (these model attributes are columns in DB table). I am trying to iterate through this array and automatically create a record which I would like to save to DB table, something like this:
columns.each_with_index do |c, i|
user.c = data[i]
puts user.c
end
user is model.
But if I try the snippet above, I get
undefined method `c=' for #<User:0x007f8164d1bb80>
I've tried also
columns.each_with_index do |c, i|
user."#{c}" = data[i]
puts user."#{c}"
end
But this doesn't work as well.
Data in columns array are taken from form that sends user, so I want to save only data that he send me, but I still cannot figure it out...
I would like to ask you for help... thank you in advance!
user.send("#{c}=".to_sym, data[i])
Also, you can access the attributes as a hash.
user.attributes[c] = data[i]
The best thing would probably be to build a hash and to use update_attributes:
mydata = {}
columns.each_with_index{|c, i| mydata[c] = data[i]}
user.update_attributes(mydata)
this way you retain the protections provided by attr_accessible.
If this is actually in a controller, you can just make use of some basic rails conventions and build the User record like this:
#user = User.new(params[:user])
if #user.save
# do something
else
# render the form again
end
Although you can set the values using send, I agree with #DaveS that you probably want to protect yourself via attr_accessibles. If your planning to use Rails 4, here's a good overview.