I'm having difficulty approaching a common issue of processing orders in Rails 4. I have a model object "Offers" which are then accepted by Users. This action "accept" needs to create a new Order object which saves the same attributes as the Offer object. From what I've read my code should look as follows:
class User
has_many :offers
has_many :orders, through :offers
# ...
end
class Offer
belongs_to :user, dependent: :destroy
has_one: order
# ...
end
class Order
belongs_to :offer
def add_fields_from_offer(order)
order.offer.each do |offer|
offer_id = nil
order << offer
end
end
end
I would appreciate any advice on this code or the structure of this approach. The Offer object is really the transactional product so it should be destroyed once accepted. But I would like the order saved as an object for the User's account history.
This essentially means repeating the same fields but in a different model - is this a good approach or is there a better way?
Many thanks
You can ditch the Offer model entirely and add a boolean accepted or is_offer field to your Order.
After some further research I found it was easier to only use a single model (here "offers") and use the state_machine gem for assistance. This is ideal if your product goes through several stages e.g. accepted, posted, etc etc.
The documentation explains how to implement this in the model e.g.
state_machine :initial => :new do
event :accept do
transition :from => :new, :to => :accepted, :unless => :expired?
end
Related
I am trying to add a condition to a has many through association without luck. This is the association in my video model:
has_many :voted_users, :through => :video_votes, :source => :user
I want to only get the voted_users whose video_votes have a value equal to 1 for that video. How would I do this?
I would suggest creating a model method within the video model class
Something like:
def users_with_one_vote
self.voted_users, :conditions => ['value = ?', 1]
end
Then in the controller use video.users_with_one_vote
Then testing is easier too.
Any chance you can change that column name from 'value'. Might give some issues (reserved?).
I'd do this in 2 stages:
First, I'd define the has_many :through relationship between the models without any conditions.
Second, I'd add a 'scope' that defines a where condition.
Specifically, I'd do something like:
class User < ActiveRecord::Base
has_many :video_votes
has_many :votes, :through=>:video_votes
def self.voted_users
self.video_votes.voted
end
end
class VideoVote
def self.voted
where("value = ?", 1)
end
end
class Video
has_many :video_votes
has_many :users, :through=>:video_votes
end
Then you could get the users that have voted using:
VideoVote.voted.collect(&:user).uniq
which I believe would return an array of all users who had voted. This isn't the exact code you'd use -- they're just snippets -- but the idea is the same.
Would
has_many :voted_users, :through => :video_votes, :source => :user, :conditions => ['users.votes = ?', 1]
Do the trick?
I found that defining this method in my model works:
def upvoted_users
self.voted_users.where("value = 1")
end
and then calling #video.upvoted_users does the trick.
The best way to do this without messing with the relations is by crafting a more complex query. Relations is not the best thing to use for this particular problem. Please understand that relations is more a "way of data definition" then a way of "bussiness rules definition".
Bussiness logic or bussiness rules must be defined on a more specifically layer.
My suggestion for your problem is to create a method to search for users who voted on your video only once. something like:
class Video < ActiveRecord::Base
def voted_once()
User.joins(:video_votes).where("video_votes.value == 1 AND video_votes.video_id == ?", this.id)
end
Rails is magical for many things, but complex queries still have to be done in a "SQL" way of thinking. Don't let the illusional object oriented metaphor blind you
As long as we are throwing around ideas, how about using association extensions.
class VideoVote
scope :upvotes, where(:value => 1)
end
class Video
has_many :voted_users, :through => :video_votes, :source => :user do
def upvoted
scoped & VideoVote.upvotes
end
end
end
Then you feel good about making a call with absolutely no arguments AND you technically didn't add another method to your Video model (it's on the association, right?)
#video.voted_users.upvoted
About to make a design decision and looking for a little validation or advice on the "Rails way" before proceeding.
Summary:
Users create Posts
Posts can include (one or more) Photos
Posts can be (optionally) related to an Event
In turn Photos from a Post are also related to said Event
One requirement, among others, would be to easily display all Photos from a given Event. Another would be showing all Photos submitted by a given User.
I originally assumed:
class Photo < ActiveRecord::Base
belongs_to :post
belongs_to :user
belongs_to :event
...
end
But I'm having trouble building all the relationships in the Post controller:
class PostsController < ApplicationController
before_filter :login_required, :except => [:index, :show]
def create
#user = User.find(session[:user_id])
#post = #user.posts.create(params[:post])
# how/where to assign Event?
...
end
...
end
I can loop through and build each :photo param in the Post model...but not sure how/where to assign the event_id? Which makes me wonder if maybe there's a better approach?
Perhaps I should be exploring has_many :through relationships where:
User has_many :photos, :through => :posts
Event has_many :photos, :through => :posts
In a nutshell, should I be storing the user_id & event_id in every Photo to make it easier to grab them as needed? If so, how best to assign the associations? Or, will this become hard to maintain and thus generally frowned upon and I should use a has_many :through approach?
I have looked through the Ruby on Rails guides and I can't seem to figure out how to prevent someone from deleting a Parent record if it has Children. For example. If my database has CUSTOMERS and each customer can have multiple ORDERS, I want to prevent someone from deleting a customer if it has any orders in the database. They should only be able to delete a customer if it has no orders.
Is there a way when defining the association between models to enforce this behavior?
class Customer < ActiveRecord::Base
has_many :orders, :dependent => :restrict # raises ActiveRecord::DeleteRestrictionError
Edit: as of Rails 4.1, :restrict is not a valid option, and instead you should use either :restrict_with_error or :restrict_with_exception
Eg.:
class Customer < ActiveRecord::Base
has_many :orders, :dependent => :restrict_with_error
You could do this in a callback:
class Customer < ActiveRecord::Base
has_many :orders
before_destroy :check_for_orders
private
def check_for_orders
if orders.count > 0
errors.add_to_base("cannot delete customer while orders exist")
return false
end
end
end
EDIT
see this answer for a better way to do this.
Try using filters to hook in custom code during request processing.
One possibility would be to avoid providing your users a link to deletion in this scenario.
link_to_unless !#customer.orders.empty?
Another way would be to handle this in your controller:
if !#customer.orders.empty?
flash[:notice] = "Cannot delete a customer with orders"
render :action => :some_action
end
Or, as Joe suggests, before_filters could work well here and would probably be a much more DRY way of doing this, especially if you want this type of behavior for more models than just Customer.
I'm tempted to say yes.
A contrived example, using has_many :through and polymorphs:
class Person < ActiveRecord::Base
has_many :clubs, :through => :memberships
has_many :gyms, :through => :memberships
end
class Membership < ActiveRecord::Base
belongs_to :member, :polymorphic => true
end
class Club < ActiveRecord::Base
has_many :people, :through => :memberships
has_many :memberships, :as => :member
end
etc.
Leaving aside, for the moment, the question of whether a Gym is a Club, or any other design flaws.
To add a User to a Club, it's tempting to be RESTful and POST a person_id and a club_id to MembersController, like so:
form_for club_members_path(#club, :person_id => person.id) ...
In this scenario, when we decide to do:
form_for gym_members_path(#gym, :person_id => person.id) ...
We would need to make MembersController decide whether the parent resource is a Club or a Gym, and act accordingly. One non-DRY solution:
class MembersController < ApplicationController
before_filter :find_parent
...
private
def find_parent
#parent = Gym.find(params[:gym_id]) if params[:gym_id]
#parent = Club.find(params[:club_id]) if params[:club_id]
end
end
Shockingly awful if you do it more than once.
Also, it's predicated on the concept that joining a Club and joining a Gym are roughly the same. Or at least, Gym#add_member and Club#add_member will behave in a more or less parallel manner. But we have to assume that Gyms and Clubs might have different reasons for rejecting an application for membership. MembersController would need to handle flash messages and redirects for two or more error states.
There are solutions in the wild. James Golick's awesome ResourceController has a way of dealing with parent_type, parent_object, etc. Revolution On Rails has a nice solution for DRYing up multiple polymorphic controllers by adding some methods to ApplicationController. And of course, ActionController has #polymorhpic_url for simpler cases like Blog#posts and Article#posts, etc.
All this leaves me wondering, is it really worth putting all that pressure on MembersController at all? Polymorphism is handled pretty well in Rails, but my feeling is that using conditionals (if/unless/case) is a clear indication that you don't know what type you're dealing with. Metaprogramming helps, but only when the types have similar behavior. Both seem to point to the need for a design review.
I'd love to hear your thoughts on this. Is it better to be DRY in this scenario, or to know exactly what parent type you have? Am I being neurotic here?
How do I destroy the association itself and leave the objects being associated alone, while keeping this RESTful?
Specifically, I have these models:
class Event < ActiveRecord::Base
has_many :model_surveys, :as => :surveyable, :dependent => :destroy, :include => :survey
has_many :surveys, :through => :model_surveys
end
class ModelSurvey < ActiveRecord::Base
belongs_to :survey
belongs_to :surveyable, :polymorphic => true
end
class Survey < ActiveRecord::Base
has_many :model_surveys
end
That's saying that the Event is :surveyable (ModelSurvey belongs_to Event). My question is, without having to create a ModelSurveysController, how do I destroy the ModelSurvey, while leaving the Event and Survey alone?
Something with map.resources :events, :has_many => :model_surveys? I'm not quite sure what to do in this situation. What needs to happen with the routes, and what needs to happen in the controller? I'm hoping the url could look something like this:
/events/:title/model_surveys/:id
Thanks for your help,
Lance
In Rails 2.3 you have accepts_nested_attributes_for which would let you pass an array of ModelSurveys to the event in question. If you allow destroy through the nested attributes declaration, you'll be able to pass event[model_surveys][1][_destroy]=1 and the association will be removed. Check out the api docs.
Resources domain != model domain
The domain of the controller is not the same as that of the models. It's perfectly fine to update multiple models by changing the state of a resource.
In your case that means doing a PUT or POST to either the Event or the Survey which contains a list of ids for the other. The model for one will update the association.
PUT or POST
Some people (but not Roy Fielding) believe that you should use a PUT to update the resource and provide all of the state again, others feel that a POST with the partial state (ala PATCH) is sufficient.