How to use Fabricate-gem to generate objects? - ruby-on-rails

I'm using Rails 4, Fabricate and Faker Gems. And I'm trying to seed my database with (100 or so) randomly created objects (Order that contains up to 3 Ice Creams). I followed This Answer that recommend using this approach.
models/order.rb
class Order < ActiveRecord::Base
...
has_many :ice_creams
...
end
models/ice_cream.rb
class IceCream < ActiveRecord::Base
...
has_and_belongs_to_many :flavors
has_many :added_extras
has_many :extras, :through => :added_extras
belongs_to :order
...
end
models/extra.rb
class Extra < ActiveRecord::Base
...
has_many :added_extras
has_many :extras, :through => :added_extras
...
end
test/fabricators/order_fabricator.rb
Fabricator(:order) do
user { User.offset(rand(User.count)).first } #fine
shift { Shift.offset(rand(Shift.count)).first } #fine
created_at { Faker::Date.backward(365) } #fine
ice_creams(rand: 3) { |attrs| Fabricate( :ice_cream, created_at: attrs[:created_at] ) } #fine
total { Faker::Number.between(5, 25) }
#add more logic depending of the total number of randomly created ice creams
discount { [0, 10, 15, 25].sample } #fine
total_after_discount { |order| order[:total] - ( (order[:total] * order[:discount]) / 100 ) }
paid { [50, 100, 200].sample } #fine
remaining { |order| order[:paid] - order[:total_after_discount] } #fine
end
test/fabricators/ice_cream_fabricator.rb
Fabricator(:ice_cream) do
size { Size.offset(rand(Size.count)).first } #fine
basis { Basis.offset(rand(Basis.count)).first } #fine
sauce { Sauce.offset(rand(Sauce.count)).first } #fine
topping { Topping.offset(rand(Topping.count)).first } #fine
flavors { [ Flavor.offset(rand(Flavor.count)).first ] }
#add additional ability to be one or two flavors randomly
extras { [ Extra.offset(rand(Extra.count)).first ] }
ice_cream_price { [15, 17, 18, 19, 20, 22].sample } #add logic
extras_price { [5, 10, 15, 20 ].sample } #add logic
total_price { |attrs| attrs[:ice_cream_price] + attrs[:extras_price] } #fine
created_at { Faker::Date.backward(365) }
end
It's working fine , I can now create fake Orders that contains upto 3 fake Ice Creams, But the thing is I'm struglling to figure out the logic to Fabricate more realistic Orders, As you may noticed in my fabricators code there are some attributes that I labeled fine -Which I'm fine with it's result- and some that I still not completely satisfied of, Like...
I wish that the Fabricated Ice Cream can -randomly- have one or two flavors.
I wish to do the same thing with Extras
I want to have the sum of the randomly Fabricated Ice Creams :total_price to be passed to the Order as :total
I've Tried to do so by creating a Flavor Fabricator but It didn't work..
test/fabricators/flavor_fabricator.rb
Fabricator(:flavor) do
Flavor.offset(rand(Flavor.count)).first
end
I also tried to sum the :total_price the activeRecord way, but it also didn't work
test/fabricators/order_fabricator.rb
Fabricator(:order) do
...
total { self.ice_creams.sum(:total_price) }
...
end
So my question is...
- Are the things that I wish for possible or it's just too much? And if so how to achieve that?
I hope I made myself clear, And you can help me,. Thanks

It looks like you're trying to use fabrication to set calculated values on your models, IceCream#total_price for example. You should be letting methods on your model do their thing, like calculate that total from the parts, instead of trying to force them with fabrication.
To answer your questions specifically:
1) I wish that the Fabricated Ice Cream can -randomly- have one or two flavors.
Fabricator(:ice_cream) do
flavors { Flavor.all.sample(rand(1..2)) }
end
2) Same as #1
3) You should have a method on Order that calculates the total when it is created.

Related

ROR: Serializing output for multiple objects

