allowing users to change the order of a scope - ruby-on-rails

I've only ever used a static default scope for my projects and my latest one I wanted to create a dynamic scope feature, where the user can click a button or use a dropdown menu to change the order they view a list.
so far I've come across a few methods like using unscoping and setting a new scope, and seen reorder. I'm looking at the docs but I also am unsure of how to make a user be able to choose. Would I use something like Link_to, or button_to in the corresponding HTML.erb file?
in my post.rb file it'd look like this
default_scope { order(created_at: :desc) }
scope :ordered_by_title, -> { reorder(title: :asc) }
scope :ordered_by_reverse_created_at, -> { reorder(created_at: :ASC)}
I added those other scopes under the default because I assumed one would set the scopes they wanted and the view would call/activate them once a user clicks or chooses it from the rendered page.
in my post_controller.rb
def index
#posts = Post.all
end
in my index.html.erb the view
I have the list of posts rendered this way, if it won't work with what I want to do can someone show me a better way to do it?
# <some way for user to choose those defined scopes and render the new page would go here>
<% #posts.each do |post| %>
<div class="media">
<div class="media-body">
<h4 class="media-heading">
<%= link_to post.title, post %>
<small> <%= post.body %> </small>
</h4>
</div>
</div>
<% end %>

There are basically two ways to do this:
purely front end, use js. You can use some existing js plugin with sorting abilities if you don't feel like writing your own (e.g. DataTables). You'll probably need to make a table with the different attributes available for this to work.
back end, use ajax. Write your own or use something like filterrific, which uses scopes for filtering and sorting.

Related

Rails, current_page?(user_path) giving an error

