I have two models:
class Song < ActiveRecord::Base
attr_accessible :title, :singer, :year, :production
end
and:
class SongsCopy < ActiveRecord::Base
attr_accessible :title, :singer, :year
end
What is the most simple way to copy attributes from A(Song) to B(SongsCopy) while creating B, remembering SongsCopy has no attribute :production?
The optimal way would be to do it inside the database with a bit of SQL:
insert into songs_copies (title, singer, year)
select title, singer, year
from songs
where ...
But if you have a bunch of callbacks and such that you need to run then you could do something like this:
song = some_song_that_you_already_have
copy = SongsCopy.create(song.attributes.except('id', 'production'))
or:
copy = SongsCopy.create(song.attributes.slice('title', 'singer', 'year'))
It's not the prettiest possibility (and certainly not preferred), but the easiest would be:
class SongsCopy < ActiveRecord::Base
def initialize(args = nil)
if args.is_a? Song
super
self.title = song.title
self.singer = song.singer
self.year = song.year
else
super(args)
end
end
end
a = Song
b = SongsCopy.new(a)
I'm sure there's another way to do this, but the above should work.
Related
I want to be able to manage has_many caching myself.
The issue being I want to be able to archive (like soft delete, but with discardable gem) some records:
has_many :tags
def tag_list=(tag_names)
# so I do a diff
names_to_create = ...
tags_to_discard = ...
tags_to_keep = ...
to_delete.each(&:discard)
new_tags = names_to_create.each { |name| tag.create!(name: name) }
# if I just go with regular
self.tags = new_tags + tags_to_keep
# tags_to_discard will be deleted
The thing is that I don't want to just tags.reload. It's costly
I know exactly what tags should be here or not.
How I can just "force" the rails cache?
You should tread carefully with that, but I find that
has_many :tags
def tag_list=(tag_names)
# Do a diff
names_to_create = ...
tags_to_discard = ...
tags_to_keep = ...
to_delete.each(&:discard)
new_tags = names_to_create.each { |name| tag.create!(name: name) }
tags.proxy_association.target = tags_to_keep + new tags
end
To do exactly that
Though it's probably private API and not meant to be used like that.
I'm trying to display a list of gins that have a similar minimum number of botanicals on my show page. I feel I'm close, but the current output is not right. It's actually just printing the name of the gin a number of times.
Gin Load (1.6ms) SELECT "gins".* FROM "gins" INNER JOIN
"gins_botanicals" ON "gins_botanicals"."gin_id" = "gins"."id" INNER
JOIN "botanicals" ON "botanicals"."id" =
"gins_botanicals"."botanical_id" WHERE "botanicals"."id" IN (4, 10, 3)
AND ("gins"."id" != $1) GROUP BY gins.id HAVING (COUNT(distinct
botanicals.id) >= 3) [["id", 2]]
I have three models; two resources with a joins table:
gin.rb
class Gin < ApplicationRecord
belongs_to :distillery, inverse_of: :gins
accepts_nested_attributes_for :distillery, reject_if: lambda {|attributes| attributes['name'].blank?}
acts_as_punchable
has_many :gins_botanical
has_many :botanicals, through: :gins_botanical
botanical.rb
class Botanical < ApplicationRecord
has_many :gins_botanical
has_many :gins, through: :gins_botanical
gins_botanical.rb
class GinsBotanical < ApplicationRecord
belongs_to :gin
belongs_to :botanical
gins_controller
def show
#gin = Gin.friendly.find(params[:id])
#gin.punch(request)
#meta_title = meta_title #gin.name
#similiar_gins = Gin.joins(:botanicals).where("botanicals.id" => #gin.botanical_ids).where.not('gins.id' => #gin.id).having("COUNT(distinct botanicals.id) >= 3").group("gins.id")
end
so in #similar_gins i am trying to count how many matching botanicals does the current #gin have compared to all the other #gins and if >= 3 return the values.
And in my view:
show.html.erb
<% #similiar_gins.each do |gin| %>
<%= #gin.name %>
<% end %>
I'm suspecting my where is not correct...
Yes, I have the similar feature but I have implemented like below
#gin = Gin.find(params[:id])
if #gin.botanicals.count > 1
#botanicals = #gin.botanical_ids
#gin_ids = Botanical.select('distinct gin_id').where('gin_id IN (?)', #botanicals).limit(10)
#ids = #gin_ids.map(&:gin_id)
#similiar_gins = Gin.where('id IN (?)', #ids).where.not(id: #gin) #=> similar all without current gin
end
This code is converted from my code which is relation is category and jobs, if you need to see my code for showing the similar jobs then it is
def show
#job = Job.find(params[:id])
if #job.categories.count > 1
#category = #job.category_ids
#jobs = JobCategory.select('distinct job_id').where('category_id IN (?)', #category).limit(10)
ids = #jobs.map(&:job_id)
#releted_jobs = Job.where('id IN (?)', ids).where.not(id: #job)
end
end
Hope it helps
I am making a custom dashboard for a school application that requires me to calculate some KPIs, the way am doing it right now is calling several class methods from the Opportunity class in the dashboard/index action from the controller, and storing each method result in a variable that is going to be used in a tile. So each variable is a different tile of the dashboard.
The methods belong to the Opportunity class shown below:
class Opportunity < ApplicationRecord
belongs_to :organization
belongs_to :opportunity_status
has_many :tasks, dependent: :destroy
has_many :opportunity_status_logs, dependent: :destroy
before_create :create_status_log
after_update :create_status_log, if: :opportunity_status_id_changed?
validates :name, :description, :revenue, :opportunity_status_id, :closing_date, presence: true
validates :name, :description, format: { with: /\A[[:alpha:]a-zA-Z0-9ñÑ#()\-.,\s]+\z/ }
validates :revenue, numericality: true
validates :closing_date, inclusion: { in: (Time.zone.today..Time.zone.today+5.years) }
def create_status_log
OpportunityStatusLog.create(opportunity_id: self.id, opportunity_status_id: self.opportunity_status_id)
end
def status_updated_by(user)
#status_log = self.opportunity_status_logs.last
#status_log.user_id = user.id
#status_log.save!
end
def self.actives
self.where.not(opportunity_status_id: [11,12])
end
def self.won
self.where(opportunity_status_id: 11)
end
def self.lost
self.where(opportunity_status_id: 12)
end
def self.average_revenue
self.won.average(:revenue)
end
def self.minimum_revenue
self.won.minimum(:revenue)
end
def self.maximum_revenue
self.won.maximum(:revenue)
end
def self.filter_by_status(status_id)
self.where(opportunity_status: status_id)
end
def self.relative_percentage(item_amount, total)
item_amount * 100 / total
end
def self.conversion_rate
self.won.count / self.all.count.to_f * 100
end
def self.potential_revenue
self.actives.sum(:revenue)
end
end
and this is the way the controller is structured:
class DashboardController < ApplicationController
before_action :authenticate_user!
def index
#opportunities = Opportunity.includes(:opportunity_status).all
#actives = Opportunity.actives.count
#won = Opportunity.won.count
#lost = Opportunity.lost.count
#average_revenue = Opportunity.average_revenue
#minimum_revenue = Opportunity.minimum_revenue
#maximum_revenue = Opportunity.maximum_revenue
#in_appreciation = Opportunity.filter_by_status(6).count
#in_value_proposition = Opportunity.filter_by_status(7).count
#in_management_analysis = Opportunity.filter_by_status(8).count
#in_proposal = Opportunity.filter_by_status(9).count
#in_review = Opportunity.filter_by_status(10).count
#app_perc = Opportunity.relative_percentage(#in_appreciation, #opportunities.count)
#vp_perc = Opportunity.relative_percentage(#in_value_proposition, #opportunities.count)
#ma_perc = Opportunity.relative_percentage(#in_management_analysis, #opportunities.count)
#pp_perc = Opportunity.relative_percentage(#in_proposal, #opportunities.count)
#rw_perc = Opportunity.relative_percentage(#in_review, #opportunities.count)
#conversion_rate = '%.2f' % [Opportunity.conversion_rate]
#potential_revenue = Opportunity.potential_revenue
end
end
Even though it works as expected, it looks like the controller is a bit too fat and I feel that with the current approach if the app scales it will be very slow due to the amount of queries that are being done. So, is there a way to refactor this in order to optimize the data retrieval and the displaying of the KPIs?
Thanks in advance
You can try implementing Facade Pattern in Rails. It will make your controller skinny but on the query part you will still be needing to make those queries, there is no way to skip that.
You can try to optimize db by adding index and creating sql views in future when you get performance lag, at this time it will be like premature optimization
I have the following in my controller:
#custom_exercises = #user.exercises.all
#all_exercises = Exercise.not_the_placeholder_exercise.public.order("name").all
if #user.trainers.present?
trainer_exercises = []
#user.trainers.each do |trainer|
trainer_exercises << trainer.exercises.all
end
#my_trainer_custom_exercises = trainer_exercises
end
#exercises = #custom_exercises + #all_exercises
if #my_trainer_custom_exercises.present?
#exercises << #my_trainer_custom_exercises
#exercises.flatten!
end
This feels really messy. How could I refactor this?
First step: set up an AR relationship between users and exercises, probably along the lines of:
class User < ActiveRecord::Base
has_many :trainer_exercises,
:through => :trainers,
:foreign_key => :client_id,
:source => :exercises
end
Second step: move #all_exercises to a class method in Exercise.
class Exercise < ActiveRecord::Base
def self.all_exercises
not_the_placeholder_exercise.public.order("name").all
end
end
This way, the whole controller gets a whole lot simpler:
#custom_exercises = #user.exercises.all
#trainer_exercises = #user.trainer_exercises.all
#exercises = Exercise.all_exercises + #custom_exercises + #trainer_exercises
From a purely less lines of code perspective, you could start with this ( more or less / not tested but should work:
if #user.trainers.present?
#my_trainer_custom_exercises = #user.trainers.each.inject([]){ |trainer, trainer_exercises|
trainer_exercises << trainer.exercises.all
}
end
Having a little trouble figuring out a way to display messages for users message threads depending on which ones have been deleted and not. Deleted messages "sender_status or recipient_status" will change from 0 to 1 on deletion of a message. Currently to display a list of users message threads in their inbox I use:
e.g.
current_user = User.find(2)
current_user.message_threads
This grabs all their message_threads with 0 sender or recipient statuses.
class User < ActiveRecord::Base
has_many :messages
has_many :message_threads
Nice and easy but what I would like to do now is some how set up something that enables me to be able to grab messages with 0 sender or recipient statuses from a current thread. So if I was to type:
one_thread = current_user.message_threads.first
From this thread I'd want to be able to easily grab the only messages I needed. Would like to put something in the message_thread model so I could eventually type out:
current_user.message_threads.first.messages
#or
one_thread.messages
and have only the messages I needed loaded and the ones with "1" statuses ignored.
User Model:
class User < ActiveRecord::Base
has_many :messages
has_many :message_threads
def message_threads
MessageThread.where([ '(sender_id = ? AND sender_status = 0) OR (recipient_id = ? AND recipient_status = 0)', self.id, self.id ])
end
def messages
Message.where([ '(sender_id = ? AND sender_status = 0) OR (recipient_id = ? AND recipient_status = 0)', self.id, self.id ])
end
MessageThread model:
class MessageThread < ActiveRecord::Base
has_many :messages
Message model:
class Message < ActiveRecord::Base
acts_as_tree
has_one :message_thread
I tried experimenting with joins but this didn't work out well for me.
def messages
joins(:messages).merge( Message.where([ '(parent_id = ? AND sender_status = 0) OR (parent_id = ? AND recipient_status = 0)',self.message_id, self.message_id ]) )
end
Help would be much appreciate.
Kind regards
I'm guessing the problem is that one_thread is returning a MessageThread object, and then you're trying to get the user id by calling self.id, but it's being called on the wrong model.
I haven't tried this code but it might work:
class User < ActiveRecord::Base
def self.threads_in_inbox
message_threads.where('(sender_id = ? AND sender_status = 0) OR (recipient_id = ? AND recipient_status = 0)', self.id, self.id)
end
end
class MessageThread < ActiveRecord::Base
def self.inbox_messages
messages.where('(sender_id = ? AND sender_status = 0) OR (recipient_id = ? AND recipient_status = 0)', self.user.id, self.user.id)
# this hits the database 1 extra time however due to self.user.id
# you could change it to inbox_messages(user_id) and pass in the id manually
end
end
first_thread = current_user.threads_in_inbox.first
first_thread.inbox_messages
In your question it seems like you're overriding the association queries generated by rails (message_threads and messages) - not sure if it's on purpose but I changed it above.
Also, I think you can remove the duplication and do the following:
where('(sender_id = ?) AND (sender_status = 0 OR recipient_status = 0)', self.id)