I have a Class Meeting from which derives MeetingOnline and MeetingOnSite.
I want to be able to use the views of Meeting for both MeetingOnline and MeetingOnSite.
Now when i do <%= render #meetings %> it asks me for meeting_on_lines/_meeting_on_line_partial. But i want him to use instead meetings/_meeting since what i want to show is shared between the 2 derived models.
In my controller i have #meetings = Meeting.all simply.
Any clues on how to achieve this ?
Explicitly set the partial. By jsut using render #meetings rails is making an assumption about which partial to use
<%= render partial: "meetings/meeting", collection: #meetings %>
Related
In my Rails app, I have a store with products and users. Both of those have models and controllers.
What I want to achieve is to show on a view template a product to a current_user based on the attribute from a model that he has. For an example if a user has "Female" attribute from user model, and then to show some products related to this attribute. How can I achieve this?
These are my product views where all products are showed:
<% #products.each do |product| %>
<%= render "product_row", product: product, order_item: #order_item %>
<% end %>
_product_row.html.erb
<h4><%= product.name %></small></h4>
<div class="image">
<%= image_tag product.image.url(:original), class: "img-responsive" %></div>
<p>Some description.</p>
Add to Package
It sounds like you need to retrieve objects from your product class based on an attribute from the User class. Sounds like a basic service object or just a method on the user:
class User < ActiveRecord::Base
def products_for_gender
if gender == 'female'
Product.where("do some logic here based on female")
elsif gender == 'male'
Product.where("do some logic here based on male")
else
#do some other logic just in case it's nil
end
end
end
then in your controller you do this
#products = current_user.products_for_gender
in your view you then render a list with those products. This prevents you from putting logic in your view, which is rarely a good idea.
Also there's more abstraction possible, the if statement is not the prettiest, but this will cover your issue i believe. Eventually you could look into using service objects for example, https://blog.engineyard.com/2014/keeping-your-rails-controllers-dry-with-services
How are you saving the users model. If you are using devise then you have current_user helper method available in your views and you can use that to get the curren_user.gender attribute and show the view based on this
Messages are displayed green if sent by the current user, and blue otherwise. Following Rails convention, where does that logic belong?
Introdution
The user will visit /group/:id to see the list of messages, so the corresponding view is views/groups/show.html.erb and the corresponding controller is controllers/groups_controller.rb.
The message we want to display are in an array in #group, as #group.messages. The array is sorted by timestamp.
The code to style the color of the message is not important, but for simplicity purposes we will say there are two class selectors (one for from and one for to) and we can simply add a class attribute to the div that a message is within to change its color.
Both the user's sent and received messages are held in the array #group.messages.
If we have an individual message stored in message, we can test if it was sent by the current user with:
if session[:user_id] == message.user_id
Problem
Messages are ordered by timestamp and will need to be displayed in that order. For this reason, I can't see any clean way of handling the logic in the controller.
I would like to keep as much logic as possible out of the views and especially out of the partials, but after considering the options for rendering sent and received messages in different ways, the cleanest option I've found is to put the logic in the message partial.
Handling the logic in the message partial:
<% if message.user.id == session[:user_id] %>
<div class="to">
<p> <%= message.body %> </p>
</div>
<% else %>
<div class="from">
<p> <%= message.body %> </p>
</div>
<% end %>
Pros:
This method handles the logic with one if statement that is clean and simple
It allows us to make the code DRY because we won't have to use the logic anywhere else if we want it on other pages
Since every message only has a body, we don't have to make another partial to display messages without this formatting
Cons:
The logic is in the partial! I think people I'm working with or other programmers or even myself would first look in the controller then in the view then in the partial to make any changes or see the code
This doesn't feel like normal Rails convention
Handling the logic in the view:
Possibly two clean solutions -
1) Style the messages inside the logic or
2) Render a different partial for sent/received messages
Styling inside the logic:
<% #group.messages.each do |message| %>
<% if message.user.id == session[:user_id] %>
<div class="to">
<p> message.body </p>
</div>
<% else %>
<div class="from">
<p> message.body </p>
</div>
<% end %>
<% end %>
Rendering different partials:
<% #group.messages.each do |message| %>
<% if message.user.id == session[:user_id] %>
<%= render :partial => '/messages/sent_message', :message => message %>
<% else %>
<%= render :partial => '/messages/received_message', :message => message %>
<% end %>
<% end %>
Pros:
Either view solution keeps the logic out of the partial
It makes sense that showing something as one color or another is decided in the view
The view solution using two partials is clean and allows us to avoid styling within logic which also means that we can change the style within the partials and affect the look of messages everywhere.
Cons:
Both view options mean that our code is no longer DRY. Using these methods will mean that if we want the same functionality on 3 other pages, we will have to write the same code 3 more times
It makes sense that a view shouldn't be deciding anything
The view solution using two partials means that we will crowd the views/messages folder with partials, and still not have a default partial for rendering messages
Both of the view solutions just feel dirty in my opinion
My main points about my solutions -
No option allows for the logic to be held within the controller
Placing the logic inside the view means that to provide the same functionality on multiple pages, the same code will be written in more than one place
The option that looks the cleanest and makes the most sense to me means putting logic inside a partial, and there must be a better way.. right?
None of the solutions seem like they follow Rails convention
Which of the three options I coded best follow Rails convention?
Is it possible to place the logic in the controller?
Is there a better way to design this so that there is a clear solution following Rails convention?
What you probably have realized is that each of the three versions you described is either not DRY or not scalable. You've done a great job analyzing pros and cons of each option, so there is very little for me to add there. :)
To add presentation functionality to your models, Rails community uses Presenters. There is a great article on Presenters here that explains more about them.
Basically, you'll want to have one partial for message:
<div class=<%=#presenter.css_class%>>
<p> <%= message.body %> </p>
</div>
Then Presenter:
class MessagesPresenter
def initialize(message, current_user)
#message = message
#current_user = current_user
end
def css_class
message.user == current_user ? 'to' : 'from'
end
private
attr_reader :message, :current_user
end
And controller:
#presenter = MessagesPresenter.new(#message, current_user)
Voila! The presenter is available in both views and partials and is a great place to stash all presentation logic.
Since the only difference in these examples in the CSS class, you're repeating yourself quite a bit. Can't you add or remove a class on the tag depending on whether the tag belongs to the current_user or not?
This is really a presentation issue, and you can handle this simple logic for displaying the correct CSS tag using decorators (http://johnotander.com/rails/2014/03/07/decorators-on-rails/). I recommend using Draper (https://github.com/drapergem/draper).
First, for simplicity, add a current_user helper method to application_controller.rb to return the authenticated user.
Add a Decorator:
MessageDecorator.rb
def recipient_class
user_id == current_user.id ? "to" : "from" # (user_id delegates to message object)
end
Now your views can have much cleaner logic
Views
Message Partial:
<div class="<%= message.recipient_class %>">
<p><%= message.body %></p>
</div>
collection partial in the main view:
<%= render partial: "message", collection: #messages, as: :message %>
Finally, call decorate on messages in your controller action:
#messages = #group.messages.decorate
EDIT
You can also use a simple helper method rather than a decorator:
def css_class_for_message(message)
message.user_id == current_user.id ? "to" : "from"
end
Using Rails 4
I am wondering (and having a hard time finding an answer) if it is OK to call an ActiveRecord method directly from the view, such as:
<%= Article.where(approved: true).count %>
or
<%= Article.where("short_answer is NOT NULL and short_answer != ''").count %>
I realize the normal practice would be to store these in an instance variable inside of the controller, but since I am using a partial, I cannot do that.
Is doing this ok? Can it hurt? Is there a better way to go about this (such as a helper method)? Thank you!
Is doing this ok? Can it hurt?
It's definitely okay, but the problem is that you'll be calling another db query - which is the most "expensive" part of a Rails app.
#instance_variables are set once, and can be used throughout the view:
#app/views/articles/show.html.erb
#Referencing #article references stored data, not a new DB query
<%= #article.title %>
<%= #article.description %>
<%= #article.created_at %>
Because the above all uses the stored #article data, the database is only hit once (when #article is created in the controller).
If you call AR methods in the view, you're basically invoking a new db call every time:
#app/views/articles/show.html.erb
#Bad practice
<%= Article.select(:name).find(params[:id]) %>
<%= Article.select(:description).find(params[:id]) %>
<%= Article.select(:created_at).find(params[:id]) %>
To answer your question directly, you would be okay to call that data IF you were only counting database-specific data.
IE if you were trying to count the number of #articles, you'd be able to call #articles.size (ActiveRecord: size vs count)
The prudent developer will determine which data they have in their controller, and which they need to pull from the db... doing all their db work in the controller itself:
#app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
def index
#articles = Article.where(approved: true) #-> could use a scope here if you wanted
end
end
#app/views/articles/index.html.erb
<%= #articles.size %>
Nithin's answer is great but won't get past the consideration that you have to determine whether you need to call the db explicitly, or use already-invoked data.
Finally, in regards to using a partial, if you have to pass that data every time, you may wish to use some sort of conditional data to determine whether you need to call the db or not:
#app/views/shared/_partial.html.erb
<% approved ||= Article.approved_articles.size %>
<% short ||= Article.short_answer_presence.size %>
This will allow you to set locals IF you want, and also have "defaults" set if they aren't set.
You should mostly do
class Article < ActiveRecord::Base
....
scope :approved_articles, where(approved: true)
scope :short_answer_presence, where("short_answer is NOT NULL and short_answer != ''")
end
In your controller method
#approved_articles_count = Article.approved_articles.count
#short_answer_presence_count = Article.short_answer_presence.count
and use those variables in view.
In case of partials, as said my Raman you can do that.
<%= render partial: "form", locals: {approved_articles_count: #approved_articles_count, short_answer_presence_count: #short_answer_presence_count} %>
You can always pass these variables inside a partial using locals:
<%= render partial: "form", locals: {zone: #zone} %>
Its always a good practice to define the instance variables in controller, it does not hurt but you don't end up doing business logic inside a view.
In my application.html.erb I have a header partial. which I rendered with the render tag
<%= render 'layouts/header' %>
So this header applies to all the controller and all the actions.
I have a dropdown partial which i want to show, in addition to the header partial, in all the controllers except one one controller. I want something like
<%= render 'layouts/dropdown' except_controller_anycontroller %>
When I put
render :partial => 'layouts/dropdown'
It just renders the dropdown partial and all other layouts are lost (like the footer,header,body). I want to add the extra dropdown partial only to certain actions and controllers.
How can I achieve that in Rails 3.2.13?
Replace your render with this:
<%= render 'layouts/dropdown' unless #disable_dropdown %>
Then you can simply set disable_dropdown to true in any controller you like:
def test_method
#disable_dropdown = true
end
call this method in your controller filter, in which you dont want to show this:
write this on top of your controller above your first method:
before_filter :test_method
it will automatically be called when your request comes to this controller.
Hope it will help. Thanks
I would suggest something like:
<%= render 'layouts/dropdown' unless params[:controller] == "controller_to_avoid" %>
In my Rails plugin, i want to display multiple view files onto a single page.
Say i want to append some view after the index view.
i am using
<% render :partial => "show" %>
in my index.erb
It works fine. But for better design I want to do the same thing from controller.
So, if i write in my contoller inside the index action
render :partial => "show"
only the show file gets rendered on the page.
Cant I use multiple partials?
Any comments/suggestions, please?
Actually, it would be more 'true' to the MVC architecture if that logic remained in the view. I think the cleanest way to do this is to include the partials in your index view using render like you've already done.
As far as I'm aware, you cannot 'append' views/partials to one another from the controller.
When I did this I added my three show page view partials
_storethenameofparitalshowviewoptionindatabase1
_storethenameofparitalshowviewoptionindatabase2
_storethenameofparitalshowviewoptionindatabase3
to my orders controller. I did this by running a migration
rails generate migration AddChose_Parital_Show_View_ToListings ordershowviewpartialoption:string
and adding a collection select to my listing controller.
<div class="field">
<%= f.label :ordershowviewpartialoption %>
<%= f.select :WHAT THE USER SEES IN THE DROP DOWN MENU FOR PARTIAL SHOW VIEW OPTION 2, [['Tictactoe Game',
'storethenameofparitalshowviewoptionindatabase1'],
['WHAT THE USER SEES IN THE DROP DOWN MENU FOR PARTIAL SHOW VIEW OPTION 2',
'storethenameofparitalshowviewoptionindatabase2'],
['WHAT THE USER SEES IN THE DROP DOWN MENU FOR PARTIAL SHOW VIEW OPTION 3',
'storethenameofparitalshowviewoptionindatabase3']], {}, {class: "form-control"} %>
</div>
and don't forget in controllers\listings_controller.rb
change this...
def listing_params
params.require(:listing).permit(:AAA, :BBB, :CCC)
end
to this...
def listing_params
params.require(:listing).permit(:AAA, :BBB, :CCC, :ordershowviewpartialoption)
end
in models\order.rb I have
belongs_to :listings
in models\listing.rb I
have has_many :orders
in my orders\show.html.erb
<%= render #order.listing.ordershowviewpartialoption %>