I have element witch I want hide on specific pages, for example on pages located at app/views/users/ (there I have new.html.erb; edit.html.erb; show.html.erb. And I have div in my layouts/application.html.erb it will be shown on all pages, so I want to hide it.
I thought i can do it like this:
<% unless current_page?(new_user_path) || current_page?(user_path) %>
<div>Some content</div>
<% end %>
But it will give me an error, pretty obvious: for user_show method he need an id of the user, but we are not visiting pages where variable #user is present. Can you land me a help:
Any possibility to get around this error? (And I don't want to assign #user variable every where and I don't want make list of all page what are allowed)
Is there any other way to hide element on specific pages?
Not entirely sure what you are trying to achieve, but this is how you can guard from user not being present:
unless current_page?(new_user_path) || #user && current_page?(user_path(#user))
I think what you probably want is:
<% unless current_page?(controller: 'users') %>
<div>Some content</div>
<% end %>
By passing controller: 'users' you catch all routes (actions) for that controller, including the new/edit/show routes.
For more detail see the docs.

Rails convention - placing logic in view vs controller vs partial

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

How to find most recent post for each record in related table

I have a one to many relationship between 'challenge' and 'entry'. I am trying to create a page that shows the most recent entry for each challenge. I don't know how to do this. So far I have something which just shows all the entries only:
discovers_controller.rb
def index
#discovers = Entry.all
end
end
discovers.html.erb
<div id="grid-gallery" class="grid-gallery">
<section class="grid-wrap">
<ul class="grid">
<li class="grid-sizer"></li><!-- for Masonry column width -->
<% #discovers.each do |discover| %>
<li>
<figure>
<%= image_tag discover.picture.url if discover.picture? %>
<figcaption><p><%= discover.blob %></p></figcaption>
</figure>
</li>
<% end %>
</ul>
</section>
If you set up relation in standard way, you should be able request entries for each challenge in the way suslov mentioned in comments: challenge.entries
Then you just take the most recent - .last the easiest way to do it, but it could not suit you if you define most recent by update_at field, for example. So, you probably would want to sort it beforehand with .order('updated_at') in this case (or any other relevant attribute)
In you particular case to define last entries, you can use something like that in your controller
#discoveries = Challenge.all.map{|c| c.entries.last}
In your controller you can do:
#discovers = Entry.order('created_at DESC').all
However, I'd recommend you to use pagination instead of doing all in your query. As it will increase the page load time as the number of data in entries table increases.
UPDATE:
You ca create a scope in Entry model:
scope :last_entries, -> { order(:created_at => :desc).group(:challenge_id) }
then in your controller:
#discovers = Entry.last_entries #no need to do `all`, as it is default
But, I'd still suggest you to consider using pagination.

Make a very simple CMS

I'm making an extremely simple CMS with rails that simply changes existing text and images on existing pages, nothing else. No changes to layout or anything crazy like that.
It's a bit like wordpress. Here's how it'll work:
If an admin is logged in, they have a special bar at the top of the
page that will enable them to edit the page.
Upon clicking, they go to the control panel, and all of the editable regions of viewable.
They can then edit the pages in the control panel, and save it.
What would be a good way to organize this flow?
I've made a start, and one thing that gets me is that all of the logic that populates a page' editable regions is occurring in the view:
splash/welcome.html.erb
<% #page = Page.find(name: 'welcome') %>
<% regions = #page.text_regions %>
<h1> <%= regions.where(name: 'title').text %> </h1> This returns the title (Welcome to my website)
<%= regions.where(name: 'welcometext').text %> This returns welcome text (This is my website bla bla)
I works fine, although the database has to be initially seeded with empty regions.
However, I don't like how there is logic in the view, so how could I populate a #regions instance variable in the application controller? Or is what I'm doing fine?
Maybe in the future I want users to be able to create pages, meaning I won't be able to have this logic in the view.
DRY
Looks like you'd be better using action_name (perhaps with a helper)
Without knowing the structure of what you're trying to do, this is just speculation, but you may be better doing something like this:
#app/models/region.rb
Class Region < ActiveRecord::Base
scope :title, -> { find_by name: "title" }
scope :welcome_text, -> { find_by name: "welcometext" }
end
#app/controllers/application_controller.rb
Class ApplicationController < ActionController::Base
before_action :set_page
private
def set_page
#page = Page.find_by name: action_name
#regions = #page.text_regions
end
end
#app/views/shared/_regions.html.erb
<h1><%= #regions.title.text %></h1>
<%= #regions.welcome_text %>
#app/views/static/welcome.html.erb
<%= render "shared/regions" %>
Hopefully this will give you some ideas as to how to achieve what you want?

How to hide parts of the view given a user role on Rails 4

I'm trying to hide parts of my views depending on the User role.
So let's say I want only admins to be able to destroy Products. Besides the code in the controller for preventing regular users from destroying records, I would do the following in the view:
<% if current_user.admin? %>
<%= link_to 'Delete', product, method: :delete %>
<% end %>
The previous code works, but it's prone to errors of omission, which may cause regular users to see links to actions they are not allowed to execute.
Also, if I decide later on that a new role (e.g. "moderator") can delete Products, I would have to find the views that display a delete link and add the logic allowing moderators to see it.
And if there are many models that can be deleted only by admin users (e.g. Promotion, User) maitenance of all the ifs would be pretty challenging.
Is there a better way of doing it? Maybe using helpers, or something similar? I'm looking for something maybe like this:
<%= destroy_link 'Delete', product %> # Only admins can see it
<%= edit_link 'Edit', promotion %> # Again, only admins see this link
<%= show_link 'Show', comment %> # Everyone sees this one
I found these two questions that are similar to mine, but none of them answered my question:
Show and hide based on user role in rails
Ruby on Rails (3) hiding parts of the view
I strongly recommend pundit.
It allows you to create "policies" for each model. For your Product model you might have a ProductPolicy that looks something like this
class ProductPolicy < ApplicationPolicy
def delete?
user.admin?
end
end
In your view you can do something like this
<% if policy(#post).delete? %>
<%= link_to 'Delete', product, method: :delete %>
<% end %>
If later on you want to add a moderator role, just modify the policy method
class ProductPolicy < ApplicationPolicy
def delete?
user.admin? || user.moderator?
end
end
So I kind of figured a way to move the IFs out of the view. First, I override the link_to helper in my application_helper.rb:
def link_to(text, path, options={})
super(text, path, options) unless options[:admin] and !current_user.admin?
end
Then on my views I use it as:
<%= link_to 'Edit Product', product, admin: true, ... %>
This prevents regular users from seeing admin links, but for other html tags with content inside, such as divs, tables etc., an if would still be needed.
CanCan is another gem that lets you define "Abilities" per user role.
In views you can use something like if can? :delete, #post to check if the
user may delete that specific post.
Using the CanCan and Role gems, what is still needed is a way to Check The Route and see if "current_user" has permissions to access that Route based on their role(s) - then show/hide based on that.
This saves the user clicking on things and getting told they cannot see it - or us having to write per-item "if" logic specifying what roles can see what list-items (which the customer will change periodically, as roles are changed/refined) around every single link in one's menu (consider a bootstrap menu with 50+ items nested in groups with html formatting, etc), which is insane.
If we must put if-logic around each menu-item, let's use the exact same logic for every item by checking the role/permissions we already defined in the Ability file.
But in our menu-list, we have route-helpers - not "controller/method" info, so how to test the user's ability to hit the controller-action specified for the "path" in each link?
To get the controller and method (action) of a path (my examples use the 'users_path' route-helper) ...
Rails.application.routes.recognize_path(app.users_path)
=> {:controller=>"users", :action=>"index"}
Get just the controller-name
Rails.application.routes.recognize_path(app.users_path)[:controller]
=> "users"
Ability uses the Model for its breakdown, so convert from controller name to it's model (assuming default naming used) ...
Rails.application.routes.recognize_path(app.users_path)[:controller].classify
=> "User"
Get just the action-name
Rails.application.routes.recognize_path(app.users_path)[:action]
=> "index"
And since the "can?" method needs a Symbol for the action, and Constant for the model, for each menu-item we get this:
path_hash = Rails.application.routes.recognize_path(app.users_path)
model = path_hash[:controller].classify.constantize
action = path_hash[:action].to_sym
Then use our existing Abilty system to check if the current_user can access it, we have to pass the action as a symbol and the Model as a constant, so ...
<% if can? action model %>
<%= link_to "Users List", users_path %>
<% end %>
Now we can change who can see this resource and link from the Ability file, without ever messing with the menu, again. But to make this a bit cleaner, I extracted out the lookup for each menu-item with this in the app-controller:
def get_path_parts(path)
path_hash = Rails.application.routes.recognize_path(path)
model_name = path_hash[:controller].classify.constantize
action_name = path_hash[:action].to_sym
return [model_name, action_name]
end
helper_method :get_path_parts
... so I could do this in the view (I took out all the html-formatting from the links for simplicity, here):
<% path_parts = get_path_parts(users_path); if can?(path_parts[1], path_parts[0]) %>
<%= link_to "Users Listing", users_path %>
<% end %>
... and to make this not take all day typing these per-menu-item if-wraps, I used regex find/replace with capture and wildcards to wrap this around every list-item in the menu-item listing in one pass.
It's far from ideal, and I could do a lot more to make it much better, but I don't have spare-time to write the rest of this missing-piece of the Role/CanCan system. I hope this part helps someone out.

Resources