Rails: How to manipulate find(params[]) - ruby-on-rails

I am trying to use Object.find(params[]) to only return objects with :stage_id = integer
Here is my controller code
def show
#lesson = Lesson.find(params[:id])
#stage1 = #lesson.units(params[:stage_id] == 1)
#stage2 = #lesson.units(params[:stage_id] == 2)
Each lesson has many units, each unit has either a stage_id = 1 or stage_id = 2, I want #stage1 to become an array with units that only have a stage_id value of 1. The same goes for stage2.
How can I properly use params to return only units that have the indicated table values?

def show
#lesson = Lesson.find(params[:id])
#stage1 = #lesson.units.first(:conditions => { :stage_id => 1 })
#stage2 = #lesson.units.first(:conditions => { :stage_id => 2 })
end

Ref find
#stage1 = Unit.find(:all, :conditions => ["stage_id=? AND lession_id=?" 1, params[:id]])
#stage2 = Unit.find(:all, :conditions => ["stage_id=? AND lession_id=?" 2, params[:id]])

If Units are "always' going to be structured with Stages, one thing you could do to DRY up your code is to have a Stage model. That allows flexibility to add more stages in the future without breaking code. Assuming that relationship is properly established and data is good, you could do something like the following.
controller code
#lesson = Lesson.find(params[:id])
view code (haml)
- for stage in #lesson.stages
= stage.name
- for unit in stage.units
= unit.name

Related

how append an object to association relation in rails?

In a rails 4.1 application I need to add an object to an "AssociationRelation"
def index
employee = Employee.where(id_person: params[:id_person]).take
receipts_t = employee.receipts.where(:consent => true) #gives 3 results
receipts_n = employee.receipts.where(:consent => nil).limit(1) #gives 1 result
#I would need to add the null consent query result to the true consent results
#something similar to this and the result is still an association relation
#receipts = receipts_t + receipts_n
end
Is there a simple way to do this?
A way of solving this:
def index
employee_receipts = Employee.find_by(id_person: params[:id_person]).receipts
receipts_t = employee_receipts.where(consent: true)
receipts_n = employee_receipts.where(consent: nil).limit(1)
#receipts = Receipt.where(id: receipts_t.ids + receipts_n.ids)
end
Unfortunately .or() can't be used here because it's only available from Rails v5.0.0.1
you could do this way
receipts_t_ids = employee.receipts.where(:consent => true).pluck(:id)
receipts_n_ids = employee.receipts.where(:consent => nil).limit(1).pluck(:id)
#receipts = Receipt.where(id: receipts_t_ids + receipts_n_ids)
To avoid extra queries and keeping arrays in memory, you can use or
Like this:
def index
employee_receipts = Employee.find_by(id_person: params[:id_person]).receipts
#receipts =
employee_receipts.where(consent: true).or(
employee_receipts.where(consent: nil).limit(1)
)
end

Is there a way I can use a rake task or method that can rewrite an HTML partial using erb?