I am learning rails, and using active_model_serializers gem.
I need to get the customized output in JSON from two different tables. Here is an example
car: id, name, color, size, shape, date...
battery: id, car_id, price, weight, name, date...
class battery < ApplicationRecord
belongs_to :car
end
query I ran is
## This is sample query will not work
Car.includes(:battery).where(car: {color: red}).where(date >= 'blah')
I have three hashes in calculated in my controller
#cars_controller.rb
available_cars ={
available_cars:[{id:id,
name:name,
color:color,
size:size,
date:date..
}]
}
available_battries = {
available_batteries:[{id:id,
card_id:car_id,
price:price,
weight:weight,
name:name,
date:date..
}]
}
cars_with_battires = {
cars_with_battry: [{id:id,
name:name,
color:color,
size:size,
date:date..},
battries:[{id: 1,
name: name1
},
{id: 2,
name: name2
}
]
]}
render json: {
available_cars: available_cars,
available_batteries: available_batteries,
cars_with_battry: cars_with_battry }
serializer: CarIndexListSerializer }
This is my serializer file looks like and I need to fill those functions
#car_index_list_serializer.rb
class CarIndexListSerializer < ActiveModel::Serializer
attributes :available_cars,
:available_batteries,
:cars_with_battry
def available_cars
#todo?
end
def available_batteries
#todo?
end
def cars_with_battry
#todo?
end
I can just remove the fields I dont want and return the result in JSON but I want to use active_model_serializers to return it in following format.
{
available_cars:[{id: 1, name:name1, size: abc}],
available_batteries:[{id: 1, name: name1}],
cars_with_battry:[{id: 1, name:name1, size: abc,
battries:[{id: 1, name: name1}]
}]
}
if any of the hash is empty, I do not want to show it in the results
result = {}
available_cars = Car.where xxx
if available_cars.count.positive?
result[:available_cars] = available_cars.map {|car| CarSerializer.new(car).as_json }
end
# do the same thing for available battery and car with battery
render json: result
just a rough idea
Update
Usually for #index action on controller, it render list of resources
so CarIndexListSerializer is not required
What you have to create is two serializers:
car serializer
battery serializer
even you can add a relationship data to your car serializer
class CarSerializer < ActiveModel::Serializer
has_many :batteries
end
class Car < ApplicationRecord
has_many :batteries
end
so when you call this
car = Car.last
CarSerializer.new(car).as_json
it would return the car and batteries data
don't forget to create BatterySerializer too

Ruby Array#sort_by on array of ActiveRecord objects seems slow

