Rails method on ActiveRecord::Associations::CollectionProxy - ruby-on-rails

I have a dilema. There's a huge function in my controller to standardise loads of different types of data into one list for the view. So I have this kind of way of handling it at the moment:
customer.notes.each do |note|
to_push = {
id: note.id,
title: 'Contact Note',
type: 'note',
description: note.notes,
user: note.user,
date: note.date,
action: nil,
extras: note.customer_interests,
closed: false,
colour: '#9b59b6'
}
history.push to_push
end
I want to move that out of the controller into the model but I'm not too sure how. I ideally want a method like customer.notes.format_for_timeline but I can't figure out how to iterate over results like that in a self method within the class.
Thanks

I found out how. Using a self method then all:
def self.format
all.each do |item|
# Manipulate items here
end
end
However, I ended up having a method like this:
def format
{
id: id,
note: 'Contact Note',
# Etc
}
end
Then just used:
customer.notes.map {|i| i.format }

Related

What is the best way to loop through a collection of records and pass back an object to the front end?

I have a controller that returns user reports, and one of the methods sums up the points of said reports, per user. I want to pass back an object of this data to the front end so it can be displayed. Ideally my object would be shaped like this:
data: {
users: {
$user_id: {
name: "Foo Bar",
points: 100
},
$user_id: {
name: "Foo Bar Two",
points: 10
}
}
}
However my current implementation is not building the object like this, and simply adding to one big object.
My code looks like this:
def user_points
hash = {}
User.all.each do |u|
user_points = Report.select("points").where("user_id = ?", u.id).sum("points")
hash.merge!(
user:
{
first_name: u.first_name,
last_name:u.last_name,
time_zone: u.time_zone
}
)
end
render json: { data: hash }
end
and the resulting object only included the last user in one big object
data:
user:
first_name: "Test"
last_name: "Test"
points: 200
time_zone: "Pacific Time (US & Canada)"
You can also achieve the same result by joining both the table and then performing aggregation on joined table.
select users.id, users.name, sum(reports.points) as points from users join reports on users.id = reports.user_id group by users.id;
sql-fiddle
Thank you max for the comment.
def user_points
result = User.join(:reports)
.select(
:first_name,
:last_name,
Report.arel_table[:points].sum.as(:points),
:time_zone
)
.group(:id)
render json: { data: result }
end
Output:
data:
first_name: "Test1"
last_name: "Test1"
points: 100
first_name: "Test2"
last_name: "Test2"
points: 200
first_name: "Test3"
last_name: "Test3"
points: 300
As mentioned by dbugger you need to provide a unique key for each hash entry otherwise merge will just replace an existing value.
For example:
{a: :foo}.merge(b: :bar)
=> {:a=>:foo, :b=>:bar}
and
{a: :foo}.merge(b: :bar).merge(a: :foo_bar)
{:a=>:foo_bar, :b=>:bar}
You might want to consider returning a json array rather than an object with unique property names.
maybe something like this?
def user_points
result = User.all.map do |u|
points = Report.select("points").where("user_id = ?", u.id).sum("points")
{
first_name: u.first_name,
last_name:u.last_name,
time_zone: u.time_zone
points: points
}
end
render json: { data: result }
end

How to write if statement in Rails 4 as_json method?

