How can I reduce repetition in this Ruby on Rails code? - ruby-on-rails

This is a snippet of code from an update method in my application. The method is POSTed an array of user id's in params[:assigned_ users_ list_ id]
The idea is to synchronise the DB associations entries with the ones that were just submitted, by removing the right ones (those that exist in the DB but not the list) and adding the right ones (vise-versa).
#list_assigned_users = User.find(:all, :conditions => { :id => params[:assigned_users_list_id]})
#assigned_users_to_remove = #task.assigned_users - #list_assigned_users
#assigned_users_to_add = #list_assigned_users - #task.assigned_users
#assigned_users_to_add.each do |user|
unless #task.assigned_users.include?(user)
#task.assigned_users << user
end
end
#assigned_users_to_remove.each do |user|
if #task.assigned_users.include?(user)
#task.assigned_users.delete user
end
end
It works - great!
My first questions is, are those 'if' and 'unless' statements totally redundant, or is it prudent to leave them in place?
My next question is, I want to repeat this exact code immediately after this, but with 'subscribed' in place of 'assigned'... To achieve this I just did a find & replace in my text editor, leaving me with almost this code in my app twice. That's hardly in keeping with the DRY principal!
Just to be clear, every instance of the letters 'assigned' becomes 'subscribed'. It is passed params[:subscribed_ users_ list_ id], and uses #task.subscribed_ users.delete user etc...
How can I repeat this code without repeating it?
Thanks as usual

