how to improve the rails code of update multiple attributes in rails - ruby-on-rails

I need to update the values of many attributes of a table in rails . I am using inline-edit for that. Please see the below code and give me suggestions to improve the code in a better way(modularization,meta-programming...).
products_controller.rb
def update
#page=params[:page] # In normal update no such param, so initialized with nil. If inline edit, that will be "inline"
#page=="inline"? inline_update: update_by_app_params
end
#Method to update by inline Update of individual params
def inline_update
if inline_edit_param_contains_description?
status = Product.update_description(params,current_user.id,#product)
elsif inline_edit_param_contains_order_number?
status = Product.update_order_number(params,current_user.id,#product)
elsif inline_edit_param_contains_date?
status = Product.update_end_date(params,current_user.id,#product)
..........................
..........................
end
(status==true)? respond_block : rescue_block
end
def respond_block
respond_to do |format|
format.json { render json: {status: true, description: #product.description} } if inline_edit_param_contains_description?
format.json { render json: {status: true, order_number: #product.order_number} } if inline_edit_param_contains_order_number?
...........................
...........................
end
def rescue_block
flash.now[:error] = #product.errors.full_messages.uniq.join(', ')
respond_to do |format|
format.json { respond_with_bip(#product) }
end
end
#Method to check description in inline edit
def inline_edit_param_contains_description?
params[:product][:description]
end
#Method to check order number in inline edit
def inline_edit_param_contains_order_number?
params[:product][:order_number]
end
..............
..............
product.rb
#This method will update the description of product
def self.update_description params,user_id,product
product.order=product.request_no
status = product.update(:description=>params[:product][:description],:last_modified_by=>user_id)
ProductHistory.update_history product, 'updated' if(status==true)
status
end
#This method will update the order_number of product
def self.update_order_number params,user_id,product
product.order=product.request_no
status = product.update(:order_number=>params[:product][:order_number],:last_modified_by=>user_id)
ProductHistory.update_history product, 'updated' if(status==true)
status
end
.................
.................
Please help to improve the code.
Thanks in advance

I think you can just do: #product.update(product_params) according to the standard CRUD operation. Rails is smart enough to understand that just one attribute has been changed and will adjust its UPDATE statement accordingly.
You can check this in the server log if you want to.
Then, my suggestion woud be to pass back the changed fields by using the changed_attributes option and handle this entire JSON object client side (since it only contains the changed values you can process them all):
class ProductController < ApplicationController
...
def update
product.update(product_params)
respond_to do |format|
format.json { render json: product.changed_attributes }
end
end
private
def product
#product ||= current_user.products.find(params[:id])
end
def product_params
params.require(:product).permit(:name, :description, :order_number, ...)
end
end

Related

Rails default parems for id set to new?

I am getting a 404 error on my RoR app and i found that it was because one of the method in the controller, which should only triggers when the record is not new, triggered when the record is new.
I do by that checking if the id of that record nil in my controller.
before_action :create_record, if: proc { not params[:id].nil? }
I was confused by it was triggered so i went head and checked my front-end network, which show following:
Request
Parameters:
{"format"=>"json", "id"=>"new"} <----Set to new by default
My completely controller looks like this:
class Api::MyController < ApplicationController
before_action :create_recotrd, if: proc { not params[:id].nil? }
def show
end
def index
#my_model = MyModel.all
end
def create
#my_model = MyModel.new(my_model_params)
respond_to do |format|
if #my_model.save
format.json { render json: #my_model, status: :created}
else
format.json { render json: #my_model.errors, status: :unprocessable_entity}
end
end
end
def update
#my_model = MyModel.update
end
private
def create_record
#my_model = MyModel.find(params[:id])
end
def my_model_params
params.require(:my_model).permit(
:city,
:state,
:county,
:zip,
:telephone,
:email,
)
end
end
I cant seem to find out why the id in the parameters is set to "new" instead of "nil".
I tried in the console by doing MyModel.new, the default id was nil, but then when i do the GET request, the id was set to "new"
This is a really weird approach to set a new record. I think the problem lies in your routes. You are probably trying to access yoursite.com/your_model/new and your routes are configured to look for
get "your_model/:id" => "your_controller#show"
You are probably missing
get "your_model/new" => "your_controller#new"
So when you try to visit your_model/new the routes map the "new" as the :id param in your url.
I don't see a new action in your controller as well. You should read up on basic resource set up for rails here: http://guides.rubyonrails.org/routing.html.

Keep changes on reload if validation fails

I'm working with validations in rails, stuff like:
validates_presence_of :some_field
I've noticed that if the validation fails, all changes are overwritten with existing values from the database. This makes some sense, as the page is basically being reloaded (as I gather from my development log), however this increases the risk of user error/frustration, as a single error in one field will require the hapless fellow to re-enter the changes he made to all fields.
My question: How can I get rails to reload the data that was just submitted if validation fails? That way, the user can correct the mistake without needing to re-enter the rest of his revisions.
Thanks for any advice.
Edit:
My update method, as requested, is as follows:
def update
#incorporation = Incorporation.find(params[:id])
#company = #incorporation.company
begin
#company.name="#{params[:company][:names_attributes].values.first["name_string"]} #{params[:company][:names_attributes].values.first["suffix"]}"
rescue NoMethodError
#company.name="Company #{#company.id} (Untitled)"
end
if #company.update(company_params)
redirect_to incorporations_index_path
else
redirect_to edit_incorporation_path(#incorporation)
end
end
Full disclosure regarding my controller: the above update is from my incorporations_controller even though I'm updating my Company model. Company has_one :incorporation. I did this because, in the larger context of my app, it made my associations much cleaner.
Update your controller to this
def update
#incorporation = Incorporation.find(params[:id])
#company = #incorporation.company
begin
#company.name="#{params[:company][:names_attributes].values.first["name_string"]} #{params[:company][:names_attributes].values.first["suffix"]}"
rescue NoMethodError
#company.name="Company #{#company.id} (Untitled)"
end
respond_to do |format|
if #company.update(company_params)
format.html { redirect_to({:action => "index"})}
else
format.html{render :edit}
format.json { render json: #incorporation.errors, status: :unprocessable_entity }
end
end
end
To add to the correct answer, you can clean up your code quite a bit:
def update
#incorporation = Incorporation.find params[:id]
respond_to do |format|
if #incorporation.update company_params
format.html { redirect_to({:action => "index"})}
else
format.html { render :edit }
format.json { render json: #incorporation.errors, status: :unprocessable_entity }
end
end
end
If you're using accepts_nested_attributes_for, you definitely should not hack the associated objects on the front-end.
You should look up fat model, skinny controller (let the model do the work):
#app/models/company.rb
class Company < ActiveRecord::Base
before_update :set_name
attr_accessor :name_string, :name_suffix
private
def set_name
if name_string && name_suffix
self[:name] = "#{name_string} #{name_suffix}"
else
self[:name] = "Company #{id} (Untitled)"
end
end
end
This will allow you to populate the name of the `company. To edit your nested/associated objects directly is an antipattern; a hack which will later come back to haunt you.
The key from the answer is: render :edit
Rendering the edit view means that your current #company / #incorporation data is maintained.
Redirecting will invoke a new instance of the controller, overriding the #incorporation, hence what you see on your front-end.

Rails calling create method from same controller instance variable

I have a few hours with something that is probably very easy.
I have a nested model
resources :grades do
resources :students
end
So I defined
before_action :set_grade, except: [:mass_input]
to my students_controller
def set_grade
#grade = Grade.find(params[:grade_id])
end
I'm very good with this, the problem is that now I'm using another action that takes :grade_id from another source, so I cant use set_grade, instead I'm passing the id with javascript. Works.
My problem appears here, when I try to call to create method, I'm probably doing it wrong ..
def mass_input
#grade = Grade.find(#data['grade'])
#data = JSON.parse(params[:form_data])
#is this create way ok or I'm overriding???
Student.create(:rut => #data['mass_students'][1][0], :nombre => #data['mass_students'][1][1], :apellido => #data['mass_students'][1][2])
end
This is my create action
def create
#student = Student.new(student_params)
#grade.students << #student
respond_to do |format|
if #student.save
format.html { redirect_to school_grade_path(#grade.school,#grade), notice: 'Alumno creado con éxito.' }
format.json { render :show, status: :created, location: #student }
else
format.html { render :new }
format.json { render json: #student.errors, status: :unprocessable_entity }
end
end
end
By this way code works but this line is not working
#grade.students << #student
#grade is not passing from mass_input to create. I think I'm not calling create properly but I cant find how to do it , because is not redirecting neither
My mass_input action is working by this way
def mass_input
#grade = Grade.find(#data['grade'])
#data = JSON.parse(params[:form_data])
Student.create(:rut => #data['mass_students'][1][0], :nombre => #data['mass_students'][1][1], :apellido => #data['mass_students'][1][2])
grade.students << student
respond_to do |format|
if student.save
format.html { redirect_to school_grade_path(grade.school,grade), notice: 'Alumno creado con éxito.' }
format.json { render :show, status: :created, location: student }
else
format.html { render :new }
format.json { render json: student.errors, status: :unprocessable_entity }
end
end
end
but I think is AWFUL, I must use my own create action
Thanks!!
Oh... From my point of view you are doing smth strange... The fast solution for your issue would be smth like this:
1) Rewrite before action in a new way:
before_action :set_grade
And method set_grade:
def set_grade
#grade = Grade.find(params[:grade_id].presence || #data['grade'])
end
2) Set method for student params
def student_params
data = JSON.parse(params[:form_data])['mass_students']
#Transform data to be student params. For ex:
data.map{|_key, info| {:rut => info[0], :nombre => info[1], :apellido => info[2]}}
end
3) Rewrite mass_input method
def mass_input
respond_to do |format|
if (#students = #grade.students.create(student_params).all?(&:persisted?)
#some actions when everything is great.
else
#some actions if not of them valid (maybe redirect & show info about not created students)
end
end
end
But you should definetly read more rails guides... http://guides.rubyonrails.org/
Sorry, I couldn't comment it. So I can just post a reply, it is not an complete answer though. In the student controller
Try to use
#student = #grade.students.new
or
#student = Student.new
#student.grade = #grade or #student.grade_id = params[:grade_id]
So when you do #student.save, you won't need to do the line below, and it will still work
#grade.students << #student
Ruby on rails has conventions you should follow to simplify lots of things. The first thing I see here is that in your def mass_input, you are using
Student.create(...)
The method create, as it says, creates an object but also saves it into database. So you should have new instead of create because new does not save it to database, just instantiates it:
#student = Student.new
...inside def mass_input, and by default the submit action in your view will take your object to the create method (if the object is new it goes to create, other way it goes to update, thanks to Rails). For this you could take a look at http://guides.rubyonrails.org/action_controller_overview.html
About the line #grade.students << #student, I assume you are intending to add the newly created student to his grade. See this example of usage of nested resources when trying to create, edit or destroy http://railscasts.com/episodes/139-nested-resources. In any case, nested resources implies this:
class Grade < ActiveRecord::Base
has_many :student
end
class Student < ActiveRecord::Base
belongs_to :grade
end
So, in your model Student you should have a column to store the Grade of that student. And then in your params you should receive the actual grade and store it in the grade_id inside your #student.
If something is not clear, I suggest you to take a look at the nested resources guide http://guides.rubyonrails.org/routing.html#nested-resources
As a commentary, << is used to add "things" to the end of an array, i.e. if you want to quickly store in an array some info you use:
array = []
Student.all.each do |s|
array << s.name
end
It will store in the array all the names of your students. Obviously there is a simpler way to do this by doing this:
Student.pluck(:name)

Ruby on Rails URL Format

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).

Using a database not generated by rake and scaffold in rails

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

Resources