I'm fairly new to Ruby on Rails and within my app the user is able to create a 'build' record that will only be saved if the entire record is unique. If a user tries to create an existing 'build' / record and the validation fails, I need to be able to redirect that user to the existing record.
As I have stated, I am a novice and made a valiant attempt at using the parameters passed to my create action as so:
def create
#build = Build.new(params[:build])
if #build.save
redirect_to :action => 'view', :id => #build.id
else
#bexist = Build.find(params[:build])
redirect_to :action => 'view', :id => #bexist.id
end
end
Clearly this isn't correct... I also tried to look into callbacks with after_validation, but wasn't sure how to access or even store the existing record's id. Anyone have any suggestions?
You need the attribute/value hash to be passed as the :conditions option, and you need to specify :first, :last, or :all as the first argument.
#bexist = Build.find(:first, :conditions => params[:build])
Alternatively, you can use the #first method instead of using #find with a :first argument.
#bexist = Build.first(:conditions => params[:build])
In Rails 3, you have yet another option...
#bexist = Build.where(params[:build]).first
Related
i've started porting one of my apps from rails 3.x to rails 4.x....
when i start the app in development, i receive a route definition related error:
=> Booting WEBrick
=> Rails 4.2.5 application starting in development on http://localhost:3000
=> Run `rails server -h` for more startup options
=> Ctrl-C to shutdown server
Exiting
/home/francesco/.rvm/gems/ruby-2.2.2#Best-i-gest_v2/gems/actionpack-4.2.5/lib/action_dispatch/routing/route_set.rb:549:in `add_route': Invalid route name, already in use: 'app_settings' (ArgumentError)
You may have defined two routes with the same name using the `:as` option, or you may be overriding a route already defined by a resource with the same naming. For the latter, you can restrict the routes created with `resources` as explained here:
http://guides.rubyonrails.org/routing.html#restricting-the-routes-created
.....
here part of my routes.rb file containing the routes marked as double defined:
.....
get 'app_settings' => 'admin/app_settings#index', :as => 'app_settings'
put 'app_settings' => 'admin/app_settings#index', :as => 'app_settings'
post 'app_settings' => 'admin/app_settings#index', :as => 'app_settings'
post 'app_settings/upload' => 'admin/app_settings#upload_logo', :as => 'app_settings/upload'
.....
i've defined these routes because i'm going to manage all actions related to application setting using the 'index' action only (the app has a single db record storing all settings, this record is automatically created at the first time the user will load page, then updated when it saves) as you can see here:
# only one record here! it will store all the application settings
def index
# manages all the controller actions inside the index...
if request.get?
# this is a get request... returns the first settings record or a new one if none exists!
#app_settings = !AppSettings.all.first.nil? ? AppSettings.all.first : AppSettings.new
elsif request.post?
# this is a post request, the settings record will be created
#app_settings = AppSettings.new(params[:app_settings])
#app_settings.save
elsif request.put?
# this will update the existing app_settings record
#app_settings = AppSettings.find_by_id(params[:app_settings][:id].to_i)
#app_settings.update_attributes(params[:app_settings])
end
# renders the index page
render "index"
end
i'm looking for a way to correct the routes.rb file (keeping my controller and view as is!!) or an alternative way to manage this issue!!
waiting your suggestions,
many thanks in advance for your time,
francesco
Seems like you have way too much logic in one method. I'm fairly sure even in rails 3 that wasn't acceptable.
What about using rest route ( resources as described in the rails routing guide )
resources :app_settings, only: [:index, :update, :create]
This will create three routes for index (geT), update (patch), create (post).
Your controller will now look like this:
def index
#app_settings = !AppSettings.all.first.nil? ? AppSettings.all.first : AppSettings.new
end
def create
#app_settings = AppSettings.new(params[:app_settings])
#app_settings.save
end
def update
#app_settings = AppSettings.find_by_id(params[:app_settings][:id].to_i)
#app_settings.update_attributes(params[:app_settings])
end
There will also be no need to use render 'index' ... rails will automatically look for /app/app_settings/index.html.erb
ok guys,
i've solved...
many thanks to Alexandre and Ryan who guided me in solving this issue.....
well,
Alexandre sure you're right!! too much logic inside a single action so...
here is the new version of my files:
routes.rb:
....
post 'app_settings/upload' => 'admin/app_settings#upload_logo', :as => 'app_settings/upload'
# site admin area
namespace :admin do
resources :app_settings, only: [:index, :update, :create]
resources :users
......
end
.....
as you can see, i've inserted app_settings route inside the admin namespace ..
app_settings_controller.rb:
# app_settings security settings - used for declarative authorization
filter_access_to [:index, :create, :update], :require => :manage
filter_access_to :upload_logo, :require => :manage
# app_settings index method (this replaces the show method so all actions will be managed in the index view page)
def index
# this is a get request... returns the first settings record or a new one if none exists!
#app_settings = !AppSettings.first.nil? ? AppSettings.first : AppSettings.new(id: 1)
#app_settings.save
end
# app_settings create method
def create
# this is a post request, the settings record will be created
#app_settings = AppSettings.new(app_settings_params)
#app_settings.save
# renders the index page
render "index"
end
# app_settings update method
def update
# this will update the existing app_settings record
#app_settings = AppSettings.find_by_id(params[:app_settings][:id].to_i)
#app_settings.update_attributes(app_settings_params)
# renders the index page
render "index"
end
......
as you can see, i've implemented your solution keeping the index view as only view used by the app, just for not changing too much inside the app....
i've fixed the declarative_authorization section accordingly with the modifications you've suggested and simplified the creation of the new record as Ryan suggested but adding a little extra (i've specified the record id and then saved the record itself) all to prevent another issue in my view where the form_for is declared as follows:
<!-- application settings edit form -->
<%= form_for [:admin, #app_settings] do |app_sett| %>
.....
.....
<% end %>
so many thanks again to you all!!
hoping to continue learning from you all the times i read a page of this site!!
regards,
francesco
Okey i just dont understand what can be wrong here
i have this app where users are approved by and admin and this was working fine until a few days ago
in my view i have a link that calls my user controller
<%= link_to 'Approve', active_user_path(user), :method => :put %>
here is my custum route for that link
match "users/:id/activate" => "users#activate", :as => "active_user"
now in my user controller i have this activate method
def activate
#user = User.find(params[:id])
puts #user.name #the correct name is displayed
puts #user.is_approved.inspect.to_i #:is_approved is 0
if #user.update_attribute(:is_approved, 1)
puts #user.is_approved.inspect # :is_approved is 1
#user.activate_user
puts #user.is_approved.inspect # :is_approved is 1
#user.save!
redirect_to "/users?is_approved=0"
else
render "/" # dosn't matter
end
end
I try to save 3 times here (update, activate_user, save!) but still the value will not be saved, the users is_approved field is still 0, how is that possible ?
here is my model method
def activate_user
self.is_approved = 1
self.save
end
btw i can update strings with this method but not integers (true and false dosnt work either)
in my model i have is_approved as both attr_accessible and attr_accessor
The solution
Well this is awkward but so it happens that in my user model i had attr_accessor :approved this resulted in that the model never went to the database to update the :approved column BUT instead it updated the local variable :approved so next time when i looked at the column then of course the :approved value had not changed
tldr?
if you have attr_accessor in your model with the same name as the column your trying to update => remove it
Never use attr_accessor on an attribute which is backed by a database column - the accessor generated by attr_accessor will mask the value stored in the database
update_attribute actually does more than just updating a single column:
Validation is skipped.
Callbacks are invoked.
updated_at/updated_on column is updated if that column is available.
Updates all the attributes that are dirty in this object.
Are there any callbacks in your User model?
Make sure the column is not being updated somewhere in a callback.
Scenario:
I would like to have a url path where you could look someone up by an :id or :name:.
For user/5 or /user/tom would all point to the same user
routes.rb
controller 'user' do
get 'user/:id'
get 'user/:name'
end
test/routes/user_routes.rb
test "/user/:id" do
assert_routing "/user/5", :controller => "user", :action => "find_by_id", :id=>"5"
end
test "/user/:name" do
assert_routing "/user/tom", :controller => "user", :action => "find_by_name", :name=>"tom"
end
I am not exactly sure if this is the right design decision with URL paths.
Looking for guidance
I don't think what you are doing is going to work, because the routes you gave are ambiguous, because rails routes can't say that url like user/15 has name or id.
Of course there is a way to do this, by specifying regular expression. Since id will always be a number, we can have regular expression check for it
controller 'user' do
get 'user/:id', :id => /\d+/
get 'user/:name', :name => /[A-Za-z][\w\s]+/
end
The above statements put a constraint of regular expression. In your controller you can check like
if params[:id]
# Get by id
else
# Get by name
You can also do this by passsing get parameters and handling it in controller in the same way.
Thanks
I am trying to replace user profile views of the sort
/users/1
with
/username
I'm aware this means I'll need to check for collisions of various kinds. I've consulted the following similar SO questions:
Ruby on rails routing matching username
customize rails url with username
Routing in Rails making the Username an URL:
routing error with :username in url
Here are the various failed routes.rb route definitions I've tried, and associated errors:
match "/:username" => "users#show", via: "get"
Here's the error:
ActiveRecord::RecordNotFound in UsersController#show
Couldn't find User without an ID
app/controllers/users_controller.rb:7:in `show'
Here is my corresponding users_controller:
6 def show
7 #user = User.find(params[:id])
8 end
match "/:username" => 'users#show', :as => :profile
Same error as above.
match "/:username", :controller => "users/:id", :action => 'show'
Routing Error
uninitialized constant Users
Try running rake routes for more information on available routes.
match '/:username', :controller => 'users', :action => 'show'
Same error as 1.
match '/:username', to: 'users/:id', via: 'show'
Server does not start.
match "/:username" => redirect("/users/:id")
Error:
ActiveRecord::RecordNotFound in UsersController#show
Couldn't find User with id=:id
Any idea why my routing is not working the same way that everyone else who asks this question's is?
Update
Just to take this issue out of the comments and put it in the question more cleanly. After making the change by #Ryan Bigg below, I had a routing problem in my redirect to profile when a new one is created. Here's my create code:
def create
#user = User.new(params[:user])
if #user.save
session[:user_id] = #user.id
flash[:success] = "Thank you for signing up."
redirect_to ('/'+#user.username)
#redirect_to #user, notice: "Thank you for signing up!"
else
render "new"
end
end
And here is my user.rb
def to_param
self.username
#username
end
However, the commented out redirect, which I think should work with the to_param update, doesn't work, while the ugly hackish one above it does. Why is the to_param overwrite, which worked for other people, not working on my app? My #update and #edit methods are also not working, as their redirects go to "users/1/edit" instead of "username/edit" if overwriting to_param doesn't take care of this.
The first one is correct, but isn't working because you're still attempting to do something like this inside your controller:
User.find(params[:username])
When you should instead be doing this:
User.find_by_username!(params[:username])
The first one will attempt to find by the primary key of your table, where the second one will, correctly, query on the username field instead.
In addition to the update for to_params, the bottom of the routes file needs the following line:
resources :users, :path => '/'
What I am trying to achieve is something similar to Github's way for routes. E.g. I have a project with the name 'question' results in the URL /hjuskewycz/question. So my goal is to have routes where the first segment is the username and the second the project's name.
I tried a couple of different approaches, this is the one I am stuck with right now:
scope ":username" do
resources :projects, :path => "" do
resources :pictures
end
end
Using
project_path :username => project.owner.username, :id => project.to_param
works as expected. However, it's tedious to always specify the username although it's always the owner's username. I would very much prefer
project_path(:id => project.to_param)
I know about default_url_options and url_for and I digged in the code. However, polymorphic_url doesn't use default_url_options.
I tried in routes.rb:
resources :projects, :path => "", :defaults => {:username => Proc.new { "just_testing" }}
since you can use a proc for constrains, but haven't got it working either.
I tried in project.rb
def to_param
"#{owner.username"/#{project.title}"
end
I spent already too much time on this problem and my current approach uses a convenience method to add the :username parameter. Nevertheless, I think using this method all over the place just to add an entry stinks (bad code smell). I wonder if there is a more elegant solution to this problem.
I think you should not make things complicated here, just use something like this:
In Routes.rb
match ':username/:projectname/' => 'projects#show_project' , :as => :show_project
and in project_controller, just define this
def show_project
#user =User.find_by_username(params[:username])
#project =Project.find_by_slug(params[:projectname])
end
Simpler is better, it saves time and easy to understand for others
You want to do something like this in your controller:
before_filter :set_username
def set_username
Rails.application.routes.default_url_options[:username] = #user.name
end