I have two Model/Controllers which are essentially duplicating data (just on different pages). When I iterate through a collection on one of the controllers, it works just as intended, however when I run the collection through the partial on the other controller, it creates another "object"
def show (users_controller.rb)
...
#missions = #user.missions
#mission = current_user.missions.build
...
end
def index (missions_controller.rb)
...
#missions = #user.missions
#mission = current_user.missions.build
...
end
When I call render #missions (app/views/missions/_mission.html.erb) on both the show.html.erb and index.html.erb files. It works as intended on the Users_controller but creates an extra "object" on the Missions_controller. It seems to be rendering both the #mission and #missions variables in the controller when rendering the collection.
Why and how can I fix this? I've tried moving the partial to a shared view directory, but the problem remains. I'm assuming it has to do with how I named my instance variables? I'm super stumped. Thanks guys
This is just a guess, but I would say this is likely happening because the mission you're building is included in #user.missions. Criteria aren't evaluated until they're actually used (by calling .all or .each or something) so by using current_user.missions.build you're adding an empty mission to the end of #user.missions... assuming that current_user and #user are the same.
So you can probably fix this by changing the call to #missions = #user.missions.all which will force it to be evaluated immediately, before the built mission is added to the end.
As I said, just a guess though!
Related
Alright, I have a basic CMS in the works & would liked to try my hand at caching what are essentially a set of dynamically generated navigation links. In my pages_controller, I have the following action to toggle a page's visibility, as well as the query to create an #nav_links variable based on those pages that are visible
before_filter :nav_links, only: [:index, :new, :show, :edit]
def toggle
#visibility = #page.visible? ? false : true
unless #page.update_attribute(:visible, #visibility)
flash[:alert] = "Uh oh! Looks like something went wrong."
end
expire_fragment "nav"
end
def nav_links
#nav_links = {}
groups = PageGroup.order("id")
groups.each do |group|
if group.visible?
#nav_links[group.name.to_sym] = group.pages.where(visible: true)
end
end
end
In my view, I've wrapped the section of the page pertaining to #nav_links with <% cache "nav" do %>...<% end %>. So my question, what happens exactly when one of these actions is called?
Does Rails still execute the before_filter, query the database, & re-populate the #nav_links hash per visit? Or does it only get called if the view can't find a copy of #nav_links in the cache? If it doesn't execute each time, does Rails use SQL caching by default to use the same results unless a record was changed?
Do I need to modify the nav_links method as well or will the cache work as expected with only the changes to the view & action that updates a page's visibility?
Thanks in advanced; I'm new at Rails so I hope my questions make sense.
If you have cache "nav" wrapped around some template code, that code will be run once, the results cached, and the cache will be reused until the template changes. Your cache invocation doesn't reference any data objects, so there is no application state that can change which would result in the cache being busted.
Also, I'm fairly certain that expire_fragment doesn't work with Rails 4 cache digests.
I'm new to Rails (I've worked in MVC but not that much) and I'm trying to do things the "right" way but I'm a little confused here.
I have a site navigation with filters Items by different criteria, meaning:
Items.popular
Items.recommended
User.items
Brand.items # by the parent brand
Category.items # by a category
The problem is that I don't know how to deal with this in the controller, where each action does a similar logic for each collection of items (for example, store in session and respond to js)
Either I have an action in ItemsController for every filter (big controller) or I put it in ItemsController BrandsController, CategoriesController (repeated logic), but neither provides a "clean" controller.
But I don't know witch one is better or if I should do something else.
Thanks in advance!
You're asking two separate questions. Items.popular and Items.recommended are best achieved in your Item model as a named scope This abstracts what Xavier recommended into the model. Then in your ItemsController, you'd have something like
def popular
#items = Item.popular
end
def recommended
#items = Item.recommended
end
This isn't functionally different than what Xavier recommended, but to me, it is more understandable. (I always try to write my code for the version of me that will come to it in six months to not wonder what the guy clacking on the keyboard was thinking.)
The second thing you're asking is about nested resources. Assuming your code reads something like:
class User
has_many :items
end
then you can route through a user to that user's items by including
resources :users do
resources :items
end
in your routes.rb file. Repeat for the other nested resources.
The last thing you said is
The problem is that I don't know how to deal with this in the controller, where each action does a similar logic for each collection of items (for example, store in session and respond to js)
If what I've said above doesn't solve this for you (I think it would unless there's a piece you've left out.) this sounds like a case for subclassing. Put the common code in the superclass, do the specific stuff in the subclass and call super.
There's a pretty convenient way to handle this, actually - you just have to be careful and sanitize things, as it involves getting input from the browser pretty close to your database. Basically, in ItemsController, you have a function that looks a lot like this:
def search
#items = Item.where(params[:item_criteria])
end
Scary, no? But effective! For security, I recommend something like:
def search
searchable_attrs = [...] #Possibly load this straight from the model
conditions = params[:item_criteria].keep_if do |k, v|
searchable_attrs.contains? k
end
conditions[:must_be_false] = false
#items = Item.where(conditions)
end
Those first four lines used to be doable with ActiveSupport's Hash#slice method, but that's been deprecated. I assume there's a new version somewhere, since it's so useful, but I'm not sure what it is.
Hope that helps!
I think both answers(#Xaviers and #jxpx777's) is good but should be used in different situations. If your view is exactly the same for popular and recommended items then i think you should use the same action for them both. Especially if this is only a way to filter your index page, and you want a way to filter for both recommended and popular items at the same time. Or maybe popular items belonging to a specific users? However if the views are different then you should use different actions too.
The same applies to the nested resource (user's, brand's and category's items). So a complete index action could look something like this:
# Items controller
before_filter :parent_resource
def index
if #parent
#items = #parent.items
else
#items = Item.scoped
end
if params[:item_criteria]
#items = #items.where(params[:item_criteria])
end
end
private
def parent_resource
#parent = if params[:user_id]
User.find(params[:user_id])
elsif params[:brand_id]
Brand.find(params[:brand_id])
elsif params[:category_id]
Category.find(params[:category_id])
end
end
right now I am trying to generalize some of my code. So far it went well, I wrote a few mixins which I can dynamically add to Controllers or Models in order to get things done while obeying DRY.
But with my "Searchform-Helper" I hit a corner in which, right now, I am a bit clueless.
I have a mixin 'SearchIndexController' which adds the methods needed to search for data within a searchindex-table.
After including the mixin I can initialize search-actions within the according controller calling this method:
def init_searchaction(object, name=nil)
singular = object.to_s.classify
plural = singular.pluralize
name = "search_#{singular}".to_sym if name.nil?
unless self.respond_to?(name)
define_method(name) do
# init
success=false
#TODO
# >>> DRAW NEW ROUTE TO THIS ACTION <<<
# evaluate searchform input for Searchindex-Call
needle = params[:query]
success, x, notice = execute_search("#{singular}", needle)
# send selected/filtered data to page
respond_to do |format|
format.js {
render :update do |page|
page.call "sidx_updateSearchResultContentAtIdTag", "##{plural.downcase} tbody", "#{render x}" if success
page.call "sidx_updateNotice", success, "#{notice}"
page.call "sidx_stopSpinner"
end
}
end
end
else
logger.warn("#{__FILE__}:#{__LINE__}:#{self.to_s}: search-action for '#{self.class.name}' can not be created, it already exists!")
end
end
So lets say I have a User-Controller. Within the Userform I have the need to search for several objects. Lets assume I want to be able to search for users, departments and clients... with my mixin I'd just have to initialize the searchactions like this:
init_searchaction :user
init_searchaction :department
init_searchaction :client, :find_clients
these would create actions within the including controller that are called
search_user
search_department
find_clients
The only thing missing is a way to get a route for them. I don't want to have to define the route upfront. I just want to 'init_searchaction' and have the mixin create the necessary route.
So... would it be possible to add the route to the accoring search-action from withing the mixins init_searchaction method dynamically? I think the necessary code would be placed at the #TODO mark in the code example above. But I still haven't found out how to do it... I mean, actually I would be surprised if it would not be possible.
Would anyone have an idea as how to do this? Thanks in advance for any idea that leads to the solution!
You can add work around standart dynamic route
match ':controller(/:action(/:id(.:format)))'
change it to your goals and enjoy :)
by default, when i ask rails controller to do messages/index, he does
def index
respond_to{|fmt| fmt.html}
end
and shows app/views/messages/index.html.erb
there is a customer which wants his instance of the platform to display views differently (and changes cannot be done with css only).
solution i think of would be
create directory app/views/#{customername}, which would have same structure as app/views, but would only have views which have to override default ones.
setting an array constant containing list of views which have to be overriden (if not, they should load the default views)
CUSTOM_VIEWS["messages"]=["index","show","edit"]
somewhere in the customer-specific config file
in all controller actions do something like
def index
respond_to do |fmt|
fmt.html do
if CUSTOM_VIEWS[params[:controller]].include?(params[:action])
#override default app/views/messages/index.html.erb with app/views/customername/messages/index.html.erb
render "#{customername}/#{params[:controller]}/#{params[:action]}"
end
end
end
end
or is there better/faster solution/plugin to do that?
This should help:
http://railscasts.com/episodes/125-dynamic-layouts
i believe "view_paths" along with "prepend_view_path" can be an answer to my question
for example
http://www.axehomeyg.com/2009/06/10/view-path-manipulation-for-rails-with-aop/
upd:
solved with simple add to application_controller
def override_views
if APP_CONFIG['pr_name']!=nil
ActionController::Base.view_paths=[RAILS_ROOT+"/app/custom_views/"+APP_CONFIG['pr_name'],RAILS_ROOT+"/app/views/"]
end
end
where APP_CONFIG['pr_name'] is specific product name.
basically what it does is loading custom view from app/custom_views/customername/ if it exists for specific controller action, if not it loads default view from app/views/
Let's say that I have a widget that displays summary information about how many posts or comments that I have on a site.
What's the cleanest way to persist this information across controllers?
Including the instance variables in the application controller seems like a bad idea. Having a before filter that loads the data for each controller smells like code duplication.
Do I have to use a plugin like the Cells Plugin (http://cells.rubyforge.org/) or is there a simpler way of doing it?
Presumably, you have a single partial that displays this info. You can put the methods that fetch the data you need in ApplicationHelper or as class methods on whatever model(s) you're getting the data from. Then call that method in the partial when you need to display it.
I wound up doing something similar to this:
In controllers/application.rb
def load_sidebar
#posts = Post.find(:all)
end
To include the sidebar in various actions I did this:
before_filter :load_sidebar, :only => [ :index ] #load from application.rb file
I made the sidebar into a shared partial.
def load_sidebar
#posts = Post.find(:all)
end
You mentioned that you were displaying summary info. If you don't really want to load all of your posts into memory, you can do the following.
def load_sidebar
#post_count = Post.count(:id)
end