You don't need if and unless statements.
As for the repetition you can make array of hashes representing what you need.
Like this:
[
{ :where_clause => params[:assigned_users_list_id], :user_list => #task.assigned_users} ,
{ :where_clause => params[:subscribed_users_list_id], :user_list => #task.subscribed_users}
] each do |list|
#list_users = User.find(:all, :conditions => { :id => list[:where_clause] })
#users_to_remove = list[:user_list] - #list_users
#users_to_add = #list_users - list[:user_list]
#users_to_add.each do |user|
list[:user_list] << user
end
#users_to_remove.each do |user|
list[:user_list].delete user
end
end
My variable names are not the happiest choice so you can change them to improve readability.

I seem to be missing something here, but aren't you just doing this?
#task.assigned_users = User.find(params[:assigned_users_list_id])

Related

Check to See if A Particular Active Record Object Is In A Particular 'Scope'

Currently on save I am trying to check to see if a recorded falls into a particular 'scope'. This 'scope' really is just some saved arguments for a .where call. Also with this 'scope' I am only ever checking values of the object, not ever how it relates to other objects in the database, so querying the database will always be over kill if that makes sense.
I have only been able to come up with the below solution
begin
result = self.class.where(scope).find(self.id)
rescue
result = false
end
The issue with this is that I have to query the database even though I already have the record, and I have to run this not only before save but after save to check the values it was and the values it will be after save, because there is no way to query the database for the updated version if it hasn't been saved.
There can be a number of these checks so I would like to avoid having to do it twice, and also having to query the database that many times, even if ultimately I am just looking something up by id.
The only other solution I have been able to think of would be to have a method that some how translates the where call into a proc that return a boolean when passed an object. The only issue with that is translating it would some how have to work with the active record adapter being used, which seems like a whole project to its own. So does anyone know of some way to do this, or of a gem that would help?
PS I getting the 'scope' from cache so I can't save it as a proc because you can't put procs into the cache with Rails.
first you can improve your first solution a bit
result = self.class.where(scope).exists?(self.id)
if you don't want to check the database, why don't you just check if your object's attributes has the values of the scope? if your scope is
class.where(:attr1 => value1, :attr2 => value2, :attr3 => value3)
then you can do
result = self.attr1 == value1 and self.attr2 == value2 and self.attr3 == value3
If your scopes are simple, you probably want to avoid code duplication. My solution allows you to call model.active? to know if an instance belongs to the scope, and Model.active to find all records matching the scope. model.active? doesn't involve any database queries.
consider adding this to config/initializers/scope_and_method.rb:
require 'active_record/named_scope'
module ActiveRecord::NamedScope::ClassMethods
def scope_and_method field, *values
field = field.to_sym
values.each do |value|
named_scope value.to_sym, :conditions => {field => value}
define_method "#{value}?" do
send(field.to_sym) == value
end
end
end
end
Usage:
scope_and_method :state, 'active', 'inactive'
Works as if it was:
named_scope :active, :conditions => {:state => 'active'}
named_scope :inactive, :conditions => {:state => 'inactive'}
def active?
state == 'active'
end
def inactive?
state == 'inactive'
end
This is a solution for Rails 2.3. This needs a very small tuning for Rails 3 and 4. (named_scope -> scope) I will check it soon.

Updating records in a database using the form_tag

In my view page, i am using form_tag to create a form which will pass a string of ids from a hidden field to the controller code.
In my controller code, i am looping through an array of ids to update each record containing that id in the Expression table. But the code below does not seem to work.
I would really appreciate it if somebody could give me some suggestion regarding what is wrong with the code below.
def update_expression
#emi_ids_array = params[:emi_ids].split(/,/)
#sub_id = params[:sub_id]
#emi_ids_array.each do |emi_id|
#existing_exp = Expression.find(:first, :conditions => [ "EXT_EMI_ID = ? and EXT_SUB_FK = ?", emi_id, #sub_id])
#expression = #existing_exp.update_attributes(
:EXT_SUB_FK => #sub_id,
:EXT_PRESENCE => "present",
:EXT_STRENGTH => "weak",
:EXT_EMI_ID => emi_id
)
end
end
Try converting the array of ID's (and the sub_id) to integers.
Is it the finding of the object that fails, or the update? Output the #expression.errors after the update call to see if there are any validations failing.
Is there a reason for all the instance variables? You don't need the #'s if the variable doesn't go beyond that method. Also the #expression item seems superfluous, you're just duplicating the #existing_exp object, you don't need to put the return into a new object, especially if it's replaced each time the loop runs anyway.
Found a temporary solution. 'update_attributes' does not seem to work, so i opted for 'update_all' attribute
Expression.update_all({:EXT_PRESENCE => "present", :EXT_STRENGTH => "weak"},['EXT_EMI_ID = ? and EXT_SUB_FK = ?', emi_id, #sub_id])
Hopefully, it might be useful to someone else

Using Rails Helper Methods within ActionRecord Queries?

I have a table of events (in a sqlite3 database for what it's worth) with a column called "when" that contains a timestamp detailing precisely when the event that particular row denotes is set to occur. Right now, I have
#events = Event.find(:all)
in my controller and I am using template helper methods to calculate where to place each event on my display page based on the day of the week it occurs on. For example:
<% if(event.when.wday == 6) %>
# DO SOMETHING
<% end %>
I want to abstract this logic to the controller however. My idea was to do the following:
#thursday_events = Event.find(:all, :conditions => ["when.wday=4"])
Obviously (I guess?) this didn't work. Throwing the error "SQLite3::SQLException: near "when": syntax error: SELECT * FROM "events" WHERE (when.wday=4)".
I'm assuming this is because I tried to use a helper method within a find condition but I don't know a better way to do this. Any advice? Thanks!
The conditions parameter needs to be a fragment of SQL.
:conditions => ["when.wday=4"]
is a fragment of Ruby code, so no go.
Try
# Model Event has a datetime field named 'when'
Event.find(:all, :conditions => ["strftime('%w', events.when) = 4"])
SQLLite ref: http://www.sqlite.org/lang_datefunc.html
Added:
While more closely reading your post, I think you're planning to send multiple instance variables (one per day of the week) from your controller to your view. That's a good idea--moving logic out of the view. But, don't do more dbms queries!
Each query has significant overhead. Eg:
#Do NOT do it this way (too many db queries)
#sunday_events = Event.find(:all,
:conditions => ["strftime('%w', events.when) = 0"])
#monday_events = Event.find(:all,
:conditions => ["strftime('%w', events.when) = 1"])
#thursday_events = Event.find(:all,
:conditions => ["strftime('%w', events.when) = 4"])
# ... etc
# Better: Just 1 database query--
events = Event.find(:all)
#sunday_events = events.select{|e| e.when.wday == 0}
#monday_events = events.select{|e| e.when.wday == 1}
#thursday_events = events.select{|e| e.when.wday == 4}
# ... etc
Final comment:
Current best practice thinking is to move code into the models from controllers wherever reasonable. This is called "Fat model, skinny controller" In the above example, you could have a class-level method in the model create the individual instance variables. Or perhaps better, one hash that contains 7 values, each being an array of the records. Eg
# in Event model
def Event.find_by_day
events = Event.find(:all)
result = {}
days = [:sun, :mon, :tue, :wed, :thu, :fri, :sat]
(0..6).each{|day_i| result[days[day_i]] =
events.select{|e| e.when.wday == day_i}
}
result
end
# in controller
#events = Event.find_by_day
# in view
# #events[:sun] is array of the Sunday events
# so do something with them...

How to apply named_scopes incrementally in Rails

named_scope :with_country, lambad { |country_id| ...}
named_scope :with_language, lambad { |language_id| ...}
named_scope :with_gender, lambad { |gender_id| ...}
if params[:country_id]
Event.with_country(params[:country_id])
elsif params[:langauge_id]
Event.with_state(params[:language_id])
else
......
#so many combinations
end
If I get both country and language then I need to apply both of them. In my real application I have 8 different named_scopes that could be applied depending on the case. How to apply named_scopes incrementally or hold on to named_scopes somewhere and then later apply in one shot.
I tried holding on to values like this
tmp = Event.with_country(1)
but that fires the sql instantly.
I guess I can write something like
if !params[:country_id].blank? && !params[:language_id].blank? && !params[:gender_id].blank?
Event.with_country(params[:country_id]).with_language(..).with_gender
elsif country && language
elsif country && gender
elsif country && gender
.. you see the problem
Actually, the SQL does not fire instantly. Though I haven't bothered to look up how Rails pulls off this magic (though now I'm curious), the query isn't fired until you actually inspect the result set's contents.
So if you run the following in the console:
wc = Event.with_country(Country.first.id);nil # line returns nil, so wc remains uninspected
wc.with_state(State.first.id)
you'll note that no Event query is fired for the first line, whereas one large Event query is fired for the second. As such, you can safely store Event.with_country(params[:country_id]) as a variable and add more scopes to it later, since the query will only be fired at the end.
To confirm that this is true, try the approach I'm describing, and check your server logs to confirm that only one query is being fired on the page itself for events.
Check Anonymous Scopes.
I had to do something similar, having many filters applied in a view. What I did was create named_scopes with conditions:
named_scope :with_filter, lambda{|filter| { :conditions => {:field => filter}} unless filter.blank?}
In the same class there is a method which receives the params from the action and returns the filtered records:
def self.filter(params)
ClassObject
.with_filter(params[:filter1])
.with_filter2(params[:filter2])
end
Like that you can add all the filters using named_scopes and they are used depending on the params that are sent.
I took the idea from here: http://www.idolhands.com/ruby-on-rails/guides-tips-and-tutorials/add-filters-to-views-using-named-scopes-in-rails
Event.with_country(params[:country_id]).with_state(params[:language_id])
will work and won't fire the SQL until the end (if you try it in the console, it'll happen right away because the console will call to_s on the results. IRL the SQL won't fire until the end).
I suspect you also need to be sure each named_scope tests the existence of what is passed in:
named_scope :with_country, lambda { |country_id| country_id.nil? ? {} : {:conditions=>...} }
This will be easy with Rails 3:
products = Product.where("price = 100").limit(5) # No query executed yet
products = products.order("created_at DESC") # Adding to the query, still no execution
products.each { |product| puts product.price } # That's when the SQL query is actually fired
class Product < ActiveRecord::Base
named_scope :pricey, where("price > 100")
named_scope :latest, order("created_at DESC").limit(10)
end
The short answer is to simply shift the scope as required, narrowing it down depending on what parameters are present:
scope = Example
# Only apply to parameters that are present and not empty
if (!params[:foo].blank?)
scope = scope.with_foo(params[:foo])
end
if (!params[:bar].blank?)
scope = scope.with_bar(params[:bar])
end
results = scope.all
A better approach would be to use something like Searchlogic (http://github.com/binarylogic/searchlogic) which encapsulates all of this for you.

Verifying if an object is in an array of objects in Rails

I'm doing this:
#snippets = Snippet.find :all, :conditions => { :user_id => session[:user_id] }
#snippets.each do |snippet|
snippet.tags.each do |tag|
#tags.push tag
end
end
But if a snippets has the same tag two time, it'll push the object twice.
I want to do something like if #tags.in_object(tag)[...]
Would it be possible? Thanks!
I think there are 2 ways to go about it to get a faster result.
1) Add a condition to your find statement ( in MySQL DISTINCT ). This will return only unique result. DBs in general do much better jobs than regular code at getting results.
2) Instead if testing each time with include, why don't you do uniq after you populate your array.
here is example code
ar = []
data = []
#get some radom sample data
100.times do
data << ((rand*10).to_i)
end
# populate your result array
# 3 ways to do it.
# 1) you can modify your original array with
data.uniq!
# 2) you can populate another array with your unique data
# this doesn't modify your original array
ar.flatten << data.uniq
# 3) you can run a loop if you want to do some sort of additional processing
data.each do |i|
i = i.to_s + "some text" # do whatever you need here
ar << i
end
Depending on the situation you may use either.
But running include on each item in the loop is not the fastest thing IMHO
Good luck
Another way would be to simply concat the #tags and snippet.tags arrays and then strip it of duplicates.
#snippets.each do |snippet|
#tags.concat(snippet.tags)
end
#tags.uniq!
I'm assuming #tags is an Array instance.
Array#include? tests if an object is already included in an array. This uses the == operator, which in ActiveRecord tests for the same instance or another instance of the same type having the same id.
Alternatively, you may be able to use a Set instead of an Array. This will guarantee that no duplicates get added, but is unordered.
You can probably add a group to the query:
Snippet.find :all, :conditions => { :user_id => session[:user_id] }, :group => "tag.name"
Group will depend on how your tag data works, of course.
Or use uniq:
#tags << snippet.tags.uniq

Resources