How to assign value in a RoR object? - ruby-on-rails

I have a order object, which have a create method like this :
def create
#order = Order.new(params[:order])
# #order.status_id = "1"
respond_to do |format|
if #order.save
flash[:notice] = 'Order was successfully created.'
format.html { redirect_to(#order) }
format.xml { render :xml => #order, :status => :created, :location => #order }
else
format.html { render :action => "new" }
format.xml { render :xml => #order.errors, :status => :unprocessable_entity }
end
end
end
I want to set the #order status_id to "1", so I have the
#order.status_id = "1"
But this code is not work, after I uncomment it, it still can't save "1" in the status_id in db, but other attribute can successfully store.
order.rb
class Order < ActiveRecord::Base
has_many :order_items
belongs_to :status
end

You should be able to assign a string value to a relationship key and it will be converted as required. The params always come in as strings, so obviously this needs to occur for anything to be working.
This is the kind of situation where a robust unit test would help narrow down the scope of the problem. It may be one of:
There is an attr_accessor defined for status_id that is blocking the assignment.
There is a status_id= method defined which is blocking the assignment.
The status_id is being reset during a before_validation or before_save call.
You'd want to create a test routine that verifies this works:
def test_status_assignment
order = Order.new
order.status_id = '1'
order.save
assert_equal 1, order.status_id
end

You could have attr_accessible in your model blocking you from assigning to that field. It does this by using a whitelist of approved fields.

Change #order.status_id = "1" to #order.status_id = 1
Ruby may be reading "1" as a string (which it is supposed to do) instead of an integer (what you are probably looking for). When you remove the quotes ruby will interpret it as an integer. If you must leave it as a string, try something like my_string.to_i or "123".to_i.
If this doesn't work, please post whatever error message you are receiving.

This
#order.status_id = "1"
should be
#order.status_id = 1
Or maybe
#order.status = Status.find_by_id(1)
if you have relationships set up nicely

Related

Want to be able to make multiple row inserts based off two different values

Is it possible to create multiple rows for one record based off two different column values?
Example: In my table for my Model Entry I have a column called full_range this will have the dates choose between a leave_start and leave_end date, so for example lets say I pick 05/01/15 as my leave_start date and 05/05/15 as my leave_end date, this would give me 05/01/15, 05/02/15, 05/03/15, 05/04/15, 05/05/15 for my full_range.
Then for my other column called range_days the value will be 5 this is because I have 5 days between 05/01/15 and 05/05/15.
What I would like to do is split my full_range values based off range_days and I would like to insert multiple rows for each date from my full range and I guess the range_days would come into play to say create the value of rows to create.
Right now I only get one row like so..
ID Created_at Full_range_date emp_id range_days leave_start leave_end full_range
10686 1-May-15 5/1/2015 TEST1 5 05/01/15 05/05/15 05/01/15 05/02/15 05/03/15 05/04/15 05/05/15
So in theory what I would like to see in my database would be this so it looks at full_range first and grabs the first date fills it in for full_range_date then looks at the next and next... based of range_days it does 5days which is 5 rows.
ID Created_at Full_range_date emp_id range_days leave_start leave_end full_range
10686 1-May-15 5/1/2015 TEST1 5 05/01/15 05/05/15 05/01/15 05/02/15 05/03/15 05/04/15 05/05/15
10687 1-May-15 5/2/2015 TEST1 5 05/01/15 05/05/15 05/01/15 05/02/15 05/03/15 05/04/15 05/05/15
10688 1-May-15 5/3/2015 TEST1 5 05/01/15 05/05/15 05/01/15 05/02/15 05/03/15 05/04/15 05/05/15
10689 1-May-15 5/4/2015 TEST1 5 05/01/15 05/05/15 05/01/15 05/02/15 05/03/15 05/04/15 05/05/15
10690 1-May-15 5/5/2015 TEST1 5 05/01/15 05/05/15 05/01/15 05/02/15 05/03/15 05/04/15 05/05/15
How could I go about doing this any help would be greatly appreciated!!!
I'm using rails 4.1.8
also for extra info here is my entry controller.
class EntryController < ApplicationController
def new
#entry = Entry.new
respond_to do |format|
format.html# new.html.haml
format.xml { render :xml => #entry }
end
end
def create
params.permit!
#entry = Entry.new(params[:entry])
#entry.t_d
#entry.day_hours
#entry.current_user = current_user
respond_to do |format|
if #entry.save
if current_user.email.nil?
#entry.create_totals
format.html { redirect_to(entry_path( #entry ), :notice => 'Entry successfully created, but you will not recieve any notifications, because you email is blank!') }
format.xml { render :xml => #entry, :status => :created, :location => #entry }
else
#entry.create_totals
EntryMailer.submit_for_approval(#entry).deliver
format.html { redirect_to(entry_path( #entry ), :notice => 'Entry successfully created.') }
format.xml { render :xml => #entry, :status => :created, :location => #entry }
end
else
format.html { render :action => "new" }
format.xml { render :xml => #entry.errors, :status => :unprocessable_entity }
end
end
end
and my entry model
class Entry < ActiveRecord::Base
self.primary_key = 'id'
end
Based off the answer given I tried this but still it only created one row what Would like it to do is create a new row for each date from full_range
def create
params.permit!
#entry = Entry.new(params[:entry])
#entry.t_d
#entry.day_hours
#entry.current_user = current_user
# send my email
respond_to do |format|
begin
Entry.transaction do
#entry.full_range.split(' ').each do |date|
entry = Entry.new( #entry.attributes.to_options )
entry.full_range = date
entry.save!
end
end
if current_user.email.nil?
#entry.create_totals
format.html { redirect_to(entry_path( #entry ), :notice => 'Entry successfully created, but you will not recieve any notifications, because you email is blank!') }
format.xml { render :xml => #entry, :status => :created, :location => #entry }
else
#entry.create_totals
EntryMailer.submit_for_approval(#entry).deliver
format.html { redirect_to(entry_path( #entry ), :notice => 'Entry successfully created.') }
format.xml { render :xml => #entry, :status => :created, :location => #entry }
end
rescue
format.html { render :action => "new" }
format.xml { render :xml => #entry.errors, :status => :unprocessable_entity }
end
end
end
Just for giggles I tried just doing a raw sql statement like in my entry model
like so this inserted the correct amount of rows but all of the data was an exact match nothing changed.
def trying_it_all
#id = self.id
#leave_end = self.leave_end.to_date
#leave_start = self.leave_start.to_date
#range_vals = self.range_days
if !(self.range_days == 0)
range_days.times do
sql = "insert into entry values('#{#id}', '#{#c_d}', '#{#c_u}', '#{#emp_id}', '#{#range_vals}', 'N')"
Entry.connection.execute(sql)
end
end
This is absolutely possible, and there are a number of things to consider in your implementation. The brunt of the will be in your #create action. You have all the information you need already, so here's how I would personally approach it.
First of all, when creating multiple records at once, always use a transaction. If you're not familiar, the idea is that your changes will only be made to the database if every record saves successfully (so if one of the records fails, it will rollback all others, preventing your data from becoming inconsistent). An implementation could look something like this:
def create
params.permit!
#entry = Entry.new(params[:entry])
#entry.t_d
#entry.day_hours
#entry.current_user = current_user
respond_to do |format|
if # Condition here
ActiveRecord::Base.transaction do
# Create your records here
end
else
# Indicate failure
end
end
end
Now, you may observe that there is no condition listed for the if statement. That's because with multiple transactions we'll need a better way to react to whether or not they succeed than just the result of #entry.save. The easiest way to do this is with a Begin/Rescue block (which you may recognize as Try/Catch from more mainstream languages). It would look like this:
respond_to do |format|
begin
ActiveRecord::Base.transaction do
# Create your records here
end
rescue
# Indicate failure
end
end
The way this works is the Begin block will execute, and the transaction will begin. Inside the transaction, if something fails we'll raise an error. The entire transaction will rollback, and the error will jump us out of the Begin block, and into the Rescue block instead.
Now, within the transaction, we need to create multiple records. This part should be fairly straightforward. We'll need to use a loop to create a number of records based upon the range_days. Inside the transaction we'll want to do something like this:
(1..#entry.range_days).each do
entry = Entry.new( #entry.attributes.to_options )
entry.full_range_date = # Calculation to determine the date of this entry
entry.save!
end
This will create one entry for each day in range_days. The first line in the loop makes a non-instance variable with the same values as #entry. The second line is where you'll change the value of full_range_date. The third line uses the .save! function, which has an important difference compared to the .save function; it will raise an error if it fails. This is the trigger that will allow you to escape the Begin block and jump to the Rescue block if anything goes horribly wrong.
Regarding your calculations for setting the new full_range_date, this will either involve date functions or string manipulation (depending on how you handle dates). See my REVISION at the bottom of the answer for an idea on how to accomplish this. So essentially, your create function could look a lot like this:
def create
params.permit!
#entry = Entry.new(params[:entry])
#entry.t_d
#entry.day_hours
#entry.current_user = current_user
respond_to do |format|
begin
ActiveRecord::Base.transaction do
(1..#entry.range_days).each do
entry = Entry.new( #entry.attributes.to_options )
entry.full_range_date = # Calculation to determine the date of this entry
entry.save!
end
end
if current_user.email.nil?
#entry.create_totals
format.html { redirect_to(entry_path( #entry ), :notice => 'Entry successfully created, but you will not recieve any notifications, because you email is blank!') }
format.xml { render :xml => #entry, :status => :created, :location => #entry }
else
#entry.create_totals
EntryMailer.submit_for_approval(#entry).deliver
format.html { redirect_to(entry_path( #entry ), :notice => 'Entry successfully created.') }
format.xml { render :xml => #entry, :status => :created, :location => #entry }
end
rescue
format.html { render :action => "new" }
format.xml { render :xml => #entry.errors, :status => :unprocessable_entity }
end
end
end
Closing Comment
params.permit! is an incredibly tempting method to use, in that it stops you from having to worry about strong parameters, or the need to update your controller when your model changes... but it's incredibly dangerous. Not only does it possible for a user to pass you fields you don't expect (thus forcing you to handle volumes of data for which you aren't prepared), it also allows hidden field to be defined by a savvy user. For instance, if I sent a POST request to your entry, I could specify { :Created_At => 1-Jan-2015 }, making it look like I created this record four months earlier, and completely devaluing that column. In this case that's not major, but imagine you had a User model with an :is_administrator field. Anyone could then create users with administrative rights. This may be worth reading, if you're interested.
REVISED
Quick addendum! If you already have the Full_range_date values in your full_range variable, you could replace the original loop with this:
#entry.full_range.split(' ').each do |date|
entry = Entry.new( #entry.attributes.to_options )
entry.full_range_date = date
entry.save!
end
This will turn full_range value into an array, iterate through each element, and set the value of full_range_date for you, with no further calculations.

ruby on rails - nested forms

I have a form that sets the attributes of a model, however, there is an attribute that I want to set through the code. That is, I want the user to set some attributes, but I want the program to set other attributes.
Is there any way of doing this?
Example:
If I have table with a "text" column and a "user" column, I want the user to enter the text, but I want the "user" column to be set by the program. How would I accomplish this?
Sure. You could do something like this:
def create
#something = Something.new(params[:something])
#something.programmatically_set_attribute = "Some value" #Here's the part that matters
respond_to do |format|
if #host.save
format.html { redirect_to(#something, :notice => 'Something was successfully created.') }
format.xml { render :xml => #something, :status => :created, :location => #something}
else
format.html { render :action => "new" }
format.xml { render :xml => #something.errors, :status => :unprocessable_entity }
end
end
end
In your form, you would just leave out the field that you don't want edited by human hands. You would also have to change the update function, as well.
If you want to ensure that only the text attribute is updateable by the user and none other you may want to use attr_accessible as follows
class MyModel < ActiveRecord::Base
attr_accesssible :text
end
This will ensure that only the text attribute of MyModel can be updated via mass-assignment.

Nice way of doing dual column validation

I'm using Rails 3 for this one. I've got a collections model, a user model and an intermediate subscription model. This way a user can subscribe to multiple collections, with a particular role. However, I don't want a user to be able to subscribe to the same collection twice.
So in my Subscription model I've got something like:
validate :subscription_duplicates
def subscription_duplicates
self.errors.add_to_base "This user is already subscribed" if Subscription.where(:user_id => self.user.id, :collection_id => self.collection.id)
end
However this seems ugly. Also, it breaks when I want to do something like the following in my collection controller:
def create
#collection = Collection.new(params[:collection])
#collection.subscriptions.build(:user => current_user, :role => Subscription::ROLES['owner'])
#collection.save
respond_with(#collection)
end
When I do the build the subscription does not have an id so I get a "Called id for nil" error.
Thanks for any guidance!
use validates_uniqueness_of
validates_uniqueness_of :user_id, :scope => :collection_id
First of all, your create action should always test if the object was saved, and if not then handle that (usually by re-rendering the new/edit page and showing the errors to the user).
A standard sort of create action would look like this (for a #post in this case):
def create
#post = Post.new(params[:post])
#created = #post.save
respond_to do |format|
if #created
flash[:notice] = 'Post was successfully created.'
format.html { redirect_to #post }
format.xml { render :xml => #post, :status => :created, :location => #post }
format.js
else
format.html { render :action => :new } #or edit or wherever you got here from
format.xml { render :xml => #post.errors, :status => :unprocessable_entity }
format.js
end
end
end
Shingara's approach to avoiding duplicates should work fine for you.

Model types and sorting in Rails?

This is something I've been stuck on for a while now, and I have to apologize in advance for going into so much detail for such a simple problem. I just want to make it clear what I'm trying to do here.
Scenario
So, there's a model Foo, each Foo can either be red, green, or blue. Having URLs like /reds to list all red objects, and /reds/some-red-object to show a certain object. In that "show" view, there should be next/previous links, that would essentially "find the next RedFoo in alphabetical order, and once at the last RedFoo, the next record should be the first GreenFoo, continuing in alphabetical order, and so on".
I've tried implementing this in a couple of ways and mostly ended up at a roadblock somewhere. I did get it working for the most part with single table inheritance though, having something like this:
class Foo < ActiveRecord::Base
class RedFoo < Foo
class GreenFoo < Foo
class BlueFoo < Foo
Each subclass's models and controllers are identical, just replace the model names. So the controllers look something like:
class RedFoosController < ApplicationController
def index
#foos = RedFoo.find(:all, :order => "title ASC")
respond_to do |format|
format.html { render :template => 'foos/index'}
format.xml { render :xml => #foos }
end
end
def show
#foo = RedFoo.find(params[:id])
respond_to do |format|
format.html { render :template => 'foos/show'}
format.xml { render :xml => #foo }
end
end
def new
#foo = RedFoo.new
respond_to do |format|
format.html { render :template => 'foos/new'}
format.xml { render :xml => #foo }
end
end
def edit
#foo = RedFoo.find(params[:id])
respond_to do |format|
format.html { render :template => 'foos/edit'}
end
end
def create
#foo = RedFoo.new(params[:foo])
respond_to do |format|
if #foo.save
flash[:notice] = 'Foo was successfully created.'
format.html { redirect_to(#foo) }
format.xml { render :xml => #foo, :status => :created, :location => #foo }
else
format.html { render :action => "new" }
format.xml { render :xml => #foo.errors, :status => :unprocessable_entity }
end
end
end
def update
#foo = RedFoo.find(params[:id])
respond_to do |format|
if #foo.update_attributes(params[:foo])
flash[:notice] = 'Foo was successfully updated.'
format.html { redirect_to(#foo) }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #foo.errors, :status => :unprocessable_entity }
end
end
end
def destroy
#foo = RedFoo.find(params[:id])
#foo.destroy
respond_to do |format|
format.html { redirect_to(foos_url) }
format.xml { head :ok }
end
end
end
The models only contain methods for next/previous, which work fine, surprisingly.
class RedFoo < Foo
def next
if self == RedFoo.find(:all, :order => "title ASC").last
GreenFoo.find(:all, :order => "title ASC").first
else
RedFoo.find(:first, :conditions => ["title > ?", self.title], :order => "title ASC")
end
end
def previous
if self == RedFoo.find(:all, :order => "title ASC").first
BlueFoo.find(:all, :order => "title ASC").last
else
RedFoo.find(:first, :conditions => ["title < ?", self.title], :order => "title DESC")
end
end
end
Problem
For whatever reason when I try to create and edit records, none of the attributes get saved in the database. It simply adds a new record with completely empty columns, regardless of what's filled in the form. No errors get returned in the script/server output or in the log files. From the script/console however, everything works perfectly fine. I can create new records and update their attributes no problem.
It's also quite a bad code smell that I have a lot of code duplication in my controllers/models (they're using the same views as the base model, so that's fine though). But I think that's unavoidable here unless I use some meta-goodness.
Any advice or suggestions about tackling this record saving issue would be great, but the reason I posted my setup in detail is because I have a feeling I'm probably going about this whole thing the wrong way. So, I'm open to other approaches if you know of something more practical than using STI. Thanks.
Update
The parameters hash looks about right:
{"commit"=>"Create", "authenticity_token"=>"+aOA6bBSrZP2B6jsDMnKTU+DIAIkhc8fqoSicVxRJls=", "red_foo"=>{"title"=>"Hello world!"}}
But #foo.inspect returns the following RedFoo object (all nil, except for type):
#<RedFoo id: nil, title: nil, type: "RedFoo", created_at: nil, updated_at: nil>
Problem is the params
:red_foo
is the name of the params in the view, whereas you use
params[:foo]
in the controller, I think the best way would be to be use :foo, in the view by using text_field_tag rather than any (what i assume can be) form builders text_field.
You can get out of the controller smell by using a module to do the basic crud stuff, since i assume most of the new/create/edit/update/destroy stuff is the same
OR
you could map all the routes to a foo controller and use some sort of parameter either passed in from the route, or through URI analysis to get the red/green/blue foo
Please take a look at the section called "Single table inheritance" on this page and let us know if it solves your problem.
Must admit, the way I go about STI is to use set_table_name inside a model.
e.g.
class RedFoo < AR::Base
set_table_name "foos"
include FooModule
extend FooClassModule # for self methods
def next; ...; end
end
But anyway, for this situation, what does your logger say when you do a #foo.inspect just before a save, and also what is the SQL that is ran on insert/update?
Right, so #foo.inspect gives you "nil" in the log?
What I mean (if I wasn't clear enough) was:
def create
#foo = RedFoo.new(params[:foo])
logger.error "******************* foo: #{#foo.inspect} **************"
respond_to do |format|
if #foo.save
...
if you do that and tail -f your log you can easily find out what is happening to foo and compare that to the incoming params hash
Infact, that would also be some useful information to have, what is the params hash?

Validating data of a model from another controller in Rails

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.

Resources