Paginated array would not show - ruby-on-rails-4

I'm rather new to Ruby on Rails so would appreciate some help
Roles have Users in my application.
When showing a role I would like to render a paginated list of user names (and as a further refactor - links to users).
Currently my HAML template is as follows
...
%ul.assigned-users
- if #role.users.present?
- names = #role.users.collect {|u| u.name}
- links = Kaminari.paginate_array(names).page(params[:page]).per(20)
= paginate links
- else
%p No users have this role assigned
...
Rendering it gives me the pagination links BUT no names.

Kaminari's paginate_array method does not show the values in the array.
The best way to do so is get the query paginated from the database. Kaminari.paginate_array in your code takes in the whole array of users from the database and then paginates it which is highly inefficient and memory consuming.
You need to add the logic to the controller. If you paginate the #role.users query, it is generated with LIMIT which is the value you assign in the per method and OFFSET which equals to (params[:page] - 1) * per. This gets only the number of records you need from the database.
In your controller app/controllers/roles_controller.rb
class ConversationsController < ApplicationController
def show
#role = Role.find params[:id]
#users = #role.users.page(params[:page]).per(20)
end
end
In your view app/views/roles/show.html.haml
%ul.assigned-users
- if #users.present?
- #users.each do |u|
%li=u.name
= paginate #users
- else
%p No users have this role assigned

Related

Rails 4 Dynamically Generate an Extra Public-Facing URL on Create

