I'm creating an action which duplicates an item and then allows the user to edit it and save it back to the database.
I've written the following method in my controller and it mostly works apart from the Paperclip attachment which won't move across for some reason.
def duplicate
existing_event = Event.find(params[:id])
#event = Event.new(existing_event.attributes)
render action: 'new'
end
I've seen this question where the person is using .dup but I can't seem to get that working in a situation where the user edits the new item before saving.
I also tried using something like #event.image = existing_event.image but that didn't have any effect either.
This is what my create method looks like:
def create
#event = Event.create(event_params)
if #event.save
redirect_to events_path, notice: "Event was successfully created."
else
render action: 'new'
end
end
If it makes any difference I'm using S3 for the image uploads too and it doesn't really matter to me if there are multiple copies of the image up there.
Can anyone help? Thanks!
Passing the attachment params does just that: pass the params.
You need to pass the file itself.
Below you get the idea how to do it, not tested it, but you can play around it and make it work, it shouldn't be that hard.
On new action:
existing_event = Event.find(params[:id])
#event = Event.new(existing_event.attributes)
#event.image = File.open(existing_event.image.path,'rb')
render :action => 'new'
Also:
Check in your create action, you have a slight mistake, calling create and save for the same record - this is redundant. You should call #event=Event.new(event_params) and then if #event.save.
Here's a little snippet I use in an initialiser:
module Paperclip
class HasAttachedFile
def define_with_extensions
define_without_extensions
define_dup_override
end
alias_method_chain :define, :extensions
private
def define_dup_override
name = #name
#klass.send :define_method, "dup" do
copy = super()
self.class.attachment_definitions.each do |name, options|
ivar = "#attachment_#{name}"
copy.instance_variable_set(ivar, nil)
copy.send(name).assign send(name)
end
copy
end
end
end
end
This will assign the files from the old record to the new record programatically without knowing what the actual attachment definitions are.
Related
In my app I want to add a value to a field if it is empty when the record gets created. It should take the name of the file ( attached to the record.
I tried adding below code to the controller, yet that doesn't do it. How can/should these kind of action be done in Rails 5?
def create
#document = Document.new(document_params)
if #document.update(document_params)
unless #document.name.present?
#document.name == #document.file_file_name
end
redirect_to #document
else
render 'new'
end
end
Use self.attribute
Add callback to model before_save :set_field_name and added a method:
def set_field_name
unless self.name.present?
self.name = self.file_file_name
end
end
Forgive me I'm new at Ruby. I am trying to create a site to contain information about a zoo.
I have an enclosure model and an animal model.
This is my code for the create method in animal_controller
def create
if params.has_key?(:enclosure_id)
#enclosure = Enclosure.find(params[:enclosure_id])
#animal = Animal.new(animals_params)
#animal.user_id = current_user.id
if #animal.save
#enclosure.animals.push(#animal)
redirect_to enclosure_path(#enclosure)
else
# ????
end
else
#animal = Animal.new(animal_params)
#animal.user_id = current_user.id
if #animal.save
redirect_to #animal
else
render 'new'
end
end
end
I then have two places where a new animal can be created. One is using a form at localhost:3000/animals/new. The other is using a similar form on the show page of a particular enclosure, so for example at localhost:3000/enclosures/1/
In my code above, I check for the presence of enclosure_id to determine where the call is coming from. If the parameter is found, I add the animal to the enclosure there and then. However, if #animal.save fails, I do not understand how I can return to the localhost:3000/enclosures/id page with the validation error messages being passed. In the case of no enclosure_id, render 'new' takes the user back to the ../animal/new page with error messages passed as well.
Thanks
I don't think it's a good idea to go to an other page, because you will have to serialize errors linked to the model and it's gonna be complicated and ugly.
I think you should render the show page of the enclosure and then display the errors
#....
if #animal.save
#enclosure.animals.push(#animal)
redirect_to enclosure_path(#enclosure)
else
render 'enclosures/show'
end
#....
Edit: it turns out I made a very simple mistake and had a Template that was associated with a LocalTemplate id that no longer existed. If anyone has this problem and thinks that they somehow are unable to unable to associate the id of another model in their update action, make sure that you didn't accidentally delete the parent object causing that id to no longer exist!
The code below, while dramatically simplified did work for me.
I have a Template model in my rails app. It has a method "data" defined in it.
I am able to access this method in the create and show actions with #template.data, however when using the same #template.data in the update action of my controller I get a no method error because I am not showing the correct local template id to it. This line can be found in the model where it reads base_data = YAML.load(local_template.data)
I stored an id of the associated local_template when initially saving a new template, but how can I make sure I reference that id again in the update action so I do not get a no method error?
Here is a simplified version of the Template model and controller
Model:
class Template < ActiveRecord::Base
def data
base_data = YAML.load(local_template.data)
# couldn't pass the correct LocalTemplate here because
# the local_template_id I had in my Template model no
# longer existed. Changing the id to a LocalTemplate
# that did exist fixed the issue.
end
end
Controller:
class TemplatesController < ApplicationController
def index
#business = Business.find(params[:business_id])
#templates = #business.templates.all
end
def new
#business = Business.find(params[:business_id])
#local_templates = LocalTemplate.all
#template = #business.templates.build
end
def create
#business = Business.find(params[:business_id])
#local_templates = LocalTemplate.all
#template = #business.templates.build(template_params)
if #template.save
#template.data #works fine here
redirect_to business_url(#template.business_id)
else
render 'new'
end
end
def show
#business = Business.find(params[:business_id])
#template = #business.templates.find(params[:id])
#template.data #works fine here too
end
def edit
#business = Business.find(params[:business_id])
#local_templates = LocalTemplate.all
#template = #business.templates.find(params[:id])
end
def update
#business = Business.find(params[:business_id])
#template = #business.templates.find(params[:id])
if #template.update_attributes!(pass_template_params)
Api.new.update_template(#template.data.to_json) #this is where I had a problem
redirect_to business_url(#template.business_id)
else
render 'edit'
end
end
end
You are mixing a lot. There is a lot to refactor in your controller...
First of all, your TemplatesController should be about the template resources, but your controller looks more like a BusinessesController. In general your update action for example should look more like:
def update
#template = Template.find params[:id]
#template.attributes = template_params # though this should raise a NoMethodError, because you dind't define it; I'd prefer params[:template] if possible
if #template.save
redirect_to business_url(#template.business_id)
else
#local_templates = LocalTemplate.all
render 'edit'
end
end
Instantiating #business and #local_templates makes non sense, because you don't use it at all. Speed up your responses if you can! :)
Fixed that, there is no need for the overhead of a nested resource in update (as you did).
If saving #template fails for validation reasons, you better should load the business object late by:
#template.business
in your /templates/edit.html.erb partial. Then you also do not need a nested route to your edit action... You see, it cleans up a lot.
As a general guideline you should create as less as possible controller instance variables.
If you cleaned up your controller and views, debugging your data issue will be easier.
I assume:
local_template
in your Template model to be an associated LocalTemplate model object. So it should no issue to call that anywhere if you ensured the referenced object exists:
class Template < ActiveRecord::Base
def data
return if local_template.nil?
YAML.load(local_template.data)
end
end
or validate the existence of the local_template object. or even b
You should confirm #template is not nil, if #template is nil, you can't use data method.
1.9.3-p547 :024 > nil.data
NoMethodError: undefined method `data' for nil:NilClass
from (irb):24
from /Users/tap4fun/.rvm/rubies/ruby-1.9.3-p547/bin/irb:12:in `<main>'
And you should use update_attributes!, it can raise an exception if record is invalid.
You can do like this.
if #template
#template.update_attributes!(template_params)
#template.data
end
Is it possible to update from an action/method other than the update action/method? For example in my users controller I already have an update method for other parts of my users account.
I need a separate one for changing my users password. Is it possible to have something like this:
def another_method_to_update
user = User.authenticate(current_user.email, params[:current_password])
if user.update_attributes(params[:user])
login user
format.js { render :js => "window.location = '#{settings_account_path}'" }
flash[:success] = "Password updated"
else
format.js { render :form_errors }
end
end
Then have my change password form know to use that method to perform the update?
It has 3 fields: current password new password confirm new password
and I use ajax to show the form errors.
Kind regards
Yes; the update action is just a default provided to make REST-based interfaces trivially easy. You will want to make sure you have a POST route in config/routes.rb that references users#another_method_to_update presuming you're doing all this in the UsersController (and on Rails 3) but the basic answer to your question is that model operations (including updating fields) can be done anywhere you have the model available.
There's no tying between what model methods can be called and what controller methods are being invoked.
Why would you want to use another route for this? Stick with the convention, use the default route. If I understood correctly, your page contains a form for password update.
We could write entire code for this in the update method, but this is cleaner and more self-explanatory:
def update
change_password and return if change_password?
# old code of your update method
# ...
end
private
def change_password?
!params[:current_password].nil?
end
def change_password
user = User.authenticate(current_user.email, params[:current_password])
respond_to do |format|
if user.update_attributes(params[:user])
login user
flash[:success] = "Password updated"
format.js { render :js => "window.location = '#{settings_account_path}'" }
else
format.js { render :form_errors }
end
end
end
This is a lot easier to understand for someone who will look at your code since you're still calling update method to update your model which then performs custom action.
I've also fixed your custom method code.
in config/routes.rb:
puts "users/other_update_method"
My 'create' function in my 'Message' controller is something like this:
def create
#message = Message.new(params[:message])
#message2 = Message.new(params[:message])
#message.sender_deleted = false
#message2.sender_deleted = true
if #message2.save
...
else
logger.debug("SAVE DIDN'T WORK")
For whatever reason, message2 cannot be saved, but #message can. I believe this is because you need to save only a variable named #message, but I can't figure out how to get around this. I need to, on this save, save multiple things to the database - is there some other way to do this or am I doing this completely wrong?
Thanks for your help
There's no reason you can't save more than once in an action, though why you'd want to do such a thing is debatable. You'll want to put the saves in a transaction so you only save when both records are valid. save! will raise an exception when the save fails.
def create
#message = Message.new(params[:message].merge(:sender_deleted=>false))
#message2 = Message.new(params[:message].merge(:sender_deleted=>true))
Message.transaction do
#message.save!
#message2.save!
end
redirect_to .... # handle success here
rescue ActiveRecord::RecordNotSaved, ActiveRecord::RecordInvalid
# do what you need to deal with failed save here,
# e.g., set flash, log, etc.
render :action => :new
end
end