Need some tips on how to achieve.
Normally we would do this for scaffold:
rails g scaffold User email:string password_hash:string password_salt:string (and exit)
However, due to the complexity of what I want to achieve in mind, thus the following:
Admin users, where "Admin" is the namespace
rails g scaffold Admin::User email:string password_hash:string password_salt:string (and etc)
Items, where "Application" is the namespace. Items is a model that will be access by different namespaces.
rails g scaffold Application::Item name:string cost:float quantity:integer
So, obviously "Admin" namespace is used for Administrators to login and they could manage items.
However, I'm creating the rails app in such that I has multiple login for different application such as
Namespaces(again)
- Application1
- Application2
So you would expect the main page of each Application to be below as respectively:
http://my.railsapp.com/application1/index
http://my.railsapp.com/application2/index
However, I want to achieve scaffolding, thus Application::Item can be used in any namespace like Application1::Item.
Example would be in my controller(application_a/items_controller)
#items = Application1::Item.all
And in my views, like rails scaffolding
...
<% #items.each do |item| %>
<tr>
<td><%= item.email %></td>
<td><%= link_to 'Show', item %></td>
<td><%= link_to 'Edit', edit_application1_item_path(item) %></td>
<td><%= link_to 'Destroy', item, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
...
To explain my reasons. I would like Admin to create, and edit any field for Items.
Application1 to edit the cost or quantity
and finally Application2 to update only the quantity.
Ofcourse, its more to just restrictions, but to put it simply is different application utilising on the same data model.
Thanks.
Scaffolding is for most common cases. If it can't meet your need on customization, then don't use it, create models and controllers separately. No need to waste time on digging that.
Related
I have a model Schools and a model PerformanceStats.
PerformanceStat
belongs_to :school
School
has_one :performance_stat
the index page for PerformanceStat shows all 2,000 performance stats, and also the school.name, school.score, and school.city, and I need access to the school.id and school.slug.
Controller:
def index
#performance_stats=PerformanceStat.all
end
My view code:
<tbody>
<% #performance_stats.each do |stat| %>
<% school = School.find(stat.school_id)%>
<tr>
<td><%= link_to school.name, school_path(city: school.city.parameterize.truncate(80, omission: ''), slug: school.slug) %></td>
<td><%= number_with_precision(school.score, precision: 2)%></td>
then the view goes on to display the performance stats.
This view load very slowly....10-20 seconds. How can I speed things up? I've tried PerformanceStats.scoped, and plucking school stats and selecting from an array, but these don't seem to help. Is there a way for me to access the school attributes without finding a School for every PerformanceStat? I believe the School.find bit is slowing things down considerably.
I have indexes on :school_id in PerformanceStat, and :score, :slug in the School model.
UPDATE:
The suggestion in the selected answer to add a cache resulted in this line of code in the index action of the SchoolsController:
fresh_when etag: #performance_stats
The load time dropped to 18ms. This solution works great for me because the content of the index action does not change often. This data gets updated once a year. This link has other suggested cache solutions for data that changes frequently.
PerformanceStat.all is a heavy query if you've a lot of data in this table and it'll be finding school for each performance stat.
What I can understand from your code is that you're facing (N + 1) problem over here.
NOTE: you should not fire queries from your views or helpers and let the controller do all the action.
For instance in your code:
<% #performance_stats.each do |stat| %>
<% school = School.find(stat.school_id)%> <- #THIS IS WRONG & LET THE ASSOCIATIONS DO ALL THE ACTION ON ITS OWN
<tr>
<td><%= link_to school.name, school_path(city: school.city.parameterize.truncate(80, omission: ''), slug: school.slug) %></td>
<td><%= number_with_precision(school.score, precision: 2)%></td>
you can use includes, PerformanceStat.includes(:school) it will fetch all the schools for each PerformanceStat.
your controller code should be:
#performance_stats = PerformanceStat.includes(:school)
instead of : #performance_stats = PerformanceStat.all
and your view code will now be:
<% #performance_stats.each do |stat| %>
<% school = stat.school %> #make sure all stats have a school assigned to them otherwise you can put a check below whether the school is nil or not
<tr>
<td><%= link_to school.name, school_path(city: school.city.parameterize.truncate(80, omission: ''), slug: school.slug) %></td>
<td><%= number_with_precision(school.score, precision: 2)%></td>
Quite a few things here. First of all change your controller method to this one, otherwise you will run into n+1 queries
def index
#performance_stats=PerformanceStat.includes(:school)
end
Since you have eagerly loaded the school, now you can access it directly in your view as
<% stat.school %>
Secondly loading almost 2000 records in one go is not optimal at all, it's gonna take a while to load all records. For this you must add pagination by using following gems
kaminari
will_paginate
Upon soft-deleting a record, I'm unable to call it on the show action on the controller, for it looks for a record matching the record's ID WHERE deleted_at IS NULLL, which is correct given the purpose of the gem, but I'd still like to be able to access it on a sort of a "readonly" state, within the application, in order to allow the user to review the archive and possibly restore it.
How can I work around the deletion scope so that I can access the object again?
UPDATE 1
By #slowjack2k's advice, I can access the soft-deleted records with the following query:
#area = Area.only_deleted.find(params[:id])
A new problem arose afterwards, due to CanCanCan's load_and_authorize_resource: it attempts to call
#area = Area.find(params[:id])
ignoring the only_deleted filter, resulting in error since the selected id is only found where deleted_at is not null (not deleted), and disabling the authorization "fixes" it, so it must be an issue between CanCanCan and Paranoia.
Here's a thread with the exact same issue: https://github.com/rubysherpas/paranoia/issues/356
Here's the new issue thread on StackOverflow: Rails 5 compatibility between Paranoia and CanCanCan, compromised?
I'll update it again with the solution if I find one, thank you.
UPDATE 2
The issue was solved and the solution can be found on the new issue thread I've mentioned above.
You can use YourModel.readonly.find_with_deleted(params[:id]) or YourModel.readonly.with_deleted.find(params[:id])
I've been throw this before and I'll explain my approach to solve this..
Overview:
Giving that I have a Model Called Items, I'll have a page that will display all Soft Deleted Items I'll call the inactive. Using the same template of index action
First you should create a route
resources :items
collection do
get 'inactive'
end
end
Second you should create a controller action...
def inactive
#Items = Items.only_deleted
render action: :index
end
Third, I'll go to ../items/inactive And it'll display the in active or archived Items
You may also after that use...
<%= link_to "Archived Items", inactive_items_path %>
In your views to go to that page
Update
Here I should mention that using the index view to render the inactive Items collection may leave you with broken links.
views/items/index.html.erb
<td><%= link_to 'Show', merchant %></td>
<td><%= link_to 'Edit', edit_merchant_path(merchant) %></td>
<td><%= link_to 'Destroy', merchant, method: :delete, data: { confirm: 'Are you sure?' } %></td>
So that leave you with a choice to make, Whether you choose to group the edit links for the normal Items and put it in a partial then group the inactive links and put it in another partial, Then to render the partial depending on which action is rendering the view.
Or Option #2 is to lose the render action: :index line and make a separate view inactive.html.erb with its links and save your self the headache. Although it would be against the DRY principal.
I get that one should not ping the database in the view... but wondering about the right solution. In one of my views, I need to pull info on an #order, it's child items, and also Amount, another model, based on each child item. Something like this:
<% #order.items.each do |item| %>
<td><%= item.name %></td>
<td><%= Refund.where(item_id:item.id).first.amount %></td>
<td><%= Amount.where(item_id: item.id).first.amount %></td>
<% end %>
For the sake of avoiding the db hits in the view, the only solution I've thought of is to create a huge hash of all the relevant data in the controller, which is then accessed from the view. So it would be something like this:
# controller (writing quickly, code may not be totally right, hopefully you get gist
data = Hash.new
data["items"] = []
#order.items.each do |item|
item_hash = {
"name" => item.name,
"amount" => Amount.where(item_id: item.id).first.amount,
"refund" => Refund.where(item_id:item.id).first.amount
}
data["items"] << item_hash
end
# view code
<% data["items"].each do |item| %>
<td><%= item["name"] %></td>
<td><%= item["refund"] %></td>
<td><%= item["amount"] %></td>
<% end %>
And I know SO hates this type of question... but I really need to know... is that the best solution? Or are there are best practices? The reason I ask is because it seems very clean in the view, but very bulky in the controller, and also it gets quite unwieldy when you have a much more complex set of nested tables, which is what I actually have (i.e., the data hash would be quite funky to put together)
First of I would use associations between item and the 2 other classes, so that you can do
item.refund
item.amount
Instead of Refund.where(...). You could further define methods such as
def refund_amount
refund.amount
end
And similarly for the other one (and hopefully come up with a better name than amount_amount.
This keeps both your view and controller clean but it won't be any faster. So far all of the approaches involve running 2 database queries per item which is the real issue as far as I'm concerned - whether those excess queries happen in the view or the controller is of lesser concern.
However you can avoid this with Active Record's include mechanism:
Item.include(:amount,:refund).where("your conditions here")
Will load the named associations in bulk rather than loaded them one at a time as each item is accessed.
When you use rails generate scaffold admin/user --model-name=User or rails generate scaffold_controller --model-name=User it generates almost everything in a namespaced fashion. You get app/controllers/admin/users_controller.rb with your controller and app/views/admin/users/ filled with your views.
The one thing it doesn't get right is your paths. You have to manually go and replace references to user_path with admin_user_path and the like. This is pretty tedious.
Is there a way to tell Rails to generate the paths to point to your new namespace, rather than the namespace that the model is in?
Using Rails 4.
With rails build-in generators you can't.
See the generator source code to understand why:
<td><%%= link_to 'Show', <%= singular_table_name %> %></td>
<td><%%= link_to 'Edit', edit_<%= singular_table_name %>_path(<%= singular_table_name %>) %></td>
<td><%%= link_to 'Destroy', <%= singular_table_name %>, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
As you can see, it goes with edit_<%= singular_table_name %>_path to generate the edit path, without considering name-spacing. (And haml-rails does the same)
The best thing to do, if you have time and patience for it, would be to fix this on the codebase and propose a PR. That's the main point of open-source after all.
If you go this direction, have a look first at open issues, I haven't dive deep into but it seems that different conversations are going on about that matter. Like https://github.com/rails/rails/pull/13927 or https://github.com/rails/rails/issues/21652
Or you can use existing gems like Beautiful-Scaffold that seem to be supporting namepacing
When I generated a scaffold for a model class named menuitem, it generates a class file for menuitem, but in the database creates menu_item and refers to the class in the views and controller as menu_item. This may be causing me quite a headache as im genereating a newsted show link, but its failing telling me missing method. The rake routes tells me the show route should be menu_menu_item, but when i do:
<td><%= link_to 'Show', menu_menu_item(#menu) %></td>
it doesnt work.
Is that because of the funky two word class name?
You must have generated either menu_item or menuItem or MenuItem. It doesn't know where one word stops and another starts unless you tell it.
Also, for your link_tos, you just need to append _path:
<td><%= link_to 'Show', menu_menu_item_path(#menu) %></td>
Well, actually, that looks a little wrong to me. That looks like you're trying to go to a single item, which I think will require you to specify both the menu and the item:
<td><%= link_to 'Show', menu_menu_item_path(#menu, #menu_item) %></td>
And to all the menu items in a menu:
<td><%= link_to 'Show', menu_menu_items_path(#menu) %></td>
The Pluralizer shows you show things should be named according to the Rails' conventions.