I'm writing a controller index method that returns a sorted array of ActiveRecord Contact objects. I need to be able to sort the objects by attributes or by the output of an instance method. For example, I need to be able to sort by contact.email as well as contact.photos_uploaded, which is an instance method that returns the number of photos a contact has.
I can't use ActiveRecord's native order or reorder method because that only works with attributes that are columns in the database. I know from reading that normally array#sort_by is much faster than array#sort for complex objects.
My question is, how can I improve the performance of this block of code in my controller method? The code currently
contacts = company.contacts.order(last_name: :asc)
if params[:order].present? && params[:order_by].present? && (Contact::READ_ONLY_METHOD.include?(params[:order_by].to_sym) || Contact::ATTRIBUTES.include?(params[:order_by].to_sym))
contacts = contacts.sort_by do |contact|
if params[:order_by] == 'engagement'
contact.engagement.to_i
else
contact.method(params[:order_by].to_sym).call
end
end
contacts.reverse! if params[:order] == 'desc'
end
The root problem here (I think) is that I'm calling sort_by on contacts, which is an ActiveRecord::Relation that could have several hundred contacts in it. Ultimately I paginate the results before returning them to the client, however they need to be sorted before they can be paginated. When I run the block of code above with 200 contacts, it takes an average of 900ms to execute, which could be a problem in a production environment if a user has thousands of contacts.
Here's my Contact model showing some relevant methods. The reason I have a special if clause for engagement is because that method returns a string that needs to be turned into an integer for sorting. I'll probably refactor that before I commit any of this to return an integer. Generally all the methods I might sort on return an integer representing the number of associated objects (e.g. number of photos, stories, etc that a contact has). There are many others, so for brevity I'm just showing a few.
class Contact < ActiveRecord::Base
has_many :invites
has_many :responses, through: :invites
has_many :photos
has_many :requests
belongs_to :company
ATTRIBUTES = self.attribute_names.map(&:to_sym)
READ_ONLY_METHOD = [:engagement, :stories_requested, :stories_submitted, :stories_published]
def engagement
invites = self.invites.present? ? self.invites.count : 1
responses = self.responses.present? ? self.responses.count : 0
engagement = ((responses.to_f / invites).round(2) * 100).to_i.to_s + '%'
end
def stories_requested
self.invites.count
end
def stories_submitted
self.responses.count
end
def stories_published
self.responses.where(published: true).count
end
end
When I run a query to get a bunch of contacts and then serialize it to get the values for all these methods, it only takes ~80ms for 200 contacts. The vast majority of the slowdown seems to be happening in the sort_by block.
The output of the controller method should look like this after I iterate over contacts to build a custom data structure, using this line of code:
#contacts = Hash[contacts.map { |contact| [contact.id, ContactSerializer.new(contact)] }]
I've already benchmarked that last line of code so I know that it's not a major source of slowdown. More on that here.
{
contacts: {
79: {
id: 79,
first_name: "Foo",
last_name: "Bar",
email: "t#t.co",
engagement: "0%",
company_id: 94,
created_at: " 9:41AM Jan 30, 2016",
updated_at: "10:57AM Feb 23, 2016",
published_response_count: 0,
groups: {
test: true,
test23: false,
Test222: false,
Last: false
},
stories_requested: 1,
stories_submitted: 0,
stories_published: 0,
amplify_requested: 1,
amplify_completed: 1,
photos_uploaded: 0,
invites: [
{
id: 112,
email: "t#t.co",
status: "Requested",
created_at: "Jan 30, 2016, 8:48 PM",
date_submitted: null,
response: null
}
],
responses: [ ],
promotions: [
{
id: 26,
company_id: 94,
key: "e5cb3bc80b58c29df8a61231d0",
updated_at: "Feb 11, 2016, 2:45 PM",
read: null,
social_media_posts: [ ]
}
]
}
}
}
if params[:order_by] == 'stories_submitted'
contact_ids = company.contact_ids
# count all invites that have the relevant contact ids
invites=Invite.where(contact_id:contact_ids).group('contact_id').count
invites_contact_ids = invites.map(&:first)
# Add contacts with 0 invites
contact_ids.each{|c| invites.push([c, 0]) unless invites_contact_ids.include?(c)}
# Sort all invites by id (add .reverse to the end of this for sort DESC)
contact_id_counts=invites.sort_by{|r| r.last}.map(&:first)
# The [0, 10] limits you to the lowest 10 results
contacts=Contact.where(id: contact_id_counts[0, 10])
contacts.sort_by!{|c| contact_id_counts.index(c.id)}
end

Rails ActiveRecord intersect query with has_many association

