What I am trying to do is kinda complicated. Basically I have an order form and my client would like to be able to add and delete fields himself, such as different services you can purchase along with your item. So what I have done is I have made an orders controller and order model along with a field model and fields controller. How would I implement this now? My order model has a has_many :fields and my field model has a belongs_to :order, but aside from that I am stuck on how to implement this. So far in my orders controller i have a new and create method and heres what inside:
def new
#order = Order.new
#maybe i should put something like: #fields = Field.find(:all)
#title = "Order Form"
end
def create
#order = Order.new params[:order]
if #order.save
flash[:notice] = "Your order has been created"
redirect_to root_path
else
#title = "Order Form"
render 'new'
end
end
and in my fields controller I have a show new create edit update functions with nothing in them. What is the best practice to accomplish what I am trying to do?
Thanks in advance guys
You're looking for nested forms.
Check two screencasts:
http://railscasts.com/episodes/196-nested-model-form-part-1
http://railscasts.com/episodes/197-nested-model-form-part-2
It will even answer your need:
my client would like to be able to add
and delete fields himself
Related
I am rather new to rails and programming in general. I feel I have picked up the rails MVC and other concepts pretty well but still have a hard time figuring out the syntax of what goes into controller actions. For example when you create a
def edit
end
How do you know how to format the contents/inside of that method.
Thus far I have seen alot of this:
def new
#product = Product.new
end
If I understand this correctly this is creating an instance of the Product Model and putting it into an instance variable that is accessible by the "new" view in products/view
But let's say I want to edit that. My inclination is to do add the following action in the controller:
def edit
#product = Product.edit
end
I'm not sure the syntax Product.edit is correct though, not sure how to differentiate between edit and update either. How do I know what calls on my Model Object when creating instance variables? Is there somewhere online I can go to learn this? I have found no where thus far with a good list of commands I can play with.
def edit
#product = Product.edit
end
should be
def edit
#product = Product.find(params[:id])
end
simple explanation
The edit action (#method) is called when you call e.q localhost:3000/products/1/edit
the 1 is the id of your product which is passed to your controller and can be accessed by using params.
when the user hit edit . It is ussually send the data to update action
def update
#product = Product.find(params[:id])
#product.update(params[:product].permit(:title, :desc))
end
Ok i know i'm not explain it good enough. You really need to read this
http://guides.rubyonrails.org/
To edit something, first you need to have (or get, or create, or etc...) it. In the new method, you just create new instance of Product, this is not necessary, but needs for *form_for* helper, and generally good practice, because it you can use same form for creating and editing. Product.new just creates new product and initialize its fields with default values. Product.find searches product (single) in database by id and returns it. So for editing you first need to find your product, then it will be used to fill fields in editing form, and than in update method you will update it:
def update
target_product_required
#product.assign_attributes(product_params)
if #product.save
redirect_to #product
else
render :edit
end
end
def target_product_required
#product ||= Product.find(params[:id])
end
def product_params
params.require(:product).permit(:title, :description, :price, :available_quantity, :image, :remote_image_url)
end
This is common pattern: target_product_required returns/assigns founded by id in params product to instance variable, product_params returns product specified params. More about this read in http://guides.rubyonrails.org/
So this has been asked previously, but with no satisfying answers.
Consider two models, User, and Subscription associated as such:
class User < ActiveRecord::Base
has_one :subscription, dependent: :destroy
end
class Subscription < ActiveRecord::Base
belongs_to :user
end
Inside of SubscriptionsController, I have a new action that looks like this
def new
user = User.find(params[:user_id])
#subscription = user.build_subscription
end
Given that a subscription already exists for a user record, I'm faced with the following problem:
user.build_subscription is destructive, meaning that simply visiting the new action actually destroys the association, thereby losing the current subscription record.
Now, I could simply check for the subscription's existence and redirect like this:
def new
user = User.find(params[:user_id])
if user.subscription.present?
redirect_to root_path
else
#subscription = user.build_subscription
end
end
But that doesn't seem all that elegant.
Here's my question
Shouldn't just building a tentative record for an association not be destructive?
Doesn't that violate RESTful routing, since new is accessed with a GET request, which should not modify the record?
Or perhaps I'm doing something wrong. Should I be building the record differently? Maybe via Subscription.new(user_id: user.id)? Doesn't seem to make much sense.
Would much appreciate an explanation as to why this is implemented this way and how you'd go about dealing with this.
Thanks!
It depends on what you want to do
Thoughts
From what you've posted, it seems the RESTful structure is still valid for you. You're calling the new action on the subscriptions controller, which, by definition, means you're making a new subscription (not loading a current subscription)?
You have to remember that Rails is basically just a group of Ruby classes, with instance methods. This means that you don't need to keep entirely to the RESTful structure if it doesn't suit
I think your issue is how you're handling the request / action:
def new
user = User.find(params[:user_id])
#subscription = user.build_subscription
end
#subscription is building a new ActiveRecord object, but doesn't need to be that way. You presumably want to change the subscription (if they have one), or create an association if they don't
Logic
Perhaps you could include some logic in an instance method:
#app/models/user.rb
Class User < ActiveRecord::Base
def build
if subscription
subscription
else
build_subscription
end
end
end
#app/controllers/subscriptions_controller.rb
def new
user = User.find(params[:user_id])
#subscription = user.build
end
This will give you a populated ActiveRecord, either with data from the subscription, or the new ActiveRecord object.
View
In the view, you can then use a select box like this:
#app/views/subscriptions/new.html.erb
<%= form_for #subscription do |f| %>
<%= "User #{params[:user_id]}'s subscription: %>
<%= f.collection_select :subscription_id, Subscription.all,:id , :name %>
<% end %>
They are my thoughts, but I think you want to do something else with your code. If you give me some comments on this answer, we can fix it accordingly!
I also always thought, that a user.build_foobar would only be written to the db, if afterwards a user.save is called. One question: After calling user.build_subscription, is the old subscription still in the database?
What is the output user.persisted? and user.subscription.persisted?, after calling user.build_subscription?
Your method to check if a subscription is present, is IMHO absolutely ok and valid.
I came across this today and agree that deleting something from the db when you call build is a very unexpected outcome (caused us to have bad data). As you suggested, you can work around if very easily by simply doing Subscription.new(user: user). I personally don't think that is much less readable then user.build_subscription.
As of 2018 Richard Peck's solution worked for me:
#app/models/user.rb
Class User < ActiveRecord::Base
def build_a_subscription
if subscription
subscription
else
build_subscription
end
end
end
My issue was that a user controller didn't have a new method, because users came from an api or from a seed file.
So mine looked like:
#app/controllers/subscriptions_controller.rb
def update
#user = User.find(params[:id])
#user.build_a_subscription
if #user.update_attributes(user_params)
redirect_to edit_user_path(#user), notice: 'User was successfully updated.'
else
render :edit
end
end
And I was finally able to have the correct singular version of subscriptions in my fields_for, so :subscription verses :subscriptions
#app/views
<%= f.fields_for :subscription do |sub| %>
<%= render 'subscription', f: sub %>
<% end %>
Before I could only get the fields_for to show in the view if I made subscriptions plural. And then it wouldn't save.
But now, everything works.
In my Rails app I have an invoices_controller.rb with these actions:
def new
#invoice = current_user.invoices.build(:project_id => params[:project_id])
#invoice.build_item(current_user)
#invoice.set_number(current_user)
end
def create
#invoice = current_user.invoices.build(params[:invoice])
if #invoice.save
flash[:success] = "Invoice created."
redirect_to edit_invoice_path(#invoice)
else
render :new
end
end
Essentially, the new method instantiates a new invoice record plus one associated item record.
Now, what sort of method do I need if I want to duplicate an existing invoice?
I am a big fan of Rails's RESTful approach, so I wonder if I should add a new method like
def duplicate
end
or if I can use the existing new method and pass in the values of the invoice to be duplicated there?
What is the best approach and what might that method look like?
Naturally, you can extend RESTful routes and controllers.
To be rally RESTful, it is important to look exactly, what you want.
i.e. if you want a new invoice and use an existing one as a kind of template, then it is comparable to a new action, and the verb should be GET (get the input form). As is it based on an existing invoice, it should reference that object. After that you would create the new invoice in the usual way.
So in you routes:
resources :invoices do
member do
get 'duplicate'
end
end
giving you a route duplicate_invoice GET /invoices/:id/duplicate(.format) invoices#duplicate
So in your view you can say
<%= link_to 'duplicate this', duplicate_invoice_path(#invoice) %>
and in your controller
def duplicate
template = Invoice.find(params[:id])
#invoice= template.duplicate # define in Invoice.duplicate how to create a dup
render action: 'new'
end
If I understand correctly your question you can:
resources :invoices do
collection do
get 'duplicate'
end
end
and with this you can do:
def duplicate
# #invoice = [get the invoice]
#invoice.clone_invoice
render 'edit' # or 'new', depends on your needs
end
clone_invoice could be a custom method which should have a invoice.clone call in your custom method.
If you question if you can use additional methods except REST, you absolutely can. Google, for example, encourage developers to use something, what they call "extended RESTful" on GoogleIO, http://www.youtube.com/watch?v=nyu5ZxGUfgs
So use additional method duplicate, but don't forget about "Thin controllers, fat models" approach to incapsulate your duplicating logic inside model.
This is a consistency problem that I'm running into often.
Let's consider a typical Forum:
User can create Posts
Posts belong to a Topic
Posts also belong to the User that created them
What's the best practice for choosing between these two options:
# Initialize #post on the User
def create
#post = current_user.posts.build(params[:post])
#post.topic_id = #topic.id
if #post.save
...
end
end
Or
# Initialize #post on the Topic
def create
#post = #topic.posts.build(params[:post])
#post.user_id = current_user.id
if #post.save
...
end
end
Or is there a better way, considering that, in the above examples, either #post's user_id or topic_id would have to be added to attr_accesssible (feels hacky)?
The cleanest approach I managed to find is using CanCan: when having a rule can :create, Post, :user_id => user.id and adding load_resource in your controller it will set the attributes.
But it is not always suitable. It would be nice to have some generic solution to initalize nested objects in one shot.
Update. I've come up with another option:
#post = #topic.posts.where(user_id: current_user.id).build(params[:post])
Generally speaking, all of these approaches break the Law of Demeter. It would be better to encapsulate in a method of the model, like this:
class Topic < ActiveRecord::Base
def new_post(params={}, author=nil)
posts.build(params).tap {|p| p.user = author}
end
end
Then in controller:
#post = #topic.new_post(params[:post], current_user)
You never need to monkey with IDs or attr_accessible. If a User has_many Posts and a Topic has_many Posts than you can do
# Initialize #post on the User
def create
#post = current_user.posts.build(params[:post])
#post.topic = #topic #assuming you've gotten the topic from somewhere
if #post.save
...
end
end
There really isn't a big difference in building from the user or from the topic, but going from the user seems more natural to me.
I prefer
#post = #topic.posts.build(params[:post])
#post.user = current_user
Although I dont see any problem with the other approach, building post via topic make more natural to me(as posts are mostly displayed in the context of its topic rather than the user itself).
I'm trying to create a record within a join table from the action of a button. I would have an events model and would like to track selected events from each user.
I used the HABTM relationship since I don't really need any extra fields.
User.rb:
has_to_and_belongs_to_many :events
Event.rb:
has_to_and_belongs_to_many :users
Events_Users Migration:
[user_id, event_id, id=>false]
I'm getting stuck on the actual creation of the record. Someone helped me earlier with adding the record in within the console:
u = User.find(1)
u.events << Event.find(1)
Now I would like to perform the action as a result of clicking a link... Is this in the right direction?
def add
#user = User.find(session[:user_id])
#event = Event.find(params[:id])
if #user.events.save(params[:user][:event])
flash[:notice] = 'Event was saved.'
end
end
Should I add a #user.events.new somewhere and if so where do I put the params of which user and which event?
The following code should work (assuming that you pass in an parameter with the name id that corresponds to the id of an event object):
def add
#user = User.find(session[:user_id])
#event = Event.find(params[:id])
#user.events << #event
flash[:notice] = 'Event was saved.'
end
The problems I see in your code are:
You are passing a hash to .save. Save should only take a boolean value corresponding whether validations should be run and is true by default. However .create and .new can accept a hash of values. (.save would be used after .new).
You load an event through params[:id] but then you attempt to create an event through params[:user][:event]. Which do you want to do? Create or load? (my example assumes load)
Actions that have an effect such as this one should happen when a user clicks a button and submits a form rather than 'clicking a link'. This code may be vulnerable to cross site request forgery (Someone could trick someone into clicking a link on another site that ran this action). Rails forms, if correctly implemented, are protected against this because they use a request forgery protection token.
Most likely you want to redirect the user after this action. Rendering pages after executing actions like this (rather than redirecting) is considered bad practice.
What you did in the console you need to do in the controller.
def add
#user = User.find(session[:user_id])
#event = Event.find(params[:id])
#user.events << #event
flash[:notice] = 'Event was saved.'
end
The thing to note here is that the << operator for existing records will cause the association to be persisted immediately.
Take a look at the ActiveRecord documentation for more info.
If the event_id is passed as params[:id] and you are adding only one event in this call then, you can do the following in your controller code:
User.find(session[:user_id]).events << Event.find(params[:id])
flash[:notice] = 'Event was saved.'
You don't need explicit save to save the has_many association of an existing model instance.
Scenario 1
u = User.new(..)
u.events << Event.first
# Now you need to call `save` in order to save the user object
# and the events association
u.save
Scenario 2
u = User.first
u.events << Event.first
# Don't need to call `save` on `u` OR `u.events`