Sort a ruby hashmap - ruby-on-rails

I have the following line of code
<% map = options_for_select(User.all.map {|u| [u.first_name+" "+u.last_name, u.id]}) %>
which grabs the first and last name of a user and submits its ID in a form. Now I have added a few users and they are not in alphabetical order. How can I sort this map by first name?

You can also use order to get the rows already ordered from the database:
<% map = options_for_select(User.all.order(:first_name).map {|u| [u.first_name+" "+u.last_name, u.id]}) %>

May be...
<% map = options_for_select(User.all.map {|u| [u.first_name+" "+u.last_name, u.id]}.sort) %>

You should never use queries on the views. You should use views only for presentation, and all the logic on the Models and or the Controllers.
Also, respecting Fat Models, Skinny Controllers Best Practice:
In practice, this can require a range of different types of refactoring, but it all comes down to one idea: by moving any logic that isn’t about the response (for example, setting a flash message, or choosing whether to redirect or render a view) to the model (instead of the controller), not only have you promoted reuse where possible but you’ve also made it possible to test your code outside of the context of a request.
Finally, in this case it's best to use a scope to reuse it later.
Use a scope on User model, and have a name method:
class User < ActiveRecord::Base
scope :order_by_name, ->(first_name, last_name) { order("#{first_name} ASC, #{ last_name} ASC") }
def name
"#{first_name} #{last_name}"
end
end
If you're calling your line from users/index, create an instance variable to load the users collection like this:
class UsersController < ApplicationController
def index
#users = User.order_by_name
end
end
Then you would call on the view using options_from_collection_for_select like this:
<% map = options_from_collection_for_select(#users, :id, :name) %>

I recommend you to do both the CONCAT and ORDER in the Database for performance reasons
User.select("CONCAT(u.first_name, ' ', u.last_name), u.id").order("u.first_name")
This User.all.map could be a bottleneck of your application

Related

How to manage users and their roles as an admin using acl9

Wondering what the best approach would be for managing user roles using acl9?
Here's the need:
Admins should be able to update a user's role.
Here are the issues:
How can we list all subject roles? :admin, :manager
To keep the api endpoints RESTful, I would like to pass the role param in the user_controller's update method ideally.
How can I authorize just for the role property so that, the owner of the user object can still modify their first_name, last_name fields, but not their role field? Only admins are allowed.
I can check for params manually outside of the access_control block:
def secure_params
if current_user.has_role?(:admin)
params.require(:user).permit(:first_name, :last_name, :role)
else
params.require(:user).permit(:first_name, :last_name)
end
end
but thought I would check and see if there is a cleaner solution.
Lastly, is it possible and wise to use something like RoleTypes.ADMIN, RoleTypes.MANAGER instead of :admin? If so, what's the best way to do this and is that Class accessible throughout a rails app?
How can we list all subject roles? :admin, :manager
The Role model is just a normal model, so you can just query this like you would anything else:
Role.uniq.pluck :name
To keep the api endpoints RESTful, I would like to pass the role param in the user_controller's update method ideally.
Generally, you're much better off using a separate controller for performing administration, and put it in the Admin:: namespace, and use namespace :admin do routes, etc..
Then in that controller you can use a normal access_control block to make sure no one else can get in:
class Admin::UsersController < ApplicationController
access_control do
allow :admin
end
# ...
end
So then, yeah, you can set/update a role in a number of ways, but seeing as a user can have many roles it's probably best not to have a single :role param, but rather to use:
class User < ActiveRecord::Base
acts_as_authorization_subject
accepts_nested_attributes_for :roles
end
So then in your controller you would have:
def user_params
params.require(:user).permit(:first_name, :last_name, roles_attributes: [:name])
end
Then in your form you can do something like:
= form_for :user do |f|
= f.text_field :first_name
= f.text_field :last_name
= f.fields_for :roles do |fr|
= fr.text_field :name
Note that I'm assuming you have just simple roles here, not roles on an object, if you have object roles then you'll need something a bit trickier to capture authorizable_id and authorizable_type for each role, and some way to select the object on which to act.
How can I authorize just for the role property so that, the owner of the user object can still modify their first_name, last_name fields, but not their role field? Only admins are allowed.
Hopefully you're already answering this one yourself now - by using different controllers for the admin interface and the user's own interface.
Lastly, is it possible and wise to use something like RoleTypes.ADMIN, RoleTypes.MANAGER instead of :admin?
No, the symbols are simpler and better, although it's quite common to do something like:
class Role < ActiveRecord::Base
def self.permitted_roles
%i/admin manager foo bar wee/
end
end
So that you can then use Role.permitted_roles to construct an array of checkboxes, or a dropdown, or something like that.
Edit: Totally missed the acl9 bit in the first sentence - sorry for that!
That being said, my answer is still decently applicable; I would have a helper method or use acl9's has_role? method and alter the view based on this. Ie:
>> <% if #user.has_role?(:foo, nil) %>
>> <p>
>> <%= f.label :usertype %><br />
>> <%= f.check_box :usertype %>
>> </p>
>> <% end %>
I would use a helper method to actually set the user role using the .has_role! method ie: user.has_role! :some_role, [optional scope], where user is the user being assigned the role.
For more info, check out this (from the acl9 docs):
https://github.com/be9/acl9/wiki/Tutorial%3A-securing-a-controller
Ok, so if what I think you're saying is correct, you want a way to have content and/or profile information editable by users but user roles editable by an admin.
First off, I'm going to assume role is a part of your user schema (if its not, I suggest you consider moving it there). If so, I would suggest adding a method to your user model may_edit?(content) that returns a boolean depending on the user role or id. Ie:
def may_edit?(content)
item.user.id == self.id
end
Where the content is found via a find_by(id) in the controller. You could then use an if in the controller to dictate which content will appear on the show:
<% if current_user.role == admin %>
<%= some button or link or dropdown render %>
<% end %>
<% if current_user.may_edit?(content) %>
<%= some button or link or dropdown render %>
<% end %>
Or something along those lines. You can also write an if..else as a separate helper method or in the may_edit? method (I'd suggest making it/them model methods). The controller ideally shouldn't be the one deciding who can and can't perform this kind of thing. If you're set on doing it in the controller, you might want to look into :before_filter at the top of your controller, ie:
:before_filter admin?, except [:foo, :bar]
I'm not sure which user id you want to pass in the params, but either the editor/admin's or the original poster's ids can be found - the current_user will be logged in session as session[:user_id] and the original poster can be found via the content being edited, since it belongs to a user and can be found via a ``User.find_by(content.user_id).
Finally, are you asking how to see a list of all user roles? If so, you can use a dropdown menu, but you'll have to either hardcode them somewhere in your project or iterate through every user and aggregate their roles, although the latter is far from ideal and very inefficient.
Hope this helps, and good luck!

Ruby on Rails 4 find object by id

I have the following show-view, where i display basic information about Product and display other User's Products.
<h1>Book <%= #product.name %></h1>
<% #products.each do |product| %>
<ul>
<%= product.name %>
<%= link_to "Make offer", {controller: "offers", :action => 'create', id: product.id } %>
</ul>
Controller
def show
#product = current_user.products.find(params[:id])
#products = Product.all
end
My goal is to make Offer between two Products.
I created Offer model and methods for making Offers:
class Offer < ActiveRecord::Base
belongs_to :product
belongs_to :exchanger, class_name: "Product", foreign_key: "exchanger_id"
validates :product_id, :exchanger_id, presence: true
def self.request(product, exchanger)
unless product == exchanger or Offer.exists?(product, exchanger)
transaction do
create(product: product, exchanger: exchanger, status: "oczekujace")
create(product: exchanger, exchanger: product, status: "oferta")
end
end
#other methods
end
Making offers is working, because I checked it in Console.
My problem is in OffersController:
class OffersController < ApplicationController
before_filter :setup_products
def create
Offer.request(#prod, #exchanger)
redirect_to root_path
end
private
def setup_products
#prod = current_user.products.find(1)
#exchanger = Product.find_by_id(params[:id])
end
end
Problem is with a following line (using link in show-page for products with different id's than 1 works):
#prod = current_user.products.find(1)
But I don't know how to find object in Db for actual product which my show-page shows. Not only for id = 1.
I don't know how to find this object in database.
I don't know the specific answer to your question, but perhaps if I explain what you need to look at, your solution will arise:
Find
Rails isn't magic - it uses ActiveRecord (which is an ORM - Object-Relation Mapper), which means every time you fire a query (through find), your ORM (ActiveRecord) will search the relevant database data for you
The problem you have is that although you're using the correct syntax for your lookup, you may not have a record with an id of 1 in your db.
current_user.products.find(1) tells ActiveRecord to scope the query around the current user, and their products. So you'll get something like like this:
SELECT * FROM 'products' WHERE user_id = '15' AND id = '1'
Objects
Further, you have to remember that Ruby (and Rails by virtue of being built on Ruby) is an object orientated language. This means that everything you load / interact with in the language should be based on an object
The problem you have is you're not associating your object to your Rails framework correctly. What I mean here is described below, but essentially, if you build your Rails framework correctly, it will give you the ability to associate your objects with each other, allowing you to call the various products you need from your offer
This is a simplistic way of looking at it, of course. You'll want to look at this diagram to see how it works:
Bottom line - try treating your application like a series of objects, rather than a logical flow. This will help you appreciate the various associations etc that you need to get it moving forward
Resources
You mention you can't show the product on your show page for an id other than one. I think the problem is really about how to get your show action to work.
If this is the case, let me explain...
Rails is resource-based, meaning that everything you do / create needs to be centred around a resource (object) of some sort. The problem is many people don't know this, and consequently complicate their controller structure for no reason:
Above is the typical "CRUD" routing structure for Rails-based resources. This should demonstrate the way that Rails will typically be constructed -- around resources
--
Further, Rails is built on the MVC programming pattern - meaning you need to use your controller to populate a series of data objects for use in your application.
To this end, if you load a resource, and want to populate it with resourceful information of another object - you need to make sure you have set up the data objects in a way to ensure you can look them up correctly, which either means passing the data through your routes or using a persistent data-type, such as cookies or sessions
The problem you have is you need to pass the product id to your controller somehow. How I'd do that is as follows (using nested resources):
#config/routes.rb
resources :offers do
resources :products #-> domain.com/offers/2/products
end
This will give you the ability to load the products controller with the variables params[:id] for the product, and params[:offer_id] for your Offer made available:
#app/controllers/products_controller.rb
Class ProductsController < ApplicationController
def show
#offer = Offer.find params[:offer_id]
#product = Product.find params[:id]
end
end

Rendering query best practice rails 4.0

I have a model for Contracts that has a field called "totalAward". In my index.html.erb view I have the following code:
<p> Total Award Amount: <td><%= number_to_currency(Contract.sum('awardAmount')) %></td> </p>
I'm fairly certain it's not a best practice for me to do a database call in the view, but I am not sure how to do it in the controller or the model in a way that I can render it in the view. Could someone help me with how make that database call in the controller or the model?
Thanks!
The instance variable (#contracts) from your controller will give you access to the attributes of your model. I believe you want to use ActiveRecord's sum method. Here is how you can use it in your view:
<%= #contracts.sum(:totalAward) %>
This way, you are not querying the db in your views. but you have access to the instance variable (#contracts) in your controller which holds a collection of all of your contracts.
Further to Wali Ali, you will be best sticking to the MVC programming pattern (the basis of Rails). This means you need to keep your data-allocation (set variables) in your controller, and data storage in your models
I would use a instance method:
#app/models/contract.rb
Class Contract < ActiveRecord::Base
def total
sum(:totalAward)
end
end
This will allow you to perform things like this:
#app/controllers/contracts_controller.rb
Class ContractsController < ApplicationController
def index
#contracts = Contract.all #-> #contracts.total for all
#contracts = Contract.where(some: value) #-> #contracts.total for these items
end
end

Best Practice for Helper/Partial that render a Model driven select

Let's assume I have a model Product, and I want a drop-down select box that contains all products. This drop-down is used in several views, so it is going to be created by a helper method. Where is the 'best practice' location to get the select options from Product? Do I set #products = Product.all in every controller action that needs to show the drop-down, or do I make the helper method self contained by having it call Product.all? Does the answer change if I am dealing with a partial, or if I am filtering the products (i.e. Product.in_category(#category))? MVC says use the controller, but DRY says use the helper.
Look at the collection_select form helper that's built in. You can pass in different collections (Product.all, Product.) as and where needed in different views.
collection_select
From the link:
collection_select(object, method, collection, value_method,
text_method, options = {}, html_options = {})
Returns and tags for the collection of existing
return values of method for object‘s class. The value returned from
calling method on the instance object will be selected. If calling
method returns nil, no selection is made without including :prompt or
:include_blank in the options hash.
The :value_method and :text_method parameters are methods to be called
on each member of collection. The return values are used as the value
attribute and contents of each tag, respectively. They can
also be any object that responds to call, such as a proc, that will be
called for each member of the collection to retrieve the value/text.
Example object structure for use with this method:
class Post < ActiveRecord::Base belongs_to :author end
class Author < ActiveRecord::Base has_many :posts def
name_with_initial
"#{first_name.first}. #{last_name}" end end
Sample usage (selecting the associated Author for an instance of Post,
#post):
collection_select(:post, :author_id, Author.all, :id,
:name_with_initial, prompt: true)
If #post.author_id is already 1, this would return:
Please
select D. Heinemeier
Hansson D. Thomas M. Clark
In my opinion, Controller should decide what data the user sees. How the user sees it can be decided by the view or by the helper.
So i would advise you to put
#products = Product.all
or
Product.in_category(#category)
in your controller
Any kind of filter you apply should be done in the controller as well
With rails being a model-view-controller (MVC) framework, you're going to want that logic to be on the model. Having some method that returns the options for your select would probably be best (though, take that with a grain of salt because these things change a lot with application). Something I might try would be along the lines of:
class Product < ActiveRecord::Base
def self.get_select_options(category=nil)
if category.nil?
Product.all
else
Product.in_category(category)
end
end
end
... which you could then call with Product.get_select_options or Product.get_select_options(#category)

Rails associate record counting inside view

I have two models, story & vote. I have a list of articles which a user can up vote which is stored as a separate vote in the vote model.
The story model has many votes and the story_id is stored with the vote record.
At the moment in my index action on the story view I'm just trying to do a simple count to see how many votes a given story has, I'm achieving this with the following code.
<%= #total_votes = Vote.where(:story_id => story.id).count %>
However I'd like to remove this from my story index action view but not sure were to store it? Is there a more efficient way of doing this?
Seems the relationship is story has many votes. So, you can simply do:
<%= story.votes.count %>
If you are worried about database performance you may want to add a counter cache.
You said you'd like to store the total votes value:
$~ rails g migration add_total_votes_to_stories total_votes:integer
class Story
after_save, :update_total_votes
private
def update_total_votes
write_attribute :total_votes, votes.count
end
Inside the model you could define a method and use it in the controller.
def total_votes(sid)
where(:story_id => sid ).count
end
Then in the controller:
#total_votes = Vote.total_votes(story_id)
this should go in controller
def index
#total_votes = Vote.where(:story_id => story.id).count
end

Resources