I am working with a Rails application that allows users to create projects. Inside these projects, users can make lists. I am trying to figure out how to allow users to choose their "Default Working Project" from the projects index page. This would then propagate throughout the app, showing only lists associated with the current project. What is the best approach to making something like this.
You can achieve this easily by adding default_working_project_id field to your users table.
Then in your controller index set:
#default_working_project = current_user.default_working_project
In your user model add:
belongs_to :default_working_project, class_name: Project, foreign_key: :default_working_project_id
You can create your own action. In your routes file:
resources :projects do
member do
get 'set_default'
end
end
In your projects_controller:
def set_default
project.find params[:id]
current_user.default_working_project_id = project.id
respond_to do |format|
if current_user.save
format.html { redirect_to projects_path }
else
format.html { render 'index', notice: "your error message" }
end
end
end
In your views just add
link_to 'set default', set_default_project_path(project.id)
UPDATED
To remove current default project id from user:
You can make some methods to achieve this, like:
In your Project model
If you have a relation that project belongs_to user try this.
def is_a_current_project?
self.id == self.user.default_working_project_id
end
Then create an after_destroy :remove_current_project_relation callback method.
And the method, I recommend to add it inside your private methods:
def remove_current_project_relation
if is_a_current_project?
self.user.default_working_project = nil
end
end
Create a database field in Users table with default_project_id and set it.
On the model set:
def default_project
return projects.find_by_id(default_project_id) if default_project_id
false
end
And then, you can use something like this:
lists = user.default_project ? user.default_project.lists : user.lists
If only one user can see his projects and other users won't be able to see other user's projects, my suggestion is:
Make a boolean value is_default in the projects table. Add it with a migration.
Add :is_default to def project_params in the controller.
In the projects index page use:
<%= render #projects %>
Create file _project.html.erb in views/projects folder, add to it:
<%= form_for project, remote: true do |f| %>
# some project data
<%= f.check_box :is_default, class: 'project_default' %>
<% end %>
In projects_controller:
def update
#project = Project.find(params[:id])
if #project.update_attributes(project_params)
respond_to do |format|
format.html { redirect_to project_page } # this will run when you update project from edit page in form without 'remote: true'
format.js # this will run if you update project with 'remote: true' form
end
end
end
In projects.coffee in assets/javascripts folder:
$('input.project_default').change -> $(this).closest('form').submit()
Create update.js.erb in the views/projects folder, add to it:
$('#edit_project_<%= #project.id %>').replaceWith("<%= j render 'project' %>");
In projects_helper
def current_project
current_user.projects.find_by(is_default: true)
end
Maybe you'll need to change these a little, based on your tasks. This solution will update projects through JavaScript.
Also it would be great to add a method in the Project model, which will make the previous default project not default when the user makes other project default and so on.
When you need to use lists from default project you can use default_project.lists in your views.
Related
I have a template, a user and user_templates. user has many templates through user_templates.
Currently, whenever a new template is created a user_template record is created as well. This works perfectly with creating 1 record.
Template Create Controller:
def create
#template = Template.new(template_params.merge(user: current_user))
if #template.save
save_user_templates(current_user.id, #template.id)
redirect_to dashboard_url
else
render(:new)
end
end
Save User Template function
def save_user_templates(user, template)
UserTemplate.create(user_id: user, template_id: template)
end
Now I want to implement a 'Multiple Select' so I can select multiple users, click save, which would then create a record for each user in the user_template table at once.
My idea is to pass an array from the view to the controller and then loop through that array and save each record before redirecting. Question is: How do I do that?
Figured out a solution after trying various things. Unfortunately, I did not see the reply in time. Here is what I did.
View
.form-group.has-feedback
.text-muted
= f.label 'Which users should see this?'
= f.collection_select :user_templates, User.all, :id, :name, { include_hidden: false }, multiple: 'true'
Controller
def create
#template = Template.new(template_params.merge(user: current_user))
if #template.save
save_user_templates(params[:template][:user_templates], #template.id)
redirect_to dashboard_url
else
render(:new)
end
end
def save_user_templates(users, template)
users.each do |user|
puts user
UserTemplate.create!(user_id: user, template_id: template)
end
end
You could iterate over the array of users and perform the same action on each of them.
def save_user_templates(user_id_array, template)
user_id_array.each { |user_id| UserTemplate.create(user_id: user_id, template_id: template) }
end
I followed the guide in the wiki to use my models from my engine in my main-app with active admin.
The only thing I must change (that doesn't mentioned in the wiki) was this:
if defined?(ActiveAdmin)
ActiveAdmin.register Blog::Category do
end
end
I just added: Blog::.
In my engine "Blog" I added a model named "category" with a name:string attribute. But when I add one in active admin the field name wasn't saved in the database. My request parameters are:
{"utf8"=>"✓", "authenticity_token"=>"PzKDTcoJZ6Sy2tXgw9WSwXiR7aZp81lOtBvfD5Ec3F72H5L7MEMLjlOFgKWQBo2U4n9mPc7AgjcIS3MTIY2nZA==", "category"=>{"name"=>"asdasd"}, "commit"=>"Update Category", "id"=>"1"}
any ideas why it isn't saved in the databse?
When I create a new one, the record is created but without my input.
I ran into the same problem. I was able to get the resources to save properly by overriding the activeadmin controller actions (in e.g. app/admin/category.rb) like this
ActiveAdmin.register Blog::Category do
permit_params :the, :parameters, :for, :your, :resource
controller do
def create
#category = Blog::Category.new permitted_params[:category]
if #category.save
notice = 'Category was successfully created.'
redirect_to admin_blog_category_url(#category), notice: notice
else
render :new
end
end
end
end
I'm not sure exactly what's going on in the default case yet, but my guess is that the activeadmin controller action is not creating the object in the right namespace - that is, it does something like #category = Category.new rather than #category = Blog::Category.new. A similar override appears to be necessary for the :update action as well.
I know it is simple but I can't get my head around a solution.
It is a job board site. Lets say it's functionality similar to this site. When a user fill all required information and click "To next step" or "Preview", another page loads with all filled data. That page is similar to the final page when data is saved.
When user on preview page, it can go forward and submit the page (in this case it will be saved to DB). Or, click back to Edit the job.
I tried the following::
Within _form.html.erb I added a preview button
<%= f.submit "Preview", :name => 'preview' %>
Within JobControllers I altered create method
def create
if params[:preview]
#job = Job.new(jobs_params)
render 'jobs/preview'
else
#job.save
end
end
Created a Preview view /jobs/preview.html.erb
Now I have 2 problems.
1- Within my preview page, I have an edit button like so: <%= link_to "Edit Job", edit_job_path(#job) %>. But I have an error because I can't find #job. Error says: No route matches {:action=>"edit", :controller=>"jobs", :id=>nil} missing required keys: [:id]
SOLUTION Changed like to <%= link_to 'Back to edit', 'javascript:history.go(-1);' %>
2- How I would submit and add to my DB all information on preview page?
Thank you.
Once I've given a similar task. What I've done is to save records, but not to publish. In my index (resource listing) action of relevant controller, I only fetch published records. Also show action prechecks if that record's published attribute is set to true.
What was my model/controllers looked like before
#model
class Book < ActiveRecord::Base
...
scope :active, -> { where(published: true).some_other_queries }
self.active?
(published && some_other_requirements)
end
...
end
#controller
def index
#books = Book.active
...
end
def show
if #book.active?
render 'show'
...
else
...
end
end
First added a secret key for previews.
#model
def secret
#some custom random key generation
# e.g. Digest::MD5.hexdigest("#{id}_#{ENV['RAILS_SECRET']}")
end
Then added preview action to controller
def preview
# i don't check if the record is active.
# also added a security layer, to prevent irrelevant guys to view
# that record
if #book.secret == params[:secret]
render 'show'
else
...
end
end
In dashboard
...
= link_to "Preview", preview_book_path(book, secret: book.secret)
...
then added a member route
#routes
resources :books do
get :preview, on: :member
end
When I have to do something like this what I normally do is create a review table in my app. This table looks just like the table that is going to saving to.
When they press the "Approved" or "Save" button just populate the new table with the proper data.
I like to create a routes to handle this
resources :something do
match 'move_to_something_else' => 'somethings#move_to_something_else', as: :move_to_something_else, via: :all
end
Now on the controller we can do the following:
def move_to_something_else
#something = Something.find(params[:id])
#something_else = SomethingElse.new
#something_else.name = #something.name
....
#something_else.save
redirect_to something_else_path(#something_else)
end
Alternative you could add a state to your table with the default value of 'draft'
# config/routes.rb
resources :something do
match 'published' => 'somethings#published', as: :published, via: :all
end
# Controller
def published
#something = Something.find(params[:id])
#something.state = 'published'
#something.save
redirect_to something_path(#something)
end
If I want to decrement the counter attribute of an object, is it better to add it to the update action, or the destroy action, or is it better to create a new action?
To explain better, let's say you have a Product and a CartItem class, and every time a user adds the same product to her cart, instead of having two copies of the same CartItem object, you increment the amount attribute of the CartItem.
If I want to let users decrement the amount, what should I use? For example, is something like the code below a viable and conventional way of doing this? Or is it better to create a new action?
View:
...
<%= button_to 'Remove One', [cart_item, remove_one: true], method: :patch %>
...
Controller:
def update
if params[:remove_one]
if #cart_item.quantity > 1
#cart_item.decrement!(:quantity)
flash[:notice] = "Removed one of #{#cart_item.product.title}"
redirect_to :back
else
destroy
end
end
return
end
Note: I understand that the title sounds a bit misleading, but I am not looking for a way to decrement an attribute in Rails. What I want to know is, if you have such a requirement, which action do you put that in? How do you decide what goes in which action?
Edit: Guys I really expected something credible like an example from a well-maintained source-code, a high-rated book, etc. Under these circumstances, unfortunately I won't be able to award the bounty to anyone.
I don't think there's a 100% correct answer but from my experience I see two options that are better than your code example:
Have a decrement action on the controller and in the controller you use the decrement_counter method (doc). This helps if you have concurrent updates on the same model (though that's not usually the case for shopping carts).
Have an update method and in the view render a small form:
The view
<%= form_for cart_item do |form| %>
<%= form.hidden_field :quantity, cart_item.quantity-1 %>
<%= form.submit 'Remove One'
<% end %>
In the controller
def update
#cart_item.update(cart_item_params)
#cart_item.destroy if #cart_item.quantity == 0
#in the above
redirect_to :back
end
def cart_item_params
params.require(:cart_item).permit(:quantity)
end
add a dedicated method in CartItem class...such as
##it will return either true or false
def !!update_amount
###use if condition if you need
if self.quantity > 1
self.update_attribute :amount,self.amount+=amount-1
end
end
use it anywhere you want such as:--
def update
if params[:remove_one]
##call the method to get only true or false
if #cart_item.update_amount
flash[:notice] = "Removed one of #{#cart_item.product.title}"
redirect_to :back
else
destroy
end
end
return
end
To me it make sense semantically to have this in a separate method.
You are updating an attribute on a model, which means that you should use the PATCH method, and preferably invoke the update method per rails' conventions, so in my opinon this separate method invocation should be performed without altering the RESTful routes.
before_action :inventory_check, only: :update
def update
if #cart_item.update
flash[:notice] = "Updated your cart."
else
flash[:error] = "Couldn't update your cart."
end
redirect_to :back
end
def destroy
#cart_item.destroy
flash[:notice] = "Item removed from your cart."
redirect_to :back
end
private
def inventory_check
if params[:decrement] && #cart_item.quantity == 1
redirect_to :destroy
end
end
Seems to me that you CartItem object describes the many-to-many relationship between Product and Card. So when you are adding a Product to a Cart, you are just updating (or creating if there isn't one yet) the content of that specific Production-Cart relationship.
In this sense, I would suggest you to just put the decrement in the update method. However, if you want to do some more logic to determine whether this relationship exists or not, you can also create a new method and do all the validations in it.
I've got model Project and model User. I've got belongs_and_has_many in these. But now I need to tell Rails: this specific user belongs to this specific project. How can I do it in Project controller, and how can I call this method from project view? Thank you very much.
in project's*show.html.erb* I ve got:
<select id="user_select" name="user_select" class="input-large">
<% #users.each do |user| %>
<option><%= user.username %></options>
<% end %>
</select>
<!-- button to addfriend method here -->
And I need to call method "addfriend" from here with parameter from selection to associated project with this user :-/
Method addfiend in project controller:
def addfriend
#project = Project.find(params[:id])
#project.users << User.find(params[:user])
respond_to do |format|
format.html { redirect_to project, :notice => 'Added.' }
end
end
This would look something like that in your controller action:
#project = Project.create(:user_id => user_id)
while user_id is your foreign key (something you would probably want to pass from your view).
This code will be written in some controller action, and you would have to define a route for connecting a URL to this action.
Notice that once you call the action that runs this code you can access #project from your view.
You can read about routes here.
You can read about mvc in rails here.
You can read about associations here:
http://guides.rubyonrails.org/association_basics.html
If in model project, you have has_and_belongs_to_many :users, you project object has an implicit collection, users, that can be added to like any other collection, e.g.:
project.users << User.find(:first, :conditions => "name = 'foo'")