Sorry for the confusing title, but I will elaborate here.
ok so on the users index page of my site I have a list of Top Trending songs. The list is ordered by user rankings and this list changes dynamically as each songs aggregate ranking changes relative to each other.
class SongratingsController < ApplicationController
#Top100 = Rails.cache.read('Top100')
lastSpot = #Top100.last
def reCalcTop100
#Top100 = Song.where('num_stars > ?', 0 ).order('num_stars desc, total_score desc').limit(100)
Rails.cache.fetch('Top100'){#Top100}
end
def addRatingToSong
userID = params[:uid].to_i
songId = params[:sid].to_i
rVal = params[:valR].to_i
#averageS = []
songRate = Songrating.find_by(:user_id => userID, :song_id => songId)
if songRate != nil
oldScore = songRate.rating
songRate.update_attributes(:rating => rVal)
#song = Song.find(songId)
score = #song.total_score - oldScore
newScore = score + rVal
averageScore = newScore/#song.songratings_count
#song.update_attributes(:total_score => newScore,:num_stars => averageScore)
#averageS[0] = averageScore
#averageS[1] = #song.songratings_count
else
Songrating.create!(:user_id => userID, :song_id => songId,:rating => rVal)
#song = Song.find(songId)
newScore = #song.total_score + rVal
averageScore = newScore/#song.songratings_count
#song.update_attributes(:total_score => newScore,:num_stars => averageScore)
#averageS[0] = averageScore
#averageS[1] = #song.songratings_count
end
if newScore > lastSpot.total_score && averageScore > lastSpot.num_stars
reCalcTop100
end
if request.xhr?
render :json => {
:starData => #averageS
}
end
end
end
As you can see in these two photos below I have a view partial that shows this list, but right now I have the list generated each time a user logs into the main page. But since this list is not unique to the user, I feel I am wasting time regenerating this list.
ideally I would like to generate and write a static HTML partial only when the top100 list changes, but I don't really know how to accomplish this.
thanks.
Yep just use erb
vars = OpenStruct.new({some_var: some_val})
rendered_html = ERB.new(File.read("#{Rails.root}/app/templates/embed_code.html.erb")).result(vars.instance_eval { binding })
This will put the rendered html in the rendered_html variable from there you can write it to a file or do anything you want. This should work in the context of a ruby class or rake task afaik.
The vars are passed to the template and can be used as <%= some_var %> in the template.
Now that i've answered you actual question, i think the better solution is to just use Rails.cache to cache the rendered data.
Anything that takes a long time can be cached with
result = Rails.cache.fetch "some_cache_key" do
# things you want to cache
end
this will cache the block and return it to result. if unstale cached data exisits in the future it will just return it from cache, if cache is empty or stale it will re-execute the block and return it into result.
Finally in the context fo a controller you can just use action caching which is a bit more hands off.
See: http://guides.rubyonrails.org/caching_with_rails.html for more details.

Create or Update Rails 4 - updates but also creates (Refactoring)

In my Rails API I have the following code in my Child model:
before_create :delete_error_from_values, :check_errors, :update_child_if_exists
def delete_error_from_values
#new_error = self.values["error"]
#values = self.values.tap { |hs| hs.delete("error") }
end
def update_child_if_exists
conditions = {type: self.type, parent_id: self.parent_id}
if existing_child = Child.find_by(conditions)
new_values = existing_child.values.reverse_merge!(#values)
hash = {:values => new_values}
existing_child.update_attributes(hash)
end
end
def check_errors
if self.type == "error"
conditions = {type: self.type, parent_id: self.parent_id}
if existing_child = Child.find_by(conditions)
bd_errors = existing_child.error_summary
bd_errors[#new_error] = bd_errors[#new_error].to_i + 1
hash = {:error_summary => bd_errors}
existing_child.update_attributes(hash)
else
self.error_summary = {#new_error => 1}
end
end
end
This works like expected, except for one small detail: The Child is updated if a record by type and parent_id already exists, but it is also created. How can I refactor this to stop creation?
I've tried to include return false, but if I do this, the update is not successful.
I wish to have something like find_or_create_by, but I'm not sure how to use it for this cases.
May be you can refactor your code in following approach:
def create
#parent = Parent.find(params[:parent_id])
existing_child = Child.where(type: child_params[:type], parent_id:
child_params[:parent_id]).first
if existing_child.present?
existing_child.update_attributes(attribute: value_1)
else
#child = #parent.child.build(child_params)
end
#other saving related code goes here.
end
This is just a basic piece of example.
Try creating separate instance methods to keep the Contrller DRY. :)

Put Action in Lib to reduce line controllers and organize code Rails

I have a mode to put my code of controller in lib/ to reduce the code of the controller, I don't know if this works with my instance variables. I need some pass the action of controller to a lib/, to make a sample call require or include in action, some to organize my code more.
The action is:
def calculate_ship
pacote = Correios::Frete::Pacote.new
#products = buy_cart.line_items
#products.each do |p|
p.product.length = 16 if !p.product.length
p.product.weight = 0.3 if !p.product.weight
p.product.width = 11 if !p.product.width
p.product.height = 6 if !p.product.height
#item = Correios::Frete::PacoteItem.new :peso => p.product.weight, :comprimento => p.product.length, :largura => p.product.width, :altura => p.product.height
while p.quantity > 0
pacote.add_item(#item)
p.quantity -= 1
end
end
frete = Correios::Frete::Calculador.new :cep_origem => "95520-000",
:cep_destino => params[:cep],
:encomenda => pacote
servicos = frete.calcular :sedex, :pac
#pac = servicos[:pac].valor
#sedex = servicos[:sedex].valor
flash[:error] = servicos[:sedex].msg_erro
end
How to move this into lib/? I'm not accustomed to programing seriously with lib/, etc.
Instead, how about moving your logic into model & private methods. something like this?
Controller
def calculate_ship
Model.products = buy_cart.line_items
Model.set_defaults
#products = Model.items
servicos = calc_costs
#pac = servicos[:pac].valor
#sedex = servicos[:sedex].valor
flash[:error] = servicos[:sedex].msg_erro
end
private
def calc_costs
frete = Correios::Frete::Calculador.new :cep_origem => "95520-000",
:cep_destino => params[:cep],
:encomenda => pacote
frete.calcular :sedex, :pac
end
Model
attr_accessor :pacote, :products
def initialize
pacote = Correios::Frete::Pacote.new
end
def self.set_defaults
products.each do |p|
p.product.length = 16 if p.product.length.empty?
p.product.weight = 0.3 if p.product.weight.empty?
p.product.width = 11 if p.product.width.empty?
p.product.height = 6 if p.product.height.empty?
end
end
def self.items
products.each do |p|
#item = Correios::Frete::PacoteItem.new :peso => p.product.weight, :comprimento => p.product.length, :largura => p.product.width, :altura => p.product.height
while p.quantity > 0
pacote.add_item(#item)
p.quantity -= 1
end
end
end
PS: I didn't understand the code logic on what its actually doing as the complete code was not given & also the language looks like English but its not English. so, This is pseudo-code might have some errors but I hope this would give you atleast an idea on how you can move code from controller into the model/private methods. controllers actions should only be used to instantiate instance variables & call methods on objects. Remember, fat models & skinny controllers!.
Hope it helps
[edit]
F.A.Q
Q1. the Model in you code in controller can be someone Model? i talk this because this action is in my Cart controller, seems that the model should not be product?
Ans: I am not sure I fully understand what you mean by the Model in you code in controller can be someone Model? Are you asking that where this model code should do? Its actually simple, if this mode code is related to Cart, put it in Cart's model & if its related Product, put it in Product's Model.
Q2. this attr_accessor :pacote, :productsis for the pacote valid in all my app ? and the products to be able to pick up the product model Product?
Ans: attr_accessor :pacote, :productsis declares getter/setters. Please see What is attr_accessor in Ruby?

Refactoring a mongoid call

Right now, i am using 2 different but very similar queries (difference is just an additional criteria
pop_answers = Answer.any_of(
{:num_likes.gte=>3, :image_filename.exists=>true},
).desc(:created_at).skip(to_skip).limit(per_page).map{|a|a}
pop_answers_in_topic = Answer.any_of(
{:num_likes.gte=>3, :image_filename.exists=>true, :topic_id=>some_id},
).desc(:created_at).skip(to_skip).limit(per_page).map{|a|a}
How can i refactor this?
You could add a class method to Answer:
def self.popular(offset, limit, for_topic_id = nil)
conditions = { :num_likes.gte => 3, :image_filename.exists => true }
conditions[:topic_id] = for_topic_id if(for_topic_id)
any_of(conditions).desc(:created_at).skip(offset).limit(limit).map{|a|a}
end
Or if you're expecting more than just a topic ID:
def self.popular(offset, limit, options = { })
conditions = { :num_likes.gte => 3, :image_filename.exists => true }.merge(options)
any_of(conditions).desc(:created_at).skip(offset).limit(limit).map{|a|a}
end
I don't use Mongoid but you might be able to drop the funny .map{|a|a} or use .to_a instead.
Or perhaps something scope-ish:
# In answer.rb
def self.popular(for_topic_id = nil)
conditions = { :num_likes.gte => 3, :image_filename.exists => true }
conditions[:topic_id] = for_topic_id if(for_topic_id)
any_of(conditions)
end
# And then where you're using it...
pop_answers = Answer.popular.desc(:created_at).skip(to_skip).limit(per_page).map{|a|a}
pop_in_topic = Answer.popular(some_id).desc(:created).skip(to_skip).limit(per_page).map{|a|a}
And I have to wonder if any_of is really what you're looking for here, perhaps all_of would make more sense.

Resources