Newbie on Ruby here, I've successfully build an html form in which users' "project" models can "follow" (or as I've phrased it "pfollow") other "plant" models. The only problem is that the code I've built doesn't work when users try to "pfollow" multiple plants on one form, which forces the user to hit "submit" multiple times for more plants. If the user selects more than one plant from a selection box at a time, my "project" model and my "prelationship" controller choke on the input, which comes as an array rather than the expected single integer (aka, the "pfollower_id" from the plant which is used by the "prelationships" controller for its create action).
How can I teach my app to accept an array of "pfollower_id(s)" and then create multiple plant prelationships?
Here is the error:
undefined method `id' for #<Array:0x26abe70>
app/models/project.rb:35:in `pfollow!'
app/controllers/prelationships_controller.rb:6:in `create'
Request
Parameters:
{"utf8"=>"✓",
"authenticity_token"=>"NKqa1f0M2yPLQDHbRLnxl3SiwBeTus/1q1hpZjD7hgY=",
"prelationship"=>{"pfollower_id"=>["4",
"5"]},
"project_id"=>"90",
"commit"=>"Pfollow"}
My "prelationships" controller:
class PrelationshipsController < ApplicationController
def create
#project = Project.find(params[:project_id])
#plant = Plant.find(params[:prelationship][:pfollower_id])
#project.pfollow!(#plant)
respond_to do |format|
format.html { redirect_to #project }
format.js
end
end
end
And the suspect "pfollow!" method in my model that the trace indicates is also culpable:
def pfollow!(pfollowed)
prelationships.create!(:pfollowed_id => pfollowed.id)
end
The form works fine, so it's just this controller and method that can't handle an array of pfollower_ids.
I'm desperate for help! Any and all direction would be immensely helpful.
Break it down with an each method in your controller :D
params[:prelationship][:pfollower_id].each do |p|
#project.pfollow!( Plant.find(p) )
end
I'm guessing this would work if only one param is returned as well. Not entirely sure.
Related
I'm trying to get a variable from the controller over to the model. Its the value of a checkbox passed up from the View. I cant see it in the controller (0 or 1) and just need to get it to the model. I have seen tons of examples on here but none of them seem to work for me. Maybe its something about the Ruby environment we have setup? Its a Rails 5 environment.
Here is what I have tried so far and the errors it gives in the logs:
Controller:
#vin = Vehicle.create!
#results = #vin.myvin!(params[:vehicle][:vincheck])
Model:
def myvin!(localvin)
logger.info "MADE IT INTO VIN"
end
Error in development.log:
AbstractController::DoubleRenderError (Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action.
I'm not calling render or redirect!! Just trying to call the method in the model.
I have also tried a variation like:
Controller:
Vehicle.myvin(params[:vehicle][:vincheck])
Model:
def self.myvin(localvin)
logger.info "MADE IT INTO VIN"
end
But regardless... I always get that render error in the logs!
I have tried instantiating a new object:
Controller:
Vehicle.new(params[:vehicle][:vincheck])
Model:
attr_reader :localvin
def initialize(localvin)
logger.info "MADE IT INTO VIN"
end
When I do that I get:
500wrong number of arguments (given 0, expected 1)
Short Stack
app/models/vehicle.rb:25:in 'initialize'
app/controllers/vehicles_controller.rb:84:in 'new'
Anyone have any thoughts on how I can get this data over? Maybe it has something to do with the Vehicle Model itself? In the controller it gets instantiated at different places.
For example:
def index
#vehicles = Vehicle.active
end
def create
#this is where I am putting all my code
#not really sure how this is getting created since there was no
#initialize in the code before I added one
#vehicle = Vehicle.new(vehicle_params.merge(
created_by_id: current_user.id,
created_by_name: current_user.name,
deleted: false))
end
thanks!
ruby 2.2.4p230 (2015-12-16 revision 53155) [x86_64-darwin14]
Rails 4.2.5.2
So I've been struggling with this for a couple of days and not sure where to focus. My scenario: My user clicks a button on a source request view at REQUEST/index that creates a new row in my PERFORMANCE destination table and assigns attributes from the source. At the same time I need to flip a boolean attribute in the source table (REQUEST).
My code is below:
REQUEST VIEW
<%= new_performance_path( :attribute_id1 => (source.attribute_id1), :attribute_id2 => (source.attribute_id2), :attribute_id3 => (source.attribute_id3)), class: "btn btn-success" %>
PERFORMANCE CONTROLLER/NEW
def new
#performance = Performance.new(:attribute_id1 => params[:attribute_id1], :attribute_id2 => params[:attribute_id2], :attribute_id3 => params[:attribute_id3] )
#performance.completed = false
#performance.save
if #request.present?
#request.available = false # Need to assign 'false' to this boolean attribute
#request.save # Then save it.
end
redirect_to :performances
end
While the code creates the new row in my PERFORMANCE table I can't figure out the method syntax to effectively flip the REQUEST table attribute to "false". This code with the if statement processes without throwing an erorr. I've originally tried this in the PERFORMANCE CONTROLLER/NEW, without the if statement, which results in an internal server error:
def new
...
#request.available = false
#request.save
...
end
RAILS CONSOLE
Completed 500 Internal Server Error in 29ms (ActiveRecord: 10.1ms)
NoMethodError (undefined method `available=' for nil:NilClass):
app/controllers/performances_controller.rb:26:in `new'
So I think this means I can't call available on request in my performances model. Based on that I tried to add a method to my PERFORMANCE model...
PERFORMANCE.RB
def available
Request.where( :available == true )
end
...but the error persists. My thinking is that I need to grab all rows in the REQUEST table where the boolean value 'available' is equal to true.
Note that these two tables are not directly related to each other. REQUESTS is a join table in my domain model. The app is a music request app, so a USER has_many :REQUESTS, and :PERFORMANCES and I'm using existing rows in the REQUEST table to build new rows in the PERFORMANCE table, but need to edit the row in the request table when a new row is created in performance.
Thanks guys. Still learning being new to Rails and I struggle with referential relationships especially. Thanks for taking a look and helping me out.
#request object is nil in your controller action new. Hence the error
undefined method `available=' for nil:NilClass
Initialize #request should fix the above error.
EDIT 1:
** wrote pseudo code, please read the comments have explaination.
def new
# Load the user objects
#user = User.find(params[:user_id])
# User has many performances, use build method to associated
# user and newly build performance.
#user.performance.build({
:attribute_id1 => params[:attribute_id1],
:attribute_id2 => params[:attribute_id2],
:attribute_id3 => params[:attribute_id3],
:completed => false;
})
# User has one request (This is an assumption).
# if its is has many you can use the above mentioned method to
# create new request method same as performance.
if #performance.save
#user.request.available = false # Need to assign 'false' to this boolean attribute
#user.request.save # Then save it.
end
end
redirect_to :performances
end
#Shishir - After more research on Stack I ended up making my model fatter with a new method & thinning out the controller. This appears to have worked.
PERFORMANCE CONTROLLER/NEW
def new
...
#performance.save
# After #performance is saved we point to new method in performance.rb
#performance.remove_request
# it then comes back for the redirect
redirect_to :performances, notice: "..."
end
performance.rb
def remove_request
#request = Request.find_by(:available => true)
if #request.available == true
#request.available = false
end
#request.save
end
This effectively sets the request to 'false' which displays as expected in my request view. Thanks for your help buddy and forcing me to think about this differently. Cheers, my friend.
I have an ActiveRecord model named Document and have implemented CRUD operations around it. I just have a problem with persisting a Document instance between requests when validation fails (be cause I wanna redirect to another page when this happens).
First, I tried storing the instance in the flash session:
# documents_controller.rb
def new
#document = flash[:document] || Document.new
end
def create
document = Document.new(document_params)
if document.save
return redirect_to documents_path
end
flash[:document] = document
redirect_to new_document_path
end
With the code above, I was expecting that the actual Document instance was stored in the flash session, but instead it became a string which looks somewhat like #<Document:0xad32368>. After searching online for a while, I found out that for some reasons you cannot store ActiveRecord objects in sessions.
There are a lot of suggestions about just storing the object's id in the flash session, but I can't do that because as you can see, the object is not yet stored in the database.
Next, I tried reconstructing the Document instance after the redirect, taking advantage of the instance's attributes method (which returns a serializeable hash that can be stored in the session):
# documents_controller.rb
def new
#document = Document.new(flash[:document_hash] || {})
end
def create
...
flash[:document_attributes] = document.attributes
redirect_to new_document_path
end
This almost solved the problem, except for the part in which the validation errors (document.errors) are not preserved. Also, if this is used to persist an instance already stored in the database (in the case of failed validations when updating a Document instance), I'm not sure which between the original attributes and the new attributes will get persisted.
Right now I've already run out ideas to try. Anyone who has a decent solution for this?
EDIT:
You might be wondering why I still have to redirect to another page instead of just rendering the new document view template or the new action in the create method. I did so because there are some things in my views that are dependent on the current controller method. For example, I have a tab which needs to be highlighted when you are on the document creation page (done by checking if action_name == "new" and controller_name == "documents"). If I do:
def create
...
render action: "new"
end
the tab will not get highlighted because action_name will now be create. I also can't just add additional condition to highlight the tab if action_name == "create" because documents can also be created from the the index page (documents_path). Documents can also be updated from the index page (documents_path) or from the detail page (document_path(document)), and if validation fails in the update method, I'd like to redirect to the previous page.
If I really need to fake persisting something between requests (all of the variables that you set are lost between requests), I will ususally put the relevant attributes into hidden fields in the new form.
In your case, this is overkill. In your code, you are redirecting, which causes a new request:
def create
document = Document.new(document_params)
if document.save
return redirect_to documents_path
end
flash[:document] = document
redirect_to new_document_path
end
You can easily render the output of another action, instead of redirecting, by using render action: 'action_to_render'. So in your example, this would probably be:
def create
#document = Document.new(document_params)
if #document.save
render action: 'index'
else
render action: 'new'
end
end
Which can be simplified to:
def create
#document = Document.new(document_params)
action_to_render = #document.save ? 'index' : 'new'
render action_to_render
end
If you need extra logic from the action, you can refactor the logic to a method called from both actions, or simply call the other action from the current one.
It is fine once in a while, but I would caution that having to jerk around with the rendering too much is usually indicative of poor architecture.
Edit:
An additional option, given the newly highlighted constraints, could be to make the new and create methods the same. Remove the new action and routes, and make create answer for GET and PATCH requests. The action might look something like:
def create
#document = Document.new(document_params)
request.patch? && #document.save && redirect_to( documents_path )
end
I actually use something very similar to this for almost all of my controllers, as it tends to DRY things significantly (as you can remove the extra probably identical view, as well)
Another option would be to just use an instance variable to keep track of the active tab in this instance, and make the rest of the code a lot cleaner.
SOLVED
I was able to make a workaround for it using ActiveSupport::Cache::Store (as suggested by #AntiFun). First I created a fake_flash method which acts closely like the flash sessions except that it uses the cache to store the data, and it looks like this:
def fake_flash(key, value)
if value
Rails.cache.write key, value
else
object = Rails.cache.read key
Rails.cache.delete key
object
end
end
And then I just used it like the flash session.
# documents_controller.rb
def new
...
#document = fake_flash[:document] || Document.new
...
end
def create
document = Document.new document_params
...
# if validation fails
fake_flash :document, document
redirect_to new_document_page
end
I have a couple of objects, foo, bar and user.
I have a form for creating a new foo object, which uses simple_fields_for and accepts_nested_attributes_for to create a new child bar object at the same time.
Now, I want to set the current_user as the author attribute for the new bar, but I can't figure out how best to do this. (still new to Rails.)
I have tried the following in my create method within the foo controller:
def create
#foo = Foo.build(params[:foo])
#foo.bars.find(:first).author = current_user
However when I run this I get an exception.
undefined method `author=' for nil:NilClass
Can anyone offer any advice on how best to go about this?
You likely need to build the Bar object under #foo. ie
def new
#foo = Foo.new
#foo.bars.build
end
def create
#foo = Foo.new(params[:foo])
#foo.bars.first.author = current_user
if #foo.save
redirect_to #foo
else
render action: "new"
end
end
A good resource for nested forms: http://railscasts.com/episodes/196-nested-model-form-part-1?view=asciicast
Provided you set accepts_nested_attributes_for in your model and correctly used fields_for in your form, params[:foo] will contain an element called bar_attributes that will contain the data entered on the form about the bar object. Look through the railcast I linked to for more info.
I have a resource in my project that collects some information from a user. Basically it's a form that they fill out before they can access another area of the site. It then sets a cookie for a week, but if they come back it will look up their previous entry and keep their preferences tied to them (and will update any details as long as the email address matches).
Currently I have a Applicants controller that looks like this:
class ApplicantsController < ApplicationController
...
def create
#applicant = Applicant.find_or_initialize_by_email(params[:applicant])
if #applicant.new_record? ? #applicant.save : #applicant.update_attributes(params[:applicant])
set_cookie_and_redirect
else
render 'new'
end
end
def update
if #applicant.update_attributes(params[:applicant])
set_cookie_and_redirect
else
render 'new'
end
end
end
The set_cookie_and_redirect is a private method that just sets some cookies and redirects the user to a page. The code works, but it just feels dirty. It's essentially updating a record within the create method under the condition that it's not a new record. I'm also forced to have an update method in case an existing record comes back with a validation error--the form helper will then switch the form over to sending to the update method.
So to my point... is there a more appropriate way to push the update_attributes call in the create method to the update method? Or better put, is there a better way to respect the RESTful methods in isolating the create and update functionality?
UPDATE: I wanted to be a little more specific too. If the user has filled this form out before it will set a cookie so they don't have to fill it out again for seven days. However after seven days the cookie is expired and they see the form again. The controller doesn't know if the user is new or existing until they add user input into the form which is then compared based on the email address.
Thanks in advance! I definitely look forward to anyone's thoughts on this.
The create method should only create, and the update method should only update. Let Rails decide which is going to happen based on what is inside of #applicant when the form is rendered - It essentially does what you're doing: Checks if the record is new or not, and sends it to update/create accordingly. Example:
def applicant
#applicant = Applicant.find_or_initialize_by_email(cookies[:email])
# renders applicant.html.erb form
end
<%= form_for #applicant do |f| %>
# ... fields ...
<% end %>
def create
#applicant = Applicant.new(params[:applicant])
#applicant.save
# .. etc.
end
def update
#applicant = Applicant.find_by_email(cookies[:email])
#applicant.update_attributes(params[:applicant])
# ... etc.
end
Rails will send the request to the correct action based on the new_record? status of the Applicant object.