I am very new to Rails, I want to be able to insert names into an array attached to my user.
I added an empty array called 'subscribed_tasks' to my 'schema.rb'
And I wondered how I can push data into that array.
My first attempt was:
Having '= link_to "Subscribe", subscribe_task_path, class: "btn btn-default"' in my show.haml and then having this within my route.rb:
resources :tasks do
member do
get :subscribe
end
end
& then adding this to my Tasks_Controller:
def subscribe
#user = current_user
#user.subscribed_tasks << #task.title
redirect_to #task, notice: "Subscribed to "+ #task.title
end
The problems I'm facing are:
How do I pass the task they're subscribing to's parameters to the controller so then the correct data can be pushed into the array. Also, I don't think I'm finding the array correctly.
All in all, what I'm programming is a mess, am I missing something fundamental about Rails and is there an entirely better way to solve this problem?
Related
Bear with me, I am new to posting and Rails so sorry if I mess up phrasing!!
I am working on a Rails app with many similar models. Each view has a _form.html.haml partial that differs in content but contains similar components, such as buttons to submit, delete, etc. Every model has_many_attached photos, and in the form you can add or delete photos. The haml for deleting photos looks like this, where variable is replaced with whatever view _form.html.haml is in:
.gallery#form
- #VARIABLE.photo.each_with_index do |image, index|
.overlay-container
.img-square{ :style => "background-image: url(#{rails_blob_url(photo(#VARIABLE, index))})", :alt => "Photo of #{image}" }
= link_to("Delete", delete_image_attachment_VARIABLE_url(image), method: :delete, class: 'button delete overlay')
To make the delete work on each photo, this code is in each controller:
def delete_image_attachment
#photo = ActiveStorage::Attachment.find(params[:id])
#photo.purge
redirect_back fallback_location: #VARIABLE
flash[:success] = 'Photo was successfully deleted.'
end
And routes.rb has this chunk of code for each model:
resources :VARIABLE do
member do
delete :delete_image_attachment
end
end
However, I have about a dozen models I need to do this on. My goal is to bring the gallery into a new partial, since it will be used in every _form regardless of the other content. However, the delete function (though the same for every controller) is tied to the controller/routes.rb of each model.
There must be some way of DRYing this functionality into a couple files instead of copy-pasting for each model, but my Google searches have not turned up anything. So, any guidance or better Rails convention is greatly appreciated!
If I'm understanding the structure properly, it sounds like you would want to do something like the following:
Create a top-level controller that handles deleting images
This could be used by any page.
This would require the following query parameters:
id the photo's ID to delete by.
redirect where to redirect the user after the action is completed.
Routes
Now there will only be 1 route to handle the controller above.
Create a reusable partial
For rendering a collection of images with a redirect url that allows you to set the following state for the partial:
photos a collection of images to render.
redirect_url this is passed as a query parameter to the centralized delete image controller.
Thoughts & Mocked Examples
This should be able to DRY up your implementation. Currently the only thing I see that couples the view with the deletion is the redirect URL. By abstracting that out and moving that essentially to a parameter for the partial will allow for re-use and flexibility.
You've already identified your coupling via #VARIABLE, here's a quick mock of how I would expect it to end up looking like:
Partial Template
.gallery#form
- #photos.each_with_index do |image, index|
.overlay-container
.img-square{ :style => "background-image: url(#{rails_blob_url(photo(#VARIABLE, index))})", :alt => "Photo of #{image}" }
= link_to("Delete", delete_image_attachment_url(image, redirect: #redirect_url), method: :delete, class: 'button delete overlay')
Ths would require: photos, and redirect_url
So make sure to set #photos and #redirect_url on the consuming controller.
Example with instance properties to access in the template
#photos = GET_PHOTOS_HERE
#redirect_url = 'some-redirect-path'
render partial: 'photos_partial'
Example with locals parameter for the template
photos = GET_PHOTOS_HERE
render partial: 'photos_partial', locals: { photos: photos, redirect: 'some-redirect-path' }`
Note: You may need to change how you access the local variables in the template.
https://guides.rubyonrails.org/layouts_and_rendering.html#passing-local-variables
Controller
def delete_image_attachment
#photo = ActiveStorage::Attachment.find(params[:id])
#photo.purge
redirect_back fallback_location: params[:redirect]
flash[:success] = 'Photo was successfully deleted.'
end
Routes
Here you would only have the single route for deleting any image attachment, and point at the single controller above.
delete 'resources/image_attachment/:id', to: 'resources#delete_image_attachment'
Note: Replace "resources" with whatever your controller name is, or the scoping/naming you would like.
PS: It's been a while since I've done Rails so I'm not completely certain on the accuracy or your environment.
I'm having this issue where the 'new' and sometimes 'edit' actions of my controllers will throw:
First argument in form cannot contain nil or be empty
While I'm 100% certain that the first argument is not nil nor empty. To hone down on this I'll only ask about the 'new' action since the resource is simply set to Resource.new
permissions/new.haml
= form_for #permission do |f|
= f.label :name, class: 'form-control-label'
= f.text_field :name, class: 'form-control'
permissions_controller.rb
class PermissionsController < ApplicationController
before_action :set_permission, only: [:show, :edit, :update, :destroy]
def new
#permission = Permission.new
end
def show
#modules = Modules.all
end
def index
// handling content with api calls instead
end
...
end
I have had this issue for a long time but I never figured out to successfully reproduce it in my development environment, until now.
I can reproduce this issue by going to the 'show' action of the same controller, then going back to the 'index' action and then going to the 'new' action. At this point the 'new' page does not work anymore until I edit the controller file or restart the server.
After changing the controller (adding a space at the end) or restarting the server it suddenly works again.
This is not the only resource this happens to. Often other resources like User have the same issue. I've had multiple projects that had the same issue that somehow disappeared so I never bothered to investigate more on this.
Ruby version: 2.3.5p376
Rails version: 5.1.4
edit:
I have noticed that I can't reproduce it anymore if I comment out Modules.all.
modules is not a activerecord class, it is a helper class for me to read out the different files in my directories, might this reading out give problems?
modules.rb
class Modules
def self.all
controllers = Dir.glob("#{Rails.root}/app/controllers/**/*.rb").
map{ |e|
if e.include?('/api/')
e.match(/api\/[^\/]*\/[^\/]*.rb/)[0].gsub('.rb', '').gsub('/', '::_').camelcase
else
e.match(/[^\/]*.rb/)[0].gsub('.rb', '').camelcase
end
}
modules = {}
controllers.each do |e|
c = Object.const_get e
modules[e.gsub(':','').gsub('Controller', '').underscore] = get_actions_for c if
!no_permission_needed_for e and c.action_methods.size > 0
end
return modules
end
def self.get_actions_for(controller)
actions = controller.action_methods
grouped_actions.each do |key, value|
actions.delete(key)
end
return actions
end
def self.grouped_actions
{"new" => "create", "edit" => "update"}
end
end
edit:
What I have noticed is that I'm trying to call actions.delete(key) where actions is a Set of strings where key is a String
When I do actions = controller.action_methods.to_a and force the set to an array my issue seems to go away. At least I can't reproduce it anymore. Now I'm wondering if there is something fundamentally going wrong in ruby where deleting something from a Set does weird stuff under the hood.
One way that it would work but is not recommended would be to initialize a Permission instance inside the form:
<%= form_for Permission.new do |f| %>
Check if the view containing your form is actually being rendered by the new action of your PermissionsController.
I think I have figured it out.
What I noticed is that in modules.rb I'm loading the actions inside a controller through controller.action_methods. after this I'm deleting the new and edit actions.
I was under the assumption that I was simply receiving a set of strings containing the action names instead of the actual actions. So it seems logical that if I delete the new and edit actions that my views would default to the new and edit actions in ActionController::Base. Which would mean that #permission is not set and the view throws a well deserved error.
I'm assuming that doing this removes these actions in runtime for the controller. which means that it would of course work again after I restart the server.
So I fixed my issue by doing controller.action_methods.to_a which forces decoupling of the actual actions in the controller. After this I'm free to delete strings in this array that I don't require.
Moral of the story: Watch out with controller.action_methods?
I'm working on a project in rails where I have a database of products, and I'm looking to use forms to manipulate them.
However, I'm having a great deal of difficulty figuring out how to do this.
I tried to use the standard:
<%= form_for(#product) %>
...
<% end %>
to send an entry to my product controller, but that returns a nil/empty field error.
My method to remove products from inventory is below. I strongly suspect this to be highly incorrect, but I have yet to figure out how to get far enough to even receive an error with that:
def ship
#products.where(#products.code = params[:code]).quantity -= 1
end
If there is any way that anybody could guide me in the right direction, I would be highly grateful. I've been trying to figure this out for a while now.
Try like this, let me know what else do you need.
def ship
#product = Product.find_by(code: params[:code]) # or you can use :code => params[:code], but not an equal sign
#product.quantity -= 1
if #product.save
respond_to do |format|
format.html {}
format.json {}
end
end
end
where returns an array or objects.
You after negating the quantity you should save it or update product on the go
You should respond back or else you'll get template missing error, OR you can redirect to some page
I want to understand how the devise current_user method works because I want to generalize it to other models which would allow code such as current_forum or current_forum_thread.
To be more specific I am trying to implement a chat forum in Rails. I have a page showing all posts (currently none) for a specific discussion thread. That same page has the new post form embedded. The debug(params) shows:
action: show
controller: discussions
forum_id: '1'
id: '1'
discussion: !ruby/object:Discussion
attributes:
id: 1
title: first discussion (thread)
forum_id: 1
So the create method in the posts controller should know what the discussion id is. Yet this code in the controller does not work.
1. #discussion = Discussion.find(params[:id])
2. #post = #discussion.posts.new(params[:post])
3. if #post.save
4. flash[:success] = "Discussion post created!"
5. redirect_to '#'
6. else
7. render '#'
8. end
Line 1. raises the error:
Couldn't find Discussion without an ID
Also, on inspection it turns out that the #discussion variable is always NIL.
I think it's more of a helper function, devise's way is to get the ID through the session, but you could do the same through the params hash,
i.e
module ApplicationHelper
def current_forum_thread
Thread.find(params[:id])
end
end
does that work for you?
I'm putting before_filter :authenticate_user! on top of every controller, and then do something like this:
current_user.posts.new(params)
This also requires relation User has_many :posts
Seems to work (not sure if that's the best way though).
Also, your error seems to mean that your prarms[:id] is nil, so check if it's passing properly. You should be able to see that in the logs.
# Discussions controller - show action
#discussion = Discussion.find(params[:id])
render ...
# Discussion show view
%ul
- #discussion.posts.each do |post|
%li= post.content # to output list of posts
= form_for #discussion.posts.new do |f|
= f.input :content
= f.submit
# form to create new post related to this discussion
# Post controller - create method
#post = Post.new(params[:id])
#post.save!
render ...
Using the current_id with threads seems overly complex for this implementation, as it looks like a pretty simple nested resource.
The post isn't saving, because it couldn't find the discussion. Since you're on the Post controller and not Discussion, you need to be looking for the discussion with
#discussion = Discussion.find(params[:discussion_id])
The :id your searching for it with is from the post's parameters. It didn't find anything because you probably have significantly more posts that discussions. If it did find something it would be finding the wrong thing.
Another things on the check list to make a nested route work, is to get the routes right. Check them with 'rake routes' but it should look like this:
resources #discussions do
resources #posts
end
This will add the routes so your form, which should look something like <%= form_for [#discussion, #post] do |f| %> can post/put to the discussion_posts_path.
Using the current_id like you got into is really scoping, and it's kind of messy, Ryan Bate's has an awesome video on Multi-tenancy with scopes http://railscasts.com/episodes/388-multitenancy-with-scopes
I was curious on how to use arrays in the link_to method in ruby on rails for example:
Controller:
def index
#test = [1,2,3]
end
View:
<%= link_to "test", {:action => 'index'}, :test => #test %>
When looking at the source then, I end up with something to the effect of:
test
My guess is that the array's to_string or something similar is getting called to set the value of test in the html.
My goal is to be able to have a form in which people can submit data on the page, and then once they've submitted the data and return to the page, if they click on the link the data will persist through clicking on the link.
*Ideally I would like to do this without having to pass the parameters in the url.
Thank you.
If you want to keep data you should probably use cookies. They are very easy to use, just assign a value with the following in the action:
cookies[:some_key] = "some value"
and retrieve it with this:
cookies[:some_key] # returns "some value"
However, just to clarify what link_to is doing in your example:
<%= link_to "test", {:action => 'index'}, :test => #test %>
When looking at the source then, I end up with something to the effect of:
test
The reason is that you are passing #test to the third argument in link_to, which is a hash of html attributes, hence why it's turned into one. To have it become an parameter on the link, you need to pass it with the second, eg, {:action => 'index', :text => #test}. As noted above, however, this is not necessarily the best way to tackle this and, in addition, it's usually best to also pass the controller name to link_to or, better yet, use a named route.
If I understand well, you want to keep the datas submitted by the user after they validate the form ?
Well Rails is able to do that without any of your code line needed.
Based on the supposition that you have a route resource "objects"
In your controller :
def edit
#object = Object.find_by_id params[:id]
end
def update
#object = Object.find_by_id params[:id]
if #object.update_attributes params[:object]
# The datas have been successfully saved. You redirect wherever you want to.
else
render :action => 'edit'
end
end
and in your view :
<% form_for #object do |f| %>
<%= text_field :name %>
<% end %>
When the form fails to validate, the "name" text field automatically gets the previous entered data.
If after that you still need to reload your datas, you don't need to add them as a parameter in a link tag.
You get the object in your controller and passes it's datas to the view where you display it.
I would just write a view helper that formats it into a string with good separators, like commas.
That isn't a good way to be passing along information though. Try session variables, cookies, or url-encoded variables instead.
The best match to what you are doing would be url-encoded variables, which will show up in a form similar to this:
test
My guess is that it is using Array#join.
You could try something like
:test => #test.join( ',' )
and then parse the string in your controller. But it is somewhat error prone if the user enters the same character you chose as delimiter.
But, assuming the linked page is also served by Rails, I think the best solution would be to use the flash area to store the results on the server
flash[ :submitted_params ] = params;
and in the controller for the linked page
old_params = flash[ :submitted_params ] || {}