I have a Rails app that stores stock photos in each project. Upon creating a project, I want the app to not only create the url for the project that we will view internally, which is projects#show,i.e. /projects/4 but I also want it to create another URL that we can show to the client that will be a little different. It will allow the to approve the photos, etc. I want the url to be something like /projects/st53d where the end of the url will be a random number generated with random_string = SecureRandom.urlsafe_base64(5)
This way, I can pass this url to the clients and they can view all the photos in the project and approve the ones they want us to use, but cannot change or view the other internal stuff we have on the standard product show page
Currently, I have added a client_url column to the Project model and I was thinking of generating the random number within the Project#create method and then saving it to the project's client_url column. This way I can loop through all client urls and make sure I did not generate a duplicate. But I cannot figure out how to do the route creation part.
i have yet to do the part where I check if it is random but I know how to do that, just not the route creation.
I was thinking I needed to create a new method which I named show_client and somehow create a route for that in my config/routes.rb file
class ProjectsController < ApplicationController
before_action :authenticate_user!, only: [:show,:index]
def create
#project = Project.create(project_params)
#project.creator = current_user.email
require 'securerandom'
random_string = SecureRandom.urlsafe_base64(5)
#project.client_url = random_string
#project.save
redirect_to #project
end
def show_client
#project = Project.find(params[:id])
#photos = #project.photos
end
This seems like more of a roles issue. You could check out something like CanCanCan. If you only need two kinds of users, you could also just add a boolean column for admin to your user table. The url extension doesn't seem to matter in the scope of your problem. If you want to use a different view for the visiting the user show page, something in the show action like:
unless current_user.admin?
redirect_to client_show(current_user.id)
end
I was able to figure it out.
I created the client_url column in the database and upon creating a project, in the create method of the projects_controller, I generated a random base64 number and assigned it to project.client_url
in routes.rb i did:
get 'projects/clients/:client_id' => 'projects#clients', as: 'projects_client'
in my projects_controller.rb:
def clients
#project = Project.where(client_id: params[:client_id])
end
then the link to that route is:
<%= link_to 'Client Version', projects_client_path(#project.client_url) %>

Wizard input - DB View - Dynamic where clause

I was trying to pass where condition values onto a database view.
View was created in init method of class defined.
Input to where clause was taken from a popped up wizard.
Issue is that the wizard form values are inserted into model bound database table.
This is happening on all submits.
Currently I am reading the latest record from table on wizard input.
And the view definition is modified to generate result set based on latest input record from wizard table.
select v.col1, v.expre2
from view_name v,
( select fld1, fld2 from wizrd_tbl_1 order by id desc limit 1 ) as w
where
v.colM between w.fld1 and w.fld2
Currently I am following the above sequence of steps and results are fetched.
But I think, this would fail if at least two users are using the same wizard concurrently.
How can I change my approach, so that
1. Wizard input is not sent to database table,
2. The inputs are sent to a where clause dynamically and the result set is bound to a List View
As a summary, I was trying to:
Creates a database view joining multiple table.
Take user input ( and saves in db table, which is not expected and
not required ).
Pass the user input to db view's where clause. ( Any alternative to wizard ? )
Bind the result set to List View
It is definitely a bad idea to morph a database view based on user input, when that view is likely to be accessed by multiple users.
The 'correct' way to do this would be to have a static database view which contains all possible records from the joined tables, and then filter that data for individual users by generating a "domain" and redirecting the user to a tree view with that domain applied.
You can redirect the user by creating a <button type="object"> which calls a function such as the below:
def action_get_results(self, cr, uid, ids, context={}):
# Redirect user to results
my_domain = ['&', ('col1','=','testval'), ('col2','>',33)]
return {
'type': 'ir.actions.act_window',
'name': 'Search Results',
'view_mode': 'tree',
'res_model': 'your.osv_memory.model.name',
'target': 'new', # or 'current'
'context': context,
'domain': my_domain,
}

Rolify and Rails 4 role management

I am trying to implement a role based access system in my rails 4 app, and I want the end user (super_admin) to have the ability to edit role assignments via the UI.
I have achieved some success but can't help feeling that there has to be a better way (Since I'm new to rails). Here is my code:
users_roles_controller.rb
# GET /user_roles/new/:id
def new
#roles = Role.all
end
# POST /user_roles/new/:id
def create
populated = params[:roles][:name].reject(&:empty?)
populated.each do |key|
#user.add_role Role.find(key).name
end
redirect_to users_path
end
And in my Form (HAML and simple_form):
= simple_form_for :roles, :url => create_user_roles_path(#user.id), :method => "post" do |f|
= f.input :name, :collection => #roles, as: :check_boxes
= f.button :submit
I'm struggling with the following:
How do I validate form entries, since there is no model?
Should I be using strong parameters and if so how do I implement on a form without a model
How do I create something similar, but with Current roles already checked? (True role management)
UPDATE
I have looked at using the reform Gem as suggested by the comments. This seems like a great solution. I am however having issues with the implementation on this case.
Let me map it out:
I have 3 tables in the database:
users
users_roles (Mapping Table with 2 Attributes : user_id & role_id {Join Table -> HABTM})
roles
I want to construct a form with all the values in the Roles model as checkboxes.The checkboxes should dictate what values are fed into the users_roles table (With relation to a specific user). What I want reform to do is validate the input of this form. This form will always display all of the values in Roles, but some/all of the boxes might be unchecked.
I have created a form folder in my app and started with the following code:
class UserRoleForm < Reform::Form
property :user__id, on: :user
property :role_id, on: :role
validates :user__id, presence: true
validates :role__id, presence: true
end
Am I going in the right direction?
Thanks for the help.
You need two things to build your form: a user's roles and the possible roles.
If I recall correctly, rolify gives your model associations ad should just let you do something like some_user.roles to return all the roles applied to some_user.
To get possible roles, try Role.all.
To combine both, try
user_roles = some_user.roles.pluck(:name) # suppose this returns ["admin"]
Role.pluck(:name).map { |r| [r, user_roles.include?(r)] }
You now have an array like this that you can use to build your form checkboxes with an each loop.
[["customer", false], ["admin", true], ["editor", false]]
Define your reform object's sync or save method to handle what to do with the submitted input, however you are handling it. You can (SHOULD) make a custom validation to verify if the submitted roles are valid roles.

How to find user group and use of caching in django?

I am new to django/python and working my way through my webapp. I need assistance in solving one of my problems.
In my app, I am planning to assign each user (from auth_user) to one of the group ( from auth_group). Each group can have multiple users. I have entry in auth_group, auth_user and auth_user_groups. Here is my question:
At time of login I want to check that logging user belongs to which group?
I want to keep that group info in session/cache so all pages I can show information about that group only.
If you have any sample code will be great.
Giving support to the very well #trinchet's answer with an example of context_processor code.
Puts inside your webapp a new file called context_processors.py and writes this lines on it:
def user_groups(request):
"""
Add `groups` var to the context with all the
groups the logged in user has, so you can access
in your templates to this var as: {{ groups }}
"""
groups = None
if request.user.is_authenticated():
groups = user.groups
return {'groups': groups}
Finally on your settings.py add 'webbapp.context_processors.user_groups'to TEMPLATE_CONTEXT_PROCESSOR:
TEMPLATE_CONTEXT_PROCESSORS = (
'webbapp.context_processors.user_groups',
)
1) Be user an instance of auth.models.User, you can get all groups the user belong to, through user.groups. If you want to ask at time of login then you should do this in your login view.
2) You can use session or cache approaches to deal with, this is irrelevant, but once you have the group you need to render the pages having this value, i mean, you need to provide the group to the template rendering, to do this I suggest to you using a custom context processor.

