Rails complicated class method chaining - ruby-on-rails

How do you write a complicated class method that applies to whatever it's being chained to? Let me explain: a simple class method such as:
def self.despondent
where(despondent: true)
end
can be easily chained:
User.desperate.despondent.disillusioned
But what if you've got a complicated class method such as:
def self.friendly_dogs(user)
#dogs1 = Dog.none
#dogs2 = Dog.none
if user.has_dog
users = User.friends.where(has_dog: true)
#dogs1 = Dog.where(user_id: users.ids)
end
if user.wife.has_dog
users = user.wife.friends.where(has_dog: true)
#dogs2 = Dog.where(user_id: users.ids)
end
return #dogs1.or(#dogs2).distinct
end
How would you write this so you can place it anywhere in the chain? And in the following example would select only the friendly_dogs from all poodles that are 1 year old:
#dogs = Dog.where(breed: "poodle").where(age: "1").friendly_dogs.paginate(page: params[:page])

Related

ROR: Creating a method with a parameter

I have an admins dashboard which displays posts created in the last 24 hours, 7 days, 28 days etc.
def index
#1DayPosts = Post.where(created_at: 1.days.ago..DateTime.now).count
#7DaysPosts = Post.where(created_at: 7.days.ago..DateTime.now).count
#28DaysPosts = Post.where(created_at: 28.days.ago..DateTime.now).count
end
How could I make this into one line? Something like the below:
def index
#calculatePosts(a) = Post.where(created_at: a.days.ago..DateTime.now).count
end
Then in the view I could do:
=#calculatePosts(1)
Or would I need to create a new method?
def calculatePosts(a)
#calculatePost = Post.where(created_at: a.days.ago..DateTime.now).count
end
How would I then call this in the index view?
Your best bet would be to create a scope on the Post model.
class Post ...
scope :last_x_days, -> (x) { where(created_at: x.days.ago..Time.zone.now) }
end
Then you can call that anywhere really, in your view or controller like this.
#last_10_days = Post.last_x_days(10).count
EDIT:
You could do this also, but scopes are meant to be chain-able, so this is discouraged, though not wrong.
scope :last_x_days_count, -> (x) { where(created_at: x.days.ago..Time.zone.now).count }

better way to build association in controller

I need a link in a show method of a parent class for creating associated models, so I have the code:
link_to "incomplete", new_polymorphic_path(part_c.underscore, :survey_id => survey.id)
in a helper.
This links to a part, which has new code like this:
# GET /source_control_parts/new
def new
get_collections
if params[:survey_id]
#s = Survey.find(params[:survey_id])
if #s.blank?
#source_control_part = SourceControlPart.new
else
#source_control_part = #s.create_source_control_part
end
else
#source_control_part = SourceControlPart.new
end
end
I know this is not very DRY. How can I simplify this? Is there a RAILS way?
How about this:
def new
get_collections
get_source_control_part
end
private
def get_source_control_part
survey = params[:survey_id].blank? ? nil : Survey.find(params[:survey_id])
#source_control_part = survey ? survey.create_source_control_part : SourceControlPart.new
end

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. :)

Remove current model instance from AR:Relation

I am creating an instance method on a model which returns instances of the same model. How can I ensure that the instance of the model that the method is being called upon is not part of the output?
My code is like this at the moment:
def other_versions(include_current = true)
if include_current
Coaster.where(order_ridden: order_ridden)
else
#coaster.other_version_count // Need this to exclude the current instance.
end
end
I'm not sure I understood, but would this help?
def other_versions(include_current = true)
query = Coaster.where(order_ridden: order_ridden)
query = query.where("id != ?", id) unless include_current
query
end

Passing in an ActiveRecord scope as a filter_by string

I have a couple of scopes in my model which need to be filters in an admin panel.
periodical.rb
class Periodical < ActiveRecord::Base
scope :archived_newspaper, where("category = 'Newspaper'").where("archived = ?", true)
scope :current_magazine, where("category = 'Magazine'").where("archived = ?", false)
end
index.html.erb
Filter by: Archived Newspapers
Current Magazines
How would you implement the controller given that there would be other typical things like pagination, search, sort, etc.?
class PeriodicalController < ApplicationController
def index
#periodicals = Periodical.page(params[:page])
#periodicals = #periodicals.order(params[:sort_by]) if !params[:sort_by].blank?
# Scope here
end
end
I'm not sure if this is the best solution since there are certainly security concerns with it, but you could do something like this:
class PeriodicalController < ApplicationController
def index
#periodicals = Periodical.page(params[:page])
#periodicals = #periodicals.order(params[:sort_by]) unless params[:sort_by].blank?
if params[:filter_by] and Periodical.respond_to?(params[:filter_by].to_sym)
begin
new_scope = Periodical.send(params[:filter_by].to_sym)
new_scope = nil unless new_scope.is_a?(ActiveRecord::Relation)
rescue ArgumentError
new_scope = nil
end
#periodicals.merge(new_scope)
end
end
end
EDIT: some alternatives:
1) Filter the param to a list of permissible scopes (better protecting the Periodical model from attack by people editing the parameters in the request):
filter_by = params[:filter_by].to_sym
if [:archived_magazine, :current_magazine].include?(filter_by)
new_scope = Periodical.send(params[:filter_by].to_sym)
#periodicals.merge(new_scope)
end
2) use a case / switch and hard code the scopes (better security, but not very DRY)
new_scope = case params[:filter_by]
when "archived_magazine"
Periodical.archived_magazine
when "current_magazine"
Periodical.current_magazine
else
nil
end
#periodicals.merge(new_scope)

Resources