I'm using Rails 4.I'm creating API databse where users can sign up from Facebook Graph API.
If user has no profile picture then the image_url is null.
After reading answers in SO I thought this is the correct way how to build custom json for my response.
I have created method as_json to render response when user is created with only parameters who should get returned.
This is the method how I'm creating json response:
def as_json(options={}){
id: self.id,
first_name: self.first_name,
last_name: self.last_name,
auth_token: self.auth_token,
image: {
thumb: "http://domain.com" + self.profile_image.thumb.url
}
}
end
This method above gives me an error: no implicit conversion of nil into String.
I need to give absolute image url path if the image exists in my db, but i don't need to give this parameter in response if image url is null in my database.
How can I write if statement inside this as_json method?
I've tried this, but it doesn't work.
def as_json(options={}){
id: self.id,
first_name: self.first_name,
last_name: self.last_name,
auth_token: self.auth_token,
if !self.profile_image.thumb.url == nil
image: {
thumb: "http://domain.com" + self.profile_image.thumb.url
}
end
}
end
With the help from Jorge de los Santos I've managed to make it pass no implicit conversion of nil into String error with this code:
def as_json(options={})
response = { id: self.id,
first_name: self.first_name,
last_name: self.last_name,
auth_token: self.auth_token }
if !self.profile_image.thumb.url == nil
image = "http://domain.com" + self.profile_image.thumb.url
response.merge(image: {thumb: image })
end
response
end
But now all the users are returned without image parameter even when he has a image url.
Your code seems fine except when you try to merge image key, merge function is not working as you expect, check the followingt to understand:
hash = {a: 1, b:2 }
hash.merge(b: 3)
puts hash #{a: 1, b:2 }
hash = hash.merge(b: 3)
puts hash #{a: 1, b:2, c: 3 }
so you will need to modify your code by changing this line:
response.merge(image: {thumb: image })
to
response = response.merge(image: {thumb: image })
Using Jbuilder
When you are building a complex json object, it's better to use jbuilder, I'll assume the model is called 'User'
Create a template called show.json.jbuilder
json.id #user.id
json.first_name #user.first_name
json.last_name #user.last_name
json.auth_token #user.auth_token
unless #user.profile_image.thumb.url.nil?
json.image do |image|
image.thumb "http://domain.com#{#user.profile_image.thumb.url}"
end
end
I would recommend creating a helper for the image url, so we could for example call something like
json.image #user.full_profile_image_url
Using as_json
As for your own method (using as_json) you could create a method that returns the full image hash
class User < ActiveRecord::Base
def image
{ thumb: "http://domain.com#{profile_image.thumb.url}" }
end
end
Then in the as json call the method
#user.to_json(
only: %i(id first_name last_name auth_key),
methods: :image
)
This will call the image method and set it's value inside a key called 'image'
You can't use logic inside a hash key, you can declare the variable before returning the hash, or you can use the full statement inside the value of the hash. But I think this is more readable.
def as_json(options={})
response = { id: self.id,
first_name: self.first_name,
last_name: self.last_name,
auth_token: self.auth_token }
image = "http://domain.com" + self.profile_image.thumb.url
response.merge!({image: {thumb: image }}) unless self.profile_image.thumb.url
response
end

Rails, Grape create custom JSON from collection

I started to learn how to use Grape. I have collection with a lot of attributes and want only some of them. I did something like this:
get :all_elements do
[
my_collection.each do |element|
{
id: element.id,
name: element.name
}
end
]
end
However this is not working. How can I create custom json array from collection?
Please try this code.
list = my_collection.map do |element|
{ :id => element.id,
:name => element.email
}
end
list.to_json

passing a block into create method in Ruby

I want to write a method which will work like this one
def create_new_car(salon)
Car.create name: salon.offer.name, description: salon.offer.description, photo: salon.offer.photo, dealer_id: salon.dealer_id
end
but i want to keep it DRY. is there a way to pass those attributes by, i dont know, iterating through array of attributes by passing a block?
You can pass a block to create:
def create_new_car(salon)
Car.create do |car|
car.name = salon.offer.name
car.description = salon.offer.description
car.photo = salon.offer.photo
car.dealer_id = salon.dealer_id
end
end
You could also set some attributes as the first parameter and then pass a block:
Car.create(name: salon.offer.name) do |car|
car.description = salon.offer.description
#...
end
You can implement any logic you want inside that block to assign the Car properties, like this:
attributes = ["name", "description", "photo", "dealer_id"]
Car.create do |car|
attributes.each { |a| car.send( "#{a}=", salon.offer.send(a) )
end
Please try this
array_of_attrbiutes = [{name: salon.offer.name...}, {name: }, {}...]
def create_new_car(array_of_attributes)
Car.create array_of_attributes
end
end
Please see https://github.com/rails/rails/blob/b15ce4a006756a0b6cacfb9593d88c9a7dfd8eb0/activerecord/lib/active_record/associations/collection_proxy.rb#L259

RABL: JSON objects without root key

I have this rabl template:
object #photo
attributes :id
child :comments do
attributes :id, :body
end
Which gives me this JSON response:
{
photo: {
id: 1,
comments: [
{
comment: {
id: 1,
body: 'some comment'
}
},
{
comment: {
id: 2,
body: 'another comment'
}
}
]
}
}
But I want it to look like this:
{
id: 1,
comments: [
{
id: 1,
body: 'some comment'
},
{
id: 2,
body: 'another comment'
}
]
}
Why does rabl wrap each element in the array with an extra object called comment. In this way when I access the collection in javascript I have to write:
var comment = image.comments[0].comment
instead of:
var comment = image.comments[0]
I know that if I include :comments in the attributes list for the #photo object it works the way I want, but when I want another level of nested associations for each comment object, there isn't a way to handle that besides using child, but that gives me the JSON response that I don't want.
Maybe I'm just misunderstanding the whole thing -- can someone explain or help? Thanks!
Got it!
Create a new file in config/initializers/rabl_config.rb:
Rabl.configure do |config|
config.include_json_root = false
config.include_child_root = false
end

Resources