I have the following models:
class Piece < ActiveRecord::Base
has_many :instrument_pieces
has_many :instruments, through: :instrument_pieces
end
class Instrument < ActiveRecord::Base
has_many :pieces, through: :instrument_pieces
has_many :instrument_pieces
end
class InstrumentPiece < ActiveRecord::Base
belongs_to :instrument
belongs_to :piece
end
And I have the following query:
Piece
.joins(:instrument_pieces)
.where(instrument_pieces: { instrument_id: search_params[:instruments] } )
.find_each(batch_size: 20) do |p|
Where search_params[:instruments] is an array. The problem with this query is that it will retrieve all pieces that have any of the instruments, so if search_params[:instruments] = ["1","3"], the query will return pieces with an instrument association of either 1 or 3 or of both. I'd like the query to only return pieces whose instrument associations include both instruments 1 and 3. I've read through the docs, but I'm still not sure how this can be done...
It seems like what I wanted was an intersection between the two queries, so what i ended up doing was:
queries = []
query = Piece.joins(:instruments)
search_params[:instruments].each do |instrument|
queries << query.where(instruments: {id: instrument})
end
sql_str = ""
queries.each_with_index do |query, i|
sql_str += "#{query.to_sql}"
sql_str += " INTERSECT " if i != queries.length - 1
end
Piece.find_by_sql(sql_str).each do |p|
Very ugly, but ActiveRecord doesn't support INTERSECT yet. Time to wait for ActiveRecord 5, I suppose.
You can use where clause chaining to achieve this. Try:
query = Piece.joins(:instrument_pieces)
search_params[:instruments].each do |instrument|
query = query.where(instrument_pieces: { instrument_id: instrument } )
end
query.find_each(batch_size: 20) do |p|
or another version
query = Piece.joins(:instruments)
search_params[:instruments].each do |instrument|
query = query.where(instrument_id: instrument)
end
query.find_each(batch_size: 20) do |p|

Any possible way to add parameters to ON clause on include left joins on rails?

I have a huge complex query like this:
#objects = Object.joins({ x: :y }).includes(
[:s, { x: { y: :z } }, { l: :m },:q, :w,
{ important_thing:
[:h, :v, :c,:l, :b, { :k [:u, :a] }]
}
]).where(conditions).order("x.foo, x.bar")
Then i want to show all Objects and only Important_things that were created at between two dates.
If i put this on there where clause i dont get all Objects, only Objects that has Important_things between informed dates.
A solution using raw sql was this:
select * from objects left join important_things on important_things.object_id = objets.id and important_things.created_at between 'x' and 'y'
Instead of:
select * from objects left join important_things on important_things.object_id = objets.id where important_things.created_at between 'x' and 'y'
I really need all those objects and i don't want to use a raw SQL, any workaround or a possibility to pass parameters to the ON clause on an association?
I do this,
class VendorsRatings < ActiveRecord::Base
def self.ratings(v_ids,sort = "DESC")
joins("RIGHT OUTER JOIN vendors_lists v
ON v.vendor_id = vendors_ratings.vendor_id").where(conditions)
end
end
I did a ugly workaround:
class Parent < ActiveRecord::Base
cattr_accessor :dt_begin, dt_end
has_many :children, conditions: Proc.new { { created_at: (##dt_begin..##dt_end) } }
end
class MetasController < ApplicationController
def index
Parent.dt_begin = Date.parse(param[:dt_begin])
Parent.dt_end = Date.parse(param[:dt_end])
#parents = Parent.includes(:children).where("children.age = ?", params[:age])
end
end
So this way i get all Parents even if i dont have Children created_at between those specified dates.
And the most important part of it i have all objects inside the ActiveRecord.
But be careful because i did messed with cattr_accessor so i have to set everytime before i search for Parents.

Active Record: Get two random objects?

What's the simplest way to get an array with three objects (Card), one of which I already have? The other two should be randomly selected from the database.
My current approach looks like this:
[
#deck.cards[rand(#deck.cards.size)],
#deck.cards[rand(#deck.cards.size)],
#mycard
].sort_by {rand}
The problem I have right now is that sometimes #mycard shows up twice in the array. How can this be avoided?
something like this might work:
class Card < ActiveRecord::Base
belongs_to :deck
named_scope :random, lambda {
{ :offset => Kernel.rand(Card.count) }
}
named_scope :not_in, lambda { |a|
{ :conditions => [ 'id NOT IN (?)', a ] }
}
end
my_cards = []
#mycard = Card.last
my_cards << #mycard
2.times {
my_cards << #deck.cards.not_in(my_cards.collect(&:id)).random
}
Get a card from the deck. Check it's not the samy as #mycard.
Get another card from the deck. Check it's not the same as #mycard or the previous card.
Pretty straightforward, I'd have thought.
You have to remove each card from the deck as you draw from it, unless you are going to reshuffle after each draw, in which case I would just draw again until you get a unique one you haven't already drawn.

Resources