Rails: How can we detect which parameters is updated in CRUD controller - ruby-on-rails

I was just wondering: How can I identify which parameters is being updated in a classic CRUD controller?
I would like to redirect to different path depending on which parameters is being changed.
For example, if I have a Person with 3 attributes id name and firstname
In my controller:
def update
#person = Person.find(params[:id])
respond_to do |format|
if #person.update_attributes(params[:person])
#...
else
#...
end
end
end
How can I detect that name is being changed, and not firstname
Would something like !params[:name].nil? work?
Thanks for your help!

so you could check the params like you said. Something like if !params.[:name].nil? ...
OR
You could use the _changed? methods
#person.attributes = params[:person]
if #person.name_changed?
next_path = name_changed_path
else
...
end
#person.save
redirect_to next_path

Related

Dynamic Strong params for require - Rails

I have an update method right now that will not work for all situations. It is hard coded in the strong params like this params.require(:integration_webhook).permit(:filters) that all fine right now but sometimes it may be integration_webhook and other times it needs to be integration_slack. Basically, is there a way that I don't need to hardcode the require in the strong params? I'll show my code for clarity.
Update Method:
def update
#integration = current_account.integrations.find(params[:id])
attrs = params.require(:integration_webhook).permit(:filters)
if #integration.update_attributes(attrs)
flash[:success] = "Filters added"
redirect_to account_integrations_path
else
render :filters
end
end
As you can see it's a standard update method. But I need the integration_webhook params to be dynamic. I'm wondering if there is a model method I could call to strip away the integration_webhook part?
Not totally sure how dynamic this needs to be, but assuming that we are either getting an integratino_webhook or a integration_slack.
def update
#integration = current_account.integrations.find(params[:id])
if #integration.update_attributes(update_params)
# ...
else
# ...
end
end
private
def update_params
params.require(:integration_webhook).permit(:filters) if params.has_key?(:integration_webhook)
params.require(:integration_slack).permit(:filters) if params.has_key?(:integration_slack)
end
Checkout Strong parameters require multiple if this didn't answer your question.
Edit
For more dynamic requiring:
def update_params
[:integration_webhook, :integration_slack].each do |model|
return params.require(model).permit(:filters) if params.has_key?(model)
end
end
Off the top of my head something like this should work. The naming convention isn't the best but it the structure will allow you to just add to the list if you need to.
def update
#integration = current_account.integrations.find(params[:id])
if #integration.update_attributes(webhook_and_slack_params)
flash[:success] = "Filters added"
redirect_to account_integrations_path
else
render :filters
end
end
def webhook_and_slack_params
[:integration_webhook, :integration_slack].each do |the_params|
if(params.has_key?(the_params))
params.require(the_params).permit(:filters)
end
end

Duplicating a record with a Paperclip attachment

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.

How to use request.env in before_filter?

I want to check some field before save and change it into default browser language.
I want to use before_save filter:
def update
#website = Website.find(params[:id])
if #website.language == "Automatic (by user's browser language)"
#website.language = full_language(request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first)
end
respond_to do |format|
if #website.update_attributes(params[:website])
format.html { redirect_to #website,
notice: 'Note: code has been updated. Please replace the code you have on your website with the code below. Only then changes will take effect.'}
format.js
end
end
end
I need to check:
if #website.language == "Automatic (by user's browser language)"
#website.language = full_language(request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first)
end
And it works in create action, but not in update.
How can I do this ?
if you want to something update after create action but not update action then see example
# write in your model
after_create :xyz
def xyz
...
...
end
above method xyz call after create action. when update call then it will not call.
Before save is used in model and request is usually not available in model.
However if you really want to do it there, check: http://m.onkey.org/how-to-access-session-cookies-params-request-in-model for more detail on how to do it.
-- edit --
There are several way to do it.
First one to cross my mind is that you add: attr_accessor :request_language to model, then pass request.env['HTTP_ACCEPT_LANGUAGE'] from controller to model:
if #website.update_attributes(params[:website])
#website.request_language = request.env['HTTP_ACCEPT_LANGUAGE']
# ...
and now you can proceed like you did before with few modifications:
def auto_language
if self.language == "Automatic (by user's browser language)"
self.language = full_language(self.request_language.scan(/^[a-z]{2}/).first)
end
end
Second way I can think of is to use before/after filters in controller to influence params before they are passed to model.
...
request object isn't available in models. You should do those tweaks on the controller layer.
def action
if #model.language == "Automatic (by user's browser language)"
#model.language = full_language(request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first)
end
if #model.save
...
else
...
end
end
private
def full_language
...
end
You need to change the value of :language in the params hash, since that's what you're ultimately passing to #website.update_attributes. I would also suggest moving that conditional into the model layer to make your controller code more readable:
# app/controllers/websites_controller.rb
def update
#website = Website.find(params[:id])
if #website.language_automatic?
params[:website][:language] = full_language(request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first)
end
respond_to do |format|
if #website.update_attributes(params[:website])
format.html { redirect_to #website,
notice: 'Note: code has been updated. Please replace the code you have on your website with the code below. Only then changes will take effect.'}
format.js
end
end
end
# app/models/website.rb
def language_automatic?
language == "Automatic (by user's browser language)"
end

Passing parameters for nested relationship

I am having trouble passing parameters
My application that is setup like this:
Fact belongs_to Source
Source has_many Facts
Source is nested under User in routes
I am using the Facts form to create the Source data. So I have getter and setter methods in the Facts model like this:
def source_name
source.try(:name)
end
def source_name=(name)
self.source = source.find_or_create_by_name(name) if name.present?
end
This is working great, but it is not setting the user_id for the parent User attribute. As a result, sources are created, but they are not associated with the User.
I have a hidden field with user_id in the form, but the user_id is still being set. What is the easiest way to pass and save the user_id so the nested relationship is set?
Here is the create method for the Source controller:
def create
#user = User.find(params[:user_id])
#source = #user.source.build(params[:source])
...
end
I think the problem is that you are creating source directly from the setter method in the Fact model. Unless you establish the chain by using something like build in the FactController, the user_id will not be set. What you are doing in SourceController needs to be done in the FactsController too. Also, it seems that the ids are set only for the immediate parent when you use the build command. You can try something as below:
def create
#source = current_user.sources.find_or_create_by_name(params["source_name"])
#fact = #source.facts.build(:user_id => #source.user_id)
....
end
Hope that helps.
If your user has a single Source, try the following as your create() method:
def create
#user = User.find params[:user_id]
#user.source = Source.new params[:source]
if #user.save
redirect_to #user, :flash => { :success => "Source updated!" }
else
flash[:error] = "Failed to update the source!"
render :action => "new"
end
end
Creating the Source as an attribute on the User object and then saving the User object should automatically associate the Source with the User.

How can I update my method using an alternative update method in ruby on rails?

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"

Resources