I'm having a very difficult rails problem and i thought to ask for some help. The situation is like this :
I'm using restful authentication for my User model. Now, user has a field named 'gold' that is a numeric value. There is another model named Book that has been created using scaffolding.
What i want to do is simple, yet i cannot see a way of doing it. I want to add some validation where if the user's gold is not, let's say 100, they cannot create a new book entry(from the scaffolding standard view).
Now the problem is that i need current_user information in order to validate this from my model. I need that in order to get the user id and therefore get their gold amount as well. I cannot find a good way (if any) to do that.
Another thought was doing that from a controller. However, the standard "if #book.save" block does not really allow me to insert my own error messages (inside scaffold create) :
if not User.has_the_needed_gold(current_user, 100)
flash[:goldError] = 'You do not have the needed gold to create this book.'
#end
respond_to do |format|
if #book.save
flash[:notice] = 'Book was successfully created.'
format.html { redirect_to(#book) }
format.xml { render :xml => #book, :status => :created, :location => #book }
else
format.html { render :action => "new" }
format.xml { render :xml => #book.errors, :status => :unprocessable_entity }
end
end
Now, i cannot output that message and abort the save of the new book as well. I've tried adding my own error to base, but it was cleared out(after save i guess). I'm quite confused with the situation and i've been searching around for a couple of hours with no results.
If anybody can help with that, please do so, you would spare me lots of trouble :)
Thanx for reading !
You could define a :user_gold virtual attribute for Book, set it in the controller where you have access to current_user and then incorporate that into your Book validation.
Look the user up when validating. It's likely the user lookup will have been cached by ActiveRecord, so it's not a performance hit to do so. Try something like this:
class Book
validate :check_gold
def check_gold
user = User.find(self.user_id)
self.errors.add(:user_id => "#{user.name} doesn't have enough gold!") if user.nil? or (user.gold < 100)
end
end
Related
I am struggling to get this working. I have three models
Student
Classroomattnd
Classroom
Using the has_many :through relationship. All my relationships are defined correctly and I have setup the nested form using the accepts_nested_attributes.
So when creating a new student I want to select from a list of classrooms instead of creating a new classroom. The form part also works fine the part I am not getting is when I create the student it complains about the following error.
Couldn't find Classrooom with ID=3 for Student with ID=
I have searched around for few days now but can not get the answer I need to get this working.
def new
#student = Student.new
#student.classrooms.build
end
def edit
end
def create
#student = Student.new(student_params)
respond_to do |format|
if #student.save
format.html { redirect_to #student, notice: 'Student was successfully created.' }
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
Can someone help here, someone must of face this issue before?
Also in the rails console when I run the following it works:
classroom = Classroom.last
student = Student.create(name: 'Dave', classrooms:[classroom])
Your parameter handling isn't supporting nesting. You can look at request parameters in your server log or inspect the fieldnames of your generated form to be sure of your target. It's going to be something along the lines of
def student_params
params.require(:student).permit(:student => [:name, :classroom => [:id, :name]])
end
Or maybe as below. In this second case I'm not assuming everything in the form is nested under a student container. Also note the switch from classroom to classroom_attributes which is a change I have sometimes needed to make even though the form above is what the docs indicate.
def student_params
params.require(:name).permit(:classroom_attributes => [:id, :name])
end
Hopefully that gives you a notion of how to tailor your parameter definition to what your form is generating. Also note your error messages give you indication of what part of your definition is failing, eg the missing Student id in the error you quote.
I'm playing with Ruby on Rails for the first time, and have an app up and running. Here's the quick database definition:
Teams
- id: int
- name: char[20]
Answers
- id: int
- answer_value: text
I want to be able to type: "http://localhost:3000/teams/1/answers/purple" in a browser
if team 1 answers purple.
I thought that adding the following to my routes.rb file would allow me to do that, but it hasn't.
resources :teams do
resources :answers
post 'answer_value', :on => :member
end
I can see the first answer by team 1 by going to "http://localhost:3000/teams/1/answers/1" , but I don't know how to actually set values via the URI.
If I understand you correctly, in you answers controller's show action, instead of doing Answer.find(params[:id]), do Answer.find_by_answer_value(params[:id])
If you want to create an answer, please post to
/teams/1/answers
with parameter :answer => { :answer_value => 'purple' }
Don't append answer_value to the url, looks like it's record id.
You need to either over ride the to_param method in the model, which can do what you are looking for although there are some caveats - including needing to use what #Chirantan suggested with Answer.find_by_answer_value(params[:id]) for all your finders. You can find more info in this Railscast on the subject or search for to_param in the Rails API and select the entry under ActiveRecord::Integration.
Alternatively you can go with a gem like FriendlyId which provides some more comprehensive functionality and control over the URL construction.
Typing things into your browser won't perform a post, regardless of what you put in the routes, it will only perform a get.
Creating something by a get is not RESTful, and you really shouldn't do it. That said, since you say you're just 'playing around with rails', you could do this:
If you want a get to create the thing, you have to set it that way in the routes. /teams/1/answers/purple
get '/teams/:team_id/answers/:answer_value' => 'answers#create'
then in your answers controller, create action, you could just check for a params[:team_id] and params[:answer value] and make sure that gets set before the save.
def create
#answer = Answer.new()
#answer.team_id=params[:team_id]
#answer.answer_value=params[:answer_value]
respond_to do |format|
if #answer.save
format.html { redirect_to #answer, notice: 'Answer was successfully created.' }
format.json { render json: #answer, status: :created, location: #answer }
else
format.html { render action: "new" }
format.json { render json: #answer.errors, status: :unprocessable_entity }
end
end
end
I am trying to implement the Safe Erb Plugin in my rails 2.0.2 app. I am
using this version for project specific purposes along with Ruby 1.8.7.
I have referred to the following tutorials:
http://www.railslodge.com/plugins/430-safe-erb
http://agilewebdevelopment.com/plugins/safe_erb
I could make only some sense of the above Url's as I am a newbie to
Rails and Rails related plugins. I honestly also found the above
tutorials to be very generic.
I really also couldn't relate this plugin's use to a great extent in terms of real world sense from the above tutorials. Could you please enlighten me on its usage on a day to day real world....?
I have implemented a books appl which has an author, title and
publishing date. I am currently facing issues implementing the taint
feature of this plugin
In the second tutorial, they say we need to call the tainted? method
from the Objects class. I have done this in my create method of my
books_controller.rb. The code for the create method looks like this:
def create
#book = Book.new(params[:book])
#book.publishing_date = params[:publishing_date]
respond_to do |format|
if #book.save
flash[:notice] = 'Book was successfully created.'
format.html { redirect_to(#book) }
format.xml { render :xml => #book, :status => :created,
:location => #book }
else
format.html { render :action => "new" }
format.xml { render :xml => #book.errors, :status =>
:unprocessable_entity }
end
if #book.tainted?
flash[:notice] = 'Books are tainted'
format.html { redirect_to(#book) }
format.xml { render :xml => #book, :status => :created,
:location => #book }
else
flash[:notice] = 'Books aren\'t tainted'
format.html { render :action => "new" }
format.xml { render :xml => #book.errors, :status =>
:unprocessable_entity }
end
end
Upon creating a new book record I get a notice saying that "Books aren't tainted". I have
copied this plugin into my vendor/plugins directory.
As per the second tutorial url they say "The string becomes tainted when
it is read from IO, such as the data read from the DB or HTTP request."
But its not happening in my case when I try to create a new book record.
Do I need to explicitly taint the string input I am taking(its currently
in varchar as per DB types - I guess that shouldn't be an issue). If yes
could you please tell me how to do it.
OR
if its not the above case.. Am I missing something?
Any insights on this would be really appreciated.
Thank you..
To begin with, if you can move on to rails 3 and ruby 1.9.2, please do so. It will almost certainly be worth the effort. Rails 2.0.2 was released in 2007 and is at least 3 years old. Rails 3 provides better protection than this plugin, right out of the box.
Having said that, safe-erb appears to be providing some xss protection. Going through the version of the plugin at https://github.com/abedra/safe-erb, you won't need to do anything special anywhere in your app to get it to work. Just install the plugin in vendor/plugins and you are good to go. Your controller should look just like it would without the plugin. You can do away with the if tainted block.
The way this plugin works is by hooking into various parts of the rails processing queue and doing some taint management to make your views automatically throw an error whenever there is unescaped user text. To use it, you don't need to do anything in your models and controllers. In your views, make sure that data is passed through h before being displayed.
I have an app that models a House. The House has_many Rooms, Rooms has_many Lights and Small_appliances, etc. I also have a controller called Calculator that is how the app is accessed. Data is added to the house (and its rooms) using the Calculator controller. Then a report is generated, which is located at app/views/calculator/report.html.erb.
My question is where should all the calculations and logic for the report go? Currently I have it all in the view, with some things in calculator_helper. Normally this would go in the model, right? But Calculator doesn't have a model that was generated. What is the standard for this?
Here is the calculator controller.
class CalculatorController < ApplicationController
def index
end
def save_house
#house = House.new(params[:house])
respond_to do |format|
if #house.save
format.html { render :action => 'add_rooms', :id => #house }
format.xml { render :xml => #house, :status => :created, :location => #house }
else
format.html { render :action => 'index' }
format.xml { render :xml => #house.errors, :status => :unprocessable_entity }
end
end
end
def add_rooms
#house = House.find(params[:id])
#rooms = Room.find_by_house_id(#house.id)
rescue ActiveRecord::RecordNotFound
logger.error("Attempt to access invalid house #{params[:id]}")
flash[:notice] = "You must create a house before adding rooms"
redirect_to :action => 'index'
end
def add_room
#room = Room.new(params[:room])
#house = #room.house
respond_to do |format|
if #room.save
flash[:notice] = "Room \"#{#room.name}\" was successfully added."
format.html { render :action => 'add_rooms' }
format.xml { render :xml => #room, :status => :created, :location => #room }
else
format.html { render :action => 'add_rooms' }
format.xml { render :xml => #room.errors, :status => :unprocessable_entity }
end
end
rescue ActiveRecord::RecordNotFound
logger.error("Attempt to access invalid house #{params[:id]}")
flash[:notice] = "You must create a house before adding a room"
redirect_to :action => 'index'
end
def report
flash[:notice] = nil
#house = House.find(params[:id])
#rooms = Room.find_by_house_id(#house.id)
rescue ActiveRecord::RecordNotFound
logger.error("Attempt to access invalid house #{params[:id]}")
flash[:notice] = "You must create a house before generating a report"
redirect_to :action => 'index'
end
end
There are a few ways to approach it, but the logic certainly does not belong in the view. You have the various models associated with one another in a clear hierarchy with the top of the hierarchy being the House model, if I am reading your description correctly. That being the case, I would add an appropriate method of set of methods to the House model that may be composed of calls to calculation methods in the Room models associated with a given House instance and on down the line of association. That ways the relevant calculation can be performed at each level and through composing one or more methods at the House model level you are able to have a clean, expressive and maintainable way to deal with calculations.
One thing to do, as well, would be to make sure that any calculations that can be performed by the DB are. For example, if there is a calculation that a Room model can do by simply querying it's own data then by all means push that computational burden to the DB using the ability of ActiveRecord to invoke such lower level calculation logic. Check out the API docs for the details.
I would look very carefully at the logic you want and see how it can be pushed into the model since that is probably where it belongs, close to the actual data of the calculations, and within the class structures that represent that data specifically; I would not create a model just to handle the calculation logic unless you really need to store the calculations persistently for some reason.
I would create a class in RAILS_ROOT/lib/ called, for example, Calculator and put the code in there.
Classes in /lib/ should be loaded an available anywhere in your app.
You can also create a plain ruby object in /app/models/. There's no reason they all have to inherit from ActiveRecord::Base
Ok, now I can see the code posted. I can see the calculator_controller actually has no calculations in it, are they in the views?. Try this approach:
Write a test that sets up an object which will return the results you need to return to the user of the web page, given a house, rooms or whatever else it needs.
Build a model (in models) to make that test pass.
Modify your controller code above to use your new calculator model
Modify the tests of your controller so they also pass. These tests, of course, do not need to test any business logic.
My prior respose:
If the business logic is fairly simple and only used behind this web app, then you can put it in your app/models folder.
class MyCoolClass
def initialize(clues)
#other_things = OtherThing.all
end
def do_cool_thing; end
def calculate_coolness
#other_things.length
end
end
Then in your controller, create an instance of your model
def index
#mcc = MyCoolClass "A clue as to what I want"
render
end
Then in your templates you can access it
<%=h #mcc.calculate_coolness %>
Note that #other_things is an instance__variable of MyCoolClass and generally not accessible to the templates without accessor methods being defined
This all depends on what kind of data you're creating. What does the calculator controller look like?
You can create your own classes in /lib and use them in your models, which can be a good way to separate out logic from the controller/helpers. Is there a reason why you couldn't put some of the logic in the models?
I have two tables:
Client(id,name,...)
Purchase(id,item,date,client_id,...)
They have their respective Model, with their validations. What I need is to create a new client with a new purchase, all into the create method of Client controller. Something like this:
def create
#client = Client.new(params[:client])
respond_to do |format|
if #client.save
# Add purchase
#sell = Purchase.new
#sell.client_id = #client.id
#sell.date = params[:date]
# Fill another fields
if #sell.save
# Do another stuff...
else
format.html { render :action => "new" }
format.xml { render :xml => #client.errors, :status => :unprocessable_entity }
end
flash[:notice] = 'You have a new client!'
format.html { redirect_to(:action => :show, :id => #evento.id) }
format.xml { render :xml => #client, :status => :created, :location => #client }
else
format.html { render :action => "new" }
format.xml { render :xml => #evento.client, :status => :unprocessable_entity }
end
end
end
In Purchase's model I have:
belongs_to :client
validates_format_of :date, :with => /^20[0-9]{2}[-][0-9]{2}[-][0-9]{2}$/, :message => 'not valid'
validates_presence_of :date
And there is my problem: how can I validate the date input, through validations into the model, from Client controller? And, how can I rollback the new client created when errors?
Yes, I can do the check as the very first instruction in the method, with a regular expression, but I think it's ugly. I feel like might exist a conventional method for doing this validation or even doing all the stuff in another way (i.e. calling create method for Purchase from Client controller).
Can you put me back in the right way?
Thank you in advance.
Take a look at the following page on working with associations.
Rails provides you with a bunch of handy methods on your objects.
Like the following:
Client.purchases.empty?
Client.purchases.size,
Client.purchases
Client.purchases<<(purchase)
Client.purchases.delete(purchase)
Client.purchases.find(purchases_id)
Client.purchases.find_all(conditions)
Client.purchases.build
Client.purchases.create
When using these methods, you're taking advantage of the validations on each of the models.
Hop into your Rails console and create a new client and try any of the above methods. You'll quickly learn how powerful they are and you'll be on your way in no time.
Edit: Here's a much better guide on Rails associations!
Depends a little on the situation, but you can use validates_associated to run the validations on associated objects. Then you can create the user (but don't save), create the purchase (but don't save) and try to save the user. If you've done it right the user will fail to save with a validation error on the associated object.