Django: structuring a complex relationship intended for use with built-in admin site

I have a fairly complex relationship that I am trying to make work with the Django admin site. I have spent quite some time trying to get this right and it just seems like I am not getting the philosophy behind the Django models.
There is a list of Groups. Each Group has multiple departments. There are also Employees. Each Employee belongs to a single group, but some employees also belong to a single Department within a Group. (Some employees might belong to only a Group but no Department, but no Employee will belong only to a Department).
Here is a simplified version of what I currently have:
class Group:
name = models.CharField(max_length=128)
class Department
group = models.ForeignKey(Group)
class Employee
department = models.ForeignKey(Department)
group = models.ForeignKey(Group)
The problem with this is that the Department select box on the Employees page must display all Departments, because a group has not yet been set. I tried to rectify this by making an EmployeeInline for the GroupAdmin page, but it is not good to have 500+ employees on a non-paginated inline. I must be able to use the models.ModelAdmin page for Employees (unless there is a way to search, sort, collapse and perform actions on inlines).
If I make EmployeeInline an inline of DepartmentAdmin (instead of having a DepartmentInline in GroupAdmin), then things are even worse, because it is not possible to have an Employee that does not belong to a Group.
Given my description of the relationships, am I missing out on some part of the Django ORM that will allow me to structure this relationship the way it 'should be' instead of hacking around and trying to make things come together?
Thanks a lot.
It sounds like what you want is for the Department options to only be those that are ForeignKey'ed to Group? The standard answer is that the admin site is only for simple CRUD operations.
But doing what you're supposed to do is boring.
You could probably overcome this limitation with some ninja javascript and JSON.
So first of all, we need an API that can let us know which departments are available for each group.
def api_departments_from_group(request, group_id):
departments = Department.objects.filter(group__id=group_id)
return json(departments) # Note: serialize, however
Once the API is in place we can add some javascript to change the <option>'s on the department select...
$(function() {
// On page load...
if ($('#id_group')) {
// Trap when the group box is changed
$('#id_group').bind('blur', function() {
$.getJSON('/api/get-departments/' + $('#id_group').val() + '/', function(data) {
// Clear existing options
$('#id_department').children().remove();
// Parse JSON and turn into <option> tags
$.each(data, function(i, item) {
$('#id_department').append('<option>' + item.name + '</option>');
});
});
});
}
});
Save that to admin-ninja.js. Then you can include it on the admin model itself...
class EmployeeAdmin(models.ModelAdmin):
# ...
class Media:
js = ('/media/admin-ninja.js',)
Yeah, so I didn't test a drop of this, but you can get some ideas hopefully. Also, I didn't get fancy with anything, for example the javascript doesn't account for an option already already being selected (and then re-select it).