I sort my "Posts" differently depending on if a user has clicked "trending", "new", or "top".
case params[:sort_type]
when "trending"
#posts = Post.order("trending_score DESC").limit(30)
when "new"
#posts = Post.order("created_at DESC").limit(30)
when "top"
#posts = Post.order("total_votes DESC").limit(30)
end
There is a lot of repeated code, but I'm not sure how to factor out the contents of the order.
One way would be simply to do:
CHOSEN_ATTRIBUTES = {'trending' => 'trending_score', 'new' => 'created_at', 'top' => 'total_votes'} #this could be a constant up in the class
chosen_attr = CHOSEN_ATTRIBUTES[params[:sort_type]]
#posts = Post.order("#{chosen_attr} DESC").limit(30)
Another way yet would be to create a scope with parameter for that. In your model:
CHOSEN_ATTRIBUTES = {'trending' => 'trending_score', 'new' => 'created_at', 'top' => 'total_votes'}
scope :order_query_by, lambda {|attr| order("#{CHOSEN_ATTRIBUTES[attr]} DESC").limit(30)}
Then whenever you want to call depending on the attribute, use:
Post.order_query_by 'trending'
Related
I have the following associations
Group:
has_many :group_links, :dependent => :destroy
GroupLink:
belongs_to :group
I want to display all the group links which belong to a particular group inside a layout which includes other objects which are not visible from inside GroupLInks views. SO I want to render a template from the GroupController as follows:
def group_links
#group_links = #group.group_links.all
render :template => 'group_links/group_links', :layout =>
'/layouts/sponsored_group_manage_sub_menu'
end
But I get the following error:
The action 'index' could not be found for GroupLinksController
If I create a 'index' view for GroupLinks and try to display it inside a layout with other objects, it throws the error
"You have a nil object"
I have the following method to initialize inside my groupscontroller:
def init_group
#group = Group.find_by_id(params[:id])
#group_blog_tags=#group.blog.blog_posts.tag_counts
#booth_links = #group.group_links.all
max_id = Group.count_by_sql("select min(profile_id) from (select profile_id from
group_memberships where group_id = #{#group.id} order by profile_id desc
limit 200) as x")
#booth_members = #group.members.all(:conditions => "profiles.id >= #
{rand(max_id)+1}", :limit => 20).to_a.sort! { |a,b| rand(3)-1 }
redirect_to groups_explorations_path unless #group
end
These other objects are used in the groups layouts to display the other objects. The thing is I was able to display another object from the GroupsController, without needing any index action inside that objects Controller. I have the exact same setup for GroupLinks as well but it does not work in this case...Please can you help me resolve this?
I think the problem is that #group is nil in your controller method group_links.
You can try to make like this:
def group_links
#group = Group.find(params[:id])
#group_links = #group.group_links.all
render :template => 'group_links/group_links', :layout =>
'/layouts/sponsored_group_manage_sub_menu'
end
But I'm not sure that this will work correctly:
render :template => 'group_links/group_links', :layout =>
'/layouts/sponsored_group_manage_sub_menu'
You can try just put your template in correct view folder with name "group_links" and match in your routes.rb file.
It turns out I had not put the proper path for this method in my layout. The routes were fine but the call to this method was not - I had to break my head to find it :(...Thanks for your time #Mosin and #RubyMan, appreciate it!
posts_controller.rb destroy method
def destroy
if !request.xhr?
render_404
return
end
if user_signed_in?
if Post.exists?(:post_id => params[:id])
if Post.post_is_mine(params[:id], current_user.id)
#return = { :error => false, :response => "Post deleted" }
else
#return = { :error => true, :response => 'You are not allowed to perform this action.' }
end
else
#return = { :error => true, :response => 'This post doesn\'t exist.' }
end
else
#return = { :error => true, :response => 'Please login before you delete a post.' }
end
render :json => ActiveSupport::JSON.encode( #return )
end
post.rb
def self.post_is_mine(post_id, user_id)
#where(:user_id => user_id, :post_id => bucket_id)
where("user_id = ? and post_id = ?", user_id, bucket_id)
end
when i check what queries are being run when i destroy a post, i can only see the .exists? to run but not the .post_is_mine which simply goes through as it returns TRUE
i tried several other names as the method since something could cause the problem or even simply trying out the if statement with .post_is_mine but still, the query was not run
Could there be a problem on the model as to how i use the where clause?
Yes. #where returns an ActiveRecord Relation, which is used to generate your query. The relation will not be evaluated in your code, so the query from .post_is_mine will never be executed. if Post.postis mine(params[:id], current_user.id) returns true because the Relation object is not nil.
What you really want is to use exists? in the post_is_mine method.
def self.post_is_mine(post_id, user_id)
exists?(:user_id => user_id, :post_id => bucket_id)
end
EDIT:
I was curious about the difference between my answer and Pavling's answer. For anyone else wondering:
#exists? executes a SQL statement with SELECT 1 FROM ...
#any? executes a SQL statement with SELECT COUNT(*) FROM ...
In practice there is likely not much difference between the two, but some crude benchmarks indicated that #any? is faster (with AR 3.2.6 and Postgresql 9.1 on OSX)
The "where" will return an empty collection, which evaluates as truthy. You would need to add a check to see if there are any records in it to get the right true/false.
def self.post_is_mine(post_id, user_id)
where("user_id = ? and post_id = ?", user_id, bucket_id).any?
end
I'm looking for a way to shorten up the :include => :child inside a respond_with which generates json.
Here is an example, not sure if it is even possible, but I would like to find out.
In the controller:
#p = Parent.where('id = ?', params[:id])
respond_with(#p, :include => {:child1 => {}, :child2 => {}, :child3 => {:include => :grandchild1}})
Is there someway to include these all when I define the instance?
Maybe something like:
#p = Parent.includes(:child1, :child2, :child3, :grandchild1).where('id = ?', params[:id])
respond_with(#p)
Basically, I'm trying to DRY up my code ... I don't want to have to keep typing the include hash over and over ... Is there someway to just include all child objects in one call?
ActiveRecord has an as_json method that defines how the object should be outputted as json. You can ovveride this method to include the associated children by default so something like this:
class Parent < ActiveRecord::Base
# We went to display grandchildren by default in the output JSON
def as_json(options={})
super(options.merge(:include => {:child1 => {}, :child2 => {}, :child3 => {:include => :grandchild1}})
end
end
That should let you clean up your controller a bit, you only need this:
#parent = Parent.find(params[:id])
respond_with #parent
I'm trying to make attributes equal predetermined values, and I'm not sure if I'm doing that efficiently with the following (in my orders controller):
def create
#order = Order.find(params[:id])
#order.price = 5.99
#order.representative = Product.find(params[:product_id]).representative
#order.shipping_location = SHIPPING_LOCATION
#order.user = current_user
respond_to do |format|
...
end
end
Is there a more efficient way to equate attributes in Rails (maybe using models)? If I'm using two different controllers, do I just repeat what I did above for the new controller?
Use before_create callback in model to assign default values.
Your code is a little off, it looks like a controller action for create, but the code reads like it's for an update.
Regardless...
You could use a parameter hash to update everything at once.
In the case where you're creating:
order_update = {:price => 5.99, :representative =>
Product.find(params[:product_id]).representative,
:shipping_location => SHIPPING_LOCATION,
:user => current_user}
#order = Order.new(order_update)
In the case where you're updating:
#order.update_attributes(order_update) #attempts to save.
Mixing it into your controller code we get:
def create
#order = Order.find(params[:id])
order_update = {:price => 5.99, :representative =>
Product.find(params[:product_id]).representative,
:shipping_location => SHIPPING_LOCATION,
:user => current_user}
respond_to do |format|
if #order.update_attributes(order_update)
# save succeeded. Redirect.
else
# save failed. Render with errors.
end
end
end
Another solution:
class Example < ActiveRecord::Base
DEFAULTS = HashWithIndifferentAccess.new(:some => 'default', :values => 'here')
def initialize(params = {})
super(DEFAULTS.merge(params))
end
end
Either use initialize and merge with params, or use an ActiveRecord hook like before_create etc.
I'd like some advice on how to best refactor this controller. The controller builds a page of zones and modules. Page has_many zones, zone has_many modules. So zones are just a cluster of modules wrapped in a container.
The problem I'm having is that some modules may have some specific queries that I don't want executed on every page, so I've had to add conditions. The conditions just test if the module is on the page, if it is the query is executed. One of the problems with this is if I add a hundred special module queries, the controller has to iterate through each one.
I think I would like to see these module condition moved out of the controller as well as all the additional custom actions. I can keep everything in this one controller, but I plan to have many apps using this controller so it could get messy.
class PagesController < ApplicationController
# GET /pages/1
# GET /pages/1.xml
# Show is the main page rendering action, page routes are aliased in routes.rb
def show
#-+-+-+-+-Core Page Queries-+-+-+-+-
#page = Page.find(params[:id])
#zones = #page.zones.find(:all, :order => 'zones.list_order ASC')
#mods = #page.mods.find(:all)
#columns = Page.columns
# restful params to influence page rendering, see routes.rb
#fragment = params[:fragment] # render single module
#cluster = params[:cluster] # render single zone
#head = params[:head] # render html, body and head
#-+-+-+-+-Page Level Json Conversions-+-+-+-+-
#metas = #page.metas ? ActiveSupport::JSON.decode(#page.metas) : nil
#javascripts = #page.javascripts ? ActiveSupport::JSON.decode(#page.javascripts) : nil
#-+-+-+-+-Module Specific Queries-+-+-+-+-
# would like to refactor this process
#mods.each do |mod|
# Reps Module Custom Queries
if mod.name == "reps"
#reps = User.find(:all, :joins => :roles, :conditions => { :roles => { :name => 'rep' } })
end
# Listing-poc Module Custom Queries
if mod.name == "listing-poc"
limit = params[:limit].to_i < 1 ? 10 : params[:limit]
PropertyEntry.update_from_listing(mod.service_url)
#properties = PropertyEntry.all(:limit => limit, :order => "city desc")
end
# Talents-index Module Custom Queries
if mod.name == "talents-index"
#talent = params[:type]
#reps = User.find(:all, :joins => :talents, :conditions => { :talents => { :name => #talent } })
end
end
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #page.to_xml( :include => { :zones => { :include => :mods } } ) }
format.json { render :json => #page.to_json }
format.css # show.css.erb, CSS dependency manager template
end
end
# for property listing ajax request
def update_properties
limit = params[:limit].to_i < 1 ? 10 : params[:limit]
offset = params[:offset]
#properties = PropertyEntry.all(:limit => limit, :offset => offset, :order => "city desc")
#render :nothing => true
end
end
So imagine a site with a hundred modules and scores of additional controller actions. I think most would agree that it would be much cleaner if I could move that code out and refactor it to behave more like a configuration.
You should check out this gem:
http://github.com/josevalim/inherited_resources/tree/master
It is very elegant, and solves all the problems you have.
I'd move your snippet-specific queries into helper methods and get them out of the controller so that the snippets themselves can execute the query via erb and kept DRY and readable via a helper. So instead of referring to #refs in your module, you can instead refer to find_all_refs or somesuch in a module and have that execute and possibly memoize the response.