I am using:
Rails 2.3.5
Ruby 1.8.7
Windows 7 Home basic 64-bit
I'm trying to use a database I acquired using mysqldump, and create functions ADD, EDIT, and DELETE to go with it. Now, when I'm creating the edit function, and i'm using its primary key (productCode) as a parameter, i get this error:
ActiveRecord::RecordNotFound in PosController#edit
Couldn't find Product without an ID
App Trace:
C:/Ruby187/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:1567:in find_from_ids'
C:/Ruby187/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:616:infind'
C:/Users/Aldrin/Documents/Trabaho!/sites/dbSample/app/controllers/pos_controller.rb:13:in `edit'
here's my code:
def edit
#product = Product.find(params[:ProductCode])
end
def update
#product = product.find(params[:ProductCode])
if session[:user_id]
#log = "Welcome Administrator!"
#logout="logout"
else
#log = "Admin Log in"
#logout=""
end
respond_to do |format|
if #product.update_attributes(params[:product])
flash[:notice] = 'product was successfully updated.'
format.html { redirect_to(#product) }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #product.errors, :status => :unprocessable_entity }
end
end
end
I don't have an :id column in my database.
If productCode is the primary key in the table then you should tell rails to use it instead of id
class Product << ActiveRecord::Base
self.primary_key = 'productCode'
end
That way standard find calls will work, and you won't need to overwrite methods like to_param as rails will already have done it for you
def edit
#product = Product.find(params[:id])
end
def update
#product = Product.find(params[:id])
..............................
end
EDIT
def to_param
"#{product_code}"
end
def edit
#product = Product.find_by_product_code(params[:id])
end
def update
#product = Product.find_by_product_code(params[:id])
..............................
end
Related
I am a newbie in RoR, thus sorry for stupid question :(
I have a Game model, with a code string. There is a welcome/index view in my app with a simple form_to input. I wish to redirect user to a Game with a specific code after he submits the form.
I understand that I should somehow combine a .where method and redirect_to in Welcome_controller, but I just can't figure out how...
Welcome_controller.rb:
class WelcomeController < ApplicationController
def index
end
def redirect
redirect_to ?game with a code that equals :param from input?
end
end
Welcome/index:
<h1>Let's join the game!</h1>
<%= form_tag redirect_path do %>
<%= text_field_tag(:param) %>
<%= submit_tag("Search") %>
<% end %>
routes.rb:
Rails.application.routes.draw do
get 'welcome/index'
resources :games
get 'games/index'
root 'welcome#index'
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
game.rb:
class Game < ApplicationRecord
validates :name, :presence => true
end
games_controller:
PREFACE = ('A'..'Z').to_a << ?_
SUFFIX = ('0'..'9').to_a
PREFACE_SIZE = 2
SUFFIX_SIZE = 3
class GamesController < ApplicationController
before_action :set_game, only: %i[ show edit update destroy ]
# GET /games or /games.json
def index
#games = Game.all
end
# GET /games/1 or /games/1.json
def show
end
# GET /games/new
def new
#game = Game.new
#game.code = gen_name
end
def gen_name
PREFACE.sample(PREFACE_SIZE).join << SUFFIX.sample(SUFFIX_SIZE).join
end
# GET /games/1/edit
def edit
end
# POST /games or /games.json
def create
#game = Game.new(game_params)
respond_to do |format|
if #game.save
format.html { redirect_to game_url(#game), notice: "Game was successfully created." }
format.json { render :show, status: :created, location: #game }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: #game.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /games/1 or /games/1.json
def update
respond_to do |format|
if #game.update(game_params)
format.html { redirect_to game_url(#game), notice: "Game was successfully updated." }
format.json { render :show, status: :ok, location: #game }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: #game.errors, status: :unprocessable_entity }
end
end
end
# DELETE /games/1 or /games/1.json
def destroy
#game.destroy
respond_to do |format|
format.html { redirect_to games_url, notice: "Game was successfully destroyed." }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_game
#game = Game.find(params[:id])
end
# Only allow a list of trusted parameters through.
def game_params
params.require(:game).permit(:code, :name)
end
end
In config/routes.rb you have defined resources :games, which creates default paths for CRUD actions. For the show action, which you are trying to get here, it would lead to /games/:id and the helper method would be game_path. You can also check this by running rails routes -c games command in the app directory. It should return all paths for games_controller
In the before_action callback for GamesController#show action, you are finding a Game object using Game.find(params[:id]). :id parameter is what you need to pass to the path helper that I mentioned earlier for the action to fire properly, so the path to a specific game would look like game_path(id: game.id). This will then automatically get converted to params. Alternatively, you can just pass the game object to the path helper and it will do the job for you like this: game_path(game)
Now in the WelcomeController#redirect action, you get the game code in params from the form submit. You need to first find the game for the submitted code like this:
game = Game.find_by(code: params[:param])
This should work if the code is unique for each game. Now that you have the correct game record, all you need is to redirect to the path that I've mentioned eariler:
redirect_to game_path(game)
I have a Ruby on Rails application where you can create 'posts'. I started of by using the scaffold generator to give generate the title which is a string and the body which is the content.
Each 'post' has a url of the id, for example /1, /2, /3, etc.
Is there a way to change it to generater a string of random characters and numbers, for example /49slc8sd, /l9scs8dl, etc?
Here is what I have for the posts_controller.rb
class PostsController < ApplicationController
# GET /posts
# GET /posts.json
def index
#posts = Post.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #posts }
end
end
# GET /posts/1
# GET /posts/1.json
def show
#post = Post.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #post }
end
end
# GET /posts/new
# GET /posts/new.json
def new
#post = Post.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #post }
end
end
# GET /posts/1/edit
def edit
#post = Post.find(params[:id])
end
# POST /posts
# POST /posts.json
def create
#post = Post.new(params[:post])
respond_to do |format|
if #post.save
format.html { redirect_to #post, notice: 'Post was successfully created.' }
format.json { render json: #post, status: :created, location: #post }
else
format.html { render action: "new" }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
# PUT /posts/1
# PUT /posts/1.json
def update
#post = Post.find(params[:id])
respond_to do |format|
if #post.update_attributes(params[:post])
format.html { redirect_to #post, notice: 'Post was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
# DELETE /posts/1
# DELETE /posts/1.json
def destroy
#post = Post.find(params[:id])
#post.destroy
respond_to do |format|
format.html { redirect_to posts_url }
format.json { head :no_content }
end
end
end
And here is what I have in the post.rb model
class Document < ActiveRecord::Base
attr_accessible :content, :name
end
If you want your models not to have their primary key id in a predictable sequence, you can generate the id based on uuid or guid with the help of something like http://codesnipers.com/?q=using-uuid-guid-as-primary-key-in-rails
However you can also route based on any other property which uniquely identifies the resource which is the recommended approach if in case you dont want to expose the database identifiers in your routes
person/:person_random_token, :controller => :persons, :action => :show #adding this in your route file directing to the controller where you can use params[:person_random_token] to uniquely identify your person object in Persons model
In your controller's action you can say
Person.find_by_random_token(params[:person_random_token]) #assuming random_token is your column name
to get the Person object
If you would like to obfuscate numerical ID's , you could take a look at this interesting discusion .
You should also be aware of the to_param method for ActiveRecord::Base objects.
Basically, Rails calls this method on your objects to know what to put in the URL and params[:id]. By default it is just the primary key of the record in the database. Say you override it as such:
class Post < ActiveRecord::Base
def to_param
return id*100
end
def self.find_by_new_id(n)
return self.find(n/100) # really you'd want to handle strings and integers
end
end
The first record in your database would have url /posts/100.
In your controller, to retrieve the object you just do
#post = Post.find_by_new_id(params[:id])
(Of course you could override the default find method as well, but that is probably frowned upon.) Basically the to_param method transforms your id and the new finder undoes it. Usually you just point to another database column that has been automatically populated via a hook when the record is created. This is what is described in the link posted by Qumara otBurgas.
It's not clear what you are asking here. The path to the action specified in the routes does not require the id passed to be of a certain format. You can pass non-numeric ids if you want and within your action use the id however you'd like. Maybe if you supplied more info about the routes and actions we could understand what you are asking for.
There is a number of ways how you can generate a random string in Ruby.
Now, to the second part of your question. If you want to access your posts using a route like /post/rndm5tr, you can simply change this line of code inside your controller:
#post = Post.find(params[:id])
to
#post = Post.find_by_randomness(params[:id])
Now, simply create a migration: rails g migration AddRandomnessToPost randomness:string and run rake db:migrate (or bundle exec rake db:migrate, depending on how it's set up).
Of course, you are free to name the field whatever you want, randomness is just a random name I used. I think the common convention is to call them slugs or tokens, but I might be wrong.
Now, add a method to before_create in your model to generate the random string and add it to the soon-to-be-saved Post object (using one of the examples from the above link). It would be wise to check if the string you're generating is already taken (you could write a recursive method that calls itself again if a post already has the random token).
I have set up 2 models in Rails:
class Category < ActiveRecord::Base
attr_accessible :name
has_many :platforms
end
and
class Platform < ActiveRecord::Base
attr_accessible :name, :url, :country
validates :name, :presence => true, :length => { :minimum => 5 }
validates :url, :presence => true, :length => { :minimum => 5 }
belongs_to :categories
end
This is my platform controller :
class PlatformsController < ApplicationController
# GET /platforms
# GET /platforms.json
def index
#platforms = Platform.all
#categories = Category.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #platforms }
end
end
# GET /platforms/1
# GET /platforms/1.json
def show
#platform = Platform.find(params[:id])
#categories = Platform.categories
respond_to do |format|
format.html # show.html.erb
format.json { render json: #platform }
end
end
# GET /platforms/new
# GET /platforms/new.json
def new
#platform = Platform.new
#categories = Category.all
respond_to do |format|
format.html # new.html.erb
format.json { render json: #platform }
end
end
# GET /platforms/1/edit
def edit
#platform = Platform.find(params[:id])
#categories = Category.find(:all)
end
# POST /platforms
# POST /platforms.json
def create
#platform = Platform.new(params[:platform])
##categories = Category.new(params[:name])
#categories = #platform.categories.create(params[:categories])
respond_to do |format|
if #platform.save
format.html { redirect_to #platform, notice: 'Platform was successfully created.' }
format.json { render json: #platform, status: :created, location: #platform }
else
format.html { render action: "new" }
format.json { render json: #platform.errors, status: :unprocessable_entity }
end
end
end
# PUT /platforms/1
# PUT /platforms/1.json
def update
#platform = Platform.find(params[:id])
#categories = Category.find(:all)
respond_to do |format|
if #platform.update_attributes(params[:platform])
format.html { redirect_to #platform, notice: 'Platform was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #platform.errors, status: :unprocessable_entity }
end
end
end
# DELETE /platforms/1
# DELETE /platforms/1.json
def destroy
#platform = Platform.find(params[:id])
#platform.destroy
respond_to do |format|
format.html { redirect_to platforms_url }
format.json { head :no_content }
end
end
end
I do not understand what I do wrong, but it doesnt correctly assign categories to platforms, and also in the platforms index view, when I try to use :
<%= platform.categories %>
it gives me error cannot find Category with id= "and here the respective id"
I am really confused since I followed tutorial for this one.
I use Rails 3.2.8
Without your view, I can't say for sure what it is you're trying to do exactly. Most importantly, what is in your params[:categories] hash? Given the name, it sounds like you intended for it to be multiple categories. However, your code is written as if you intended it to be a single set of attributes which describe one Category.
Since I can't say for sure what you want to do, I'll answer your question by explaining what you are doing. Maybe that will help you figure out how to fix it.
Your create code currently looks like this:
# POST /platforms
# POST /platforms.json
def create
#platform = Platform.new(params[:platform])
##categories = Category.new(params[:name])
#categories = #platform.categories.create(params[:categories])
The first line creates the new Platform and is easy. Skipping over the comment to the third line. This is probably what's tripping you up.
You are selecting the associations for your newly created Platform and trying to create a new category with attributes as stored in the params[:categories] hash. I'm afraid this is not allowed. (I think it throws an ActiveRecord::RecordNotSaved exception, but I could be wrong.) You can not create on a #platform which hasn't been persisted yet. Instead, I think you want build.
Here is the relevant documentation:
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
The difference between create and build is that build just sets up the association without actually saving it to the database yet. create saves it immediately. The nice thing about build is that you don't actually have to save it yourself. It tags along for free when you call #platform.save or #platform.update_attributes. Also, save is automatically wrapped in a transaction, so it won't create the new Category if it fails to create the new Platform for whatever reason.
The next interesting thing is that you are assigning the result of your create to #categories. I don't think this is what you want either. You don't need to save the new Category because it tags along with your #platform. However, if the save of the platform fails, then you are going to re-render your new view with this value of #categories whereas in new you set #categories = Category.all. This could certainly cause some confusion on the new view after a failed create.
In summary, I think your create code should look something like the following.
# POST /platforms
# POST /platforms.json
def create
#platform = Platform.new(params[:platform])
#platform.categories.build(params[:categories])
respond_to do |format|
if #platform.save
format.html { redirect_to #platform, notice: 'Platform was successfully created.' }
format.json { render json: #platform, status: :created, location: #platform }
else
#categories = Category.all
format.html { render action: "new" }
format.json { render json: #platform.errors, status: :unprocessable_entity }
end
end
end
If you're params[:categories] is not a hash of category attributes and is actually a comma delimited string of category names, then you would want to do something like the following instead of my second line above:
params[:categories].split(",").each do |category|
#project.categories.build(name: category)
end
You may also want to check out accepts_nested_attributes_for which can DRY out your controller even more.
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
I hope that helps.
I have a Ruby on Rails application where you can create 'posts'. I started of by using the scaffold generator to give generate the title which is a string and the body which is the content.
Each 'post' has a url of the id, for example /1, /2, /3, etc.
Is there a way to change that to a string of random characters, for example /49sl, /l9sl, etc?
Update
Here is what I have for the posts_controller.rb
class PostsController < ApplicationController
# GET /posts
# GET /posts.json
def index
#posts = Post.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #posts }
end
end
# GET /posts/1
# GET /posts/1.json
def show
#post = Post.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #post }
end
end
# GET /posts/new
# GET /posts/new.json
def new
#post = Post.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #post }
end
end
# GET /posts/1/edit
def edit
#post = Post.find(params[:id])
end
# POST /posts
# POST /posts.json
def create
#post = Post.new(params[:post])
respond_to do |format|
if #post.save
format.html { redirect_to #post, notice: 'Post was successfully created.' }
format.json { render json: #post, status: :created, location: #post }
else
format.html { render action: "new" }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
# PUT /posts/1
# PUT /posts/1.json
def update
#post = Post.find(params[:id])
respond_to do |format|
if #post.update_attributes(params[:post])
format.html { redirect_to #post, notice: 'Post was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
# DELETE /posts/1
# DELETE /posts/1.json
def destroy
#post = Post.find(params[:id])
#post.destroy
respond_to do |format|
format.html { redirect_to posts_url }
format.json { head :no_content }
end
end
end
Rails uses the to_param method of an ActiveRecord object in order to resolve it into a URL.
Assuming you have a way to generate these unique ids (referring to it as IdGenerator) you can do the following:
1- Generate this id whenever you persist a Post object and save it to the database, let's say under the column url_id
class Post < ActiveRecord::Base
before_create :generate_url_id
def generate_url_id
self.url_id = IdGenerator.generate_id
end
end
2- Inside your Post model override the to_param method:
class Post < ActiveRecord::Base
def to_param
return url_id
end
end
Now post_path(#post) will resolve to /posts/url_id
By the way, you can use SecureRandom.urlsafe_base64 or look here if you don't have an ID generator yet.
Read more on the documentation for to_param.
I hope these two resources are going to help you :
The gem , named obfuscate_id . It represents the ID in a format like :
http://tennisfans.eu/products/4656446465
Another gem - masked_id . It provides a similar functionality . You are in control with a format of the url creation , defining it in a class . Looking at the source it appears , that this gem uses a strategy of obfuscate_id gem .
You can give your posts random URLs by following these 3 steps:
1- In your model (Post.rb), generate a random string for each post before it is saved. For example,
class Post < ActiveRecord::Base
before_create :generate_url_id
def generate_url_id
self.url_id = SecureRandom.urlsafe_base64
end
end
2- In your model (Post.rb), supply a to_param method to override Rails default URL generation. For example:
class Post < ActiveRecord::Base
def to_param
self.url_id
end
end
3- In your controller (PostsController.rb), use a dynamic finder to find your post by its random string. For instance,
class PostsController < ApplicationController
def show
#post = Post.find_by_url_id(params[:id])
...
end
end
I went ahead and put together a complete example and posted it to Github.
Next to Erez manual way you can use the friendly_id gem, with a unique id as your slug.
class Post < ActiveRecord::Base
# FriendlyId
friendly_id :uid
# Set a unique user id on create
before_save :set_uid, on: :create
def set_uid
self[uid] = rand(36**8).to_s(36)
end
end
Please note that the setting of the uid here does not ensure uniqueness. You certainly need to add some kind of validation, but that whole topic is a different one to google.
Friendly_id is a good solution, if you want to use a gem for it.
Follow this screencast:
http://railscasts.com/episodes/314-pretty-urls-with-friendlyid
(either video or asciicast, as you prefer)
Screencasts by Ryan Bates are really well done.
If you still want another option for id generation, you can try using UUIDs:
https://en.wikipedia.org/wiki/Universally_unique_identifier
And a ruby gem to generate them easily:
https://github.com/assaf/uuid
However, I would ask: Why do you want to make them random anyway? If what you are trying to do
is to avoid one of your users from typing another id in the url and accessing data that is not theirs, then probably you would want to limit access in the controller by scoping the finder, with something like this:
def show
#post = current_user.posts.where(:id => params[:id]).first
respond_to do |format|
format.html # show.html.erb
format.json { render json: #post }
end
end
In this case, current_user is a function that returns the current authenticated user (from session, or whatever you application logic dictates).
A basic overview of my app. There is currently two models. A jobs model and a clients model. Both models have a has_and_belongs_to_many relationship as I intended to allow the user to create a client entry and then assign them one or many jobs.
Here are both of my models.
Clients -
class Client < ActiveRecord::Base
has_and_belongs_to_many :job
end
Jobs -
class Job < ActiveRecord::Base
has_and_belongs_to_many :client
end
I have been doing some research and I think im right in thinking that the relationship needs a foreign key to function so have added a client_id column & a job_id column to my database.
The clients page is currently working and here is my controller for that.
class ClientsController < ApplicationController
def index
#clients = Client.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #clients }
end
end
# GET /Clients/1
# GET /Clients/1.json
def show
#clients = Client.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #clients }
end
end
# GET /Clients/new
# GET /Clients/new.json
def new
#clients = Client.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #clients }
end
end
# GET /Clients/1/edit
def edit
#clients = Client.find(params[:id])
end
def create
#clients = Client.new(params[:client])
respond_to do |format|
if #clients.save
format.html { redirect_to #clients, notice: 'Client was successfully created.' }
format.json { render json: #clients, status: :created, location: #clients }
else
format.html { render action: "new" }
format.json { render json: #clients.errors, status: :unprocessable_entity }
end
end
end
# PUT /Clients/1
# PUT /Clients/1.json
def update
#clients = Client.find(params[:id])
respond_to do |format|
if #clients.update_attributes(params[:client])
format.html { redirect_to #clients, notice: 'Client was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #clients.errors, status: :unprocessable_entity }
end
end
end
# DELETE /Clients/1
# DELETE /Clients/1.json
def destroy
#clients = Client.find(params[:id])
#clients.destroy
respond_to do |format|
format.html { redirect_to :clients , notice: 'Client was successfully removed.'}
format.json { head :no_content }
end
end
def details
#clients = Client.find_by_id(params[:id])
#jobs = Client.job
end
end
And here's what I currently have for my jobs controller.
class JobsController < ApplicationController
def index
#jobs = Job.find(:all)
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #job }
end
end
def new
#jobs = Job.new
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #job }
end
end
def create
#jobs = Job.new(params[:job])
#cients = Client.find = Client.find(params[:id])
respond_to do |format|
if #jobs.save
format.html { redirect_to(#jobs,
:notice => 'Job was successfully created.') }
format.xml { render :xml => #jobs,
:status => :created, :location => #Job }
else
format.html { render :action => "new" }
format.xml { render :xml => #jobs.errors,
:status => :unprocessable_entity }
end
end
end
end
In my jobs form I was given thr following code which added a drop down with all the created clients.
<%= select("job", "client_id", Client.all.collect {|c| [ c.name, c.id ] }, {:include_blank => 'None'})%>
When I press save though. I recieve the following error.
unknown attribute: client_id
Application Trace | Framework Trace | Full Trace
app/controllers/jobs_controller.rb:22:in `new'
app/controllers/jobs_controller.rb:22:in `create'
I assume this is because I need to define a way of finding the client_id in my job creation as well as specifying one in my client creation.
This is my first rails app though so im not quite sure how.
Any help would be greatly appreciated.
Your jobs table doesn't have a client_id, nor should it. You need to create a junction table to facilitate a many-to-many relationship. It should be called clients_jobs and contain an integer client_id and job_id.
There is a lot more wrong here. Here are just the things I caught at a casual glance:
This line:
#cients = Client.find = Client.find(params[:id])
should be:
#cients = Client.find(params[:id])
Pluralization is important in Rails. A client doesn't have many "job". It has many jobs. Your models should reflect this:
class Client < ActiveRecord::Base
has_and_belongs_to_many :jobs
end
class Job < ActiveRecord::Base
has_and_belongs_to_many :clients
end
You'll need to create a junction table via a migration, which is where your foreign keys will exist:
$ rails g migration AddClientsJobsTable
In index and new, you first create #jobs = Job.new and then you render it via :xml => #job. Again, pluralization is important. You need #job = Job.new. You have the same problem in create, except you've dropped the 's' and capitalized the 'J': :location => #Job } You can't do that in programming. Case and spelling both matter.
Job.find(:all) or Client.all: Pick one. Don't mix find :all and .all.
#clients = Client.find(params[:id]). You're finding a single specific Client, not a collection of clients. Your variable should be called #client. This is not an error, but it is seriously ugly.
pluralize your jobs and clients in your associations. I.E
has_many_and_belongs_to :jobs
has_many_and_belongs_to :clients
And if you do not use the alternative to this many-to-many associations with the ActiveRecord :through method (the alternative to HMABT) You must create the join table yourself which is a table of job_id's and client_id's.