Rails: Block building/create for model - ruby-on-rails

I have a simple e-commerce application with models User and Order. The model Order has a column status indicating the status of the order where an order with status = 0 is a cart. In practice, User can have many Orders. a User can have only one cart though. To achieve this functionality, I have the following models. order.rb:
STATUS_VALUES = { 'CART' => 0, 'CONFIRMED' => 1,'DELIVERED' => 2}
class Order < ApplicationRecord
belongs_to :user
has_many :order_items, inverse_of: :order
accepts_nested_attributes_for :order_items
end
user.rb:
class User < ApplicationRecord
has_secure_password
has_many :photos
has_many :orders
has_one :cart
end
And in addition I have created the model Cart, where cart.rb:
class Cart < Order
self.table_name = "orders"
default_scope { where("orders.status = 0") }
end
Through this implementation, I'm able to:
1. For User to allow only building one cart at a time. Two successive user.build_cart won't work. This is perfect, exactly what I wanted. My problem though is that you can user.orders.build(status: 0) will allows work no matter how many carts I already have.
My aim is to block creating orders so that building carts is only allowed. A cart can then be updated to a non-cart through changing the status column. How can I do this in Rails?

The best way to build cart is to save them in session variables not in databases. U can find solutions by doing quick search.

Related

Caching for model in rails

product.rb
has_many :votes
vote.rb
belongs_to :product
Every time, i use sorting in my index controller:
index_controller.rb
def index
#products = Product.all.sort { |m| m.votes.count }
end
So, i think it would be good to cache votes count for each product (create additional column votesCount in products table)?
If yes, can i preform that using before_save and before_delete callbacks in vote.rb model?
Or what is the best practice method?
Give me some examples please.
I guess you are looking for counter_cache
The :counter_cache option can be used to make finding the number of belonging objects more efficient
Consider these models:
class Order < ActiveRecord::Base
belongs_to :customer, counter_cache: true
end
class Customer < ActiveRecord::Base
has_many :orders
end
With this declaration, Rails will keep the cache value up to date, and then return that value in response to the size method.
Although the :counter_cache option is specified on the model that includes the belongs_to declaration, the actual column must be added to the associated model. In the case above, you would need to add a column named orders_count to the Customer model

Rails Multiple Parent Relationships

Three models:
User
List
Item
A User can have many Lists and a List can have many Items. Each List can have an Item added to it by ANY User. This means, for example, that you can create a list and I can add items to it. Make sense? Let's keep going.
I want to be able to find all Items created by X User at any point in time.
class User < ActiveRecord::Base
has_many :lists
has_many :items
end
class List < ActiveRecord::Base
belongs_to :user
has_many :items
end
class Item < ActiveRecord::Base
belongs_to :list
belongs_to :user
end
I don't like this. It has a funky smell but I can't put my finger on it. I feel like there should be something better. When creating a new Item I would have to write something that looks like the following:
list = List.find(params[:list_id])
#item = list.items.new(params[:item])
user = User.first
#item.user = user
#item.save!
So, what am I missing? Are my relationships wrong (very likely)? Tell me! :)
It seems like there can be two different relationships between items and users: 1) items are added to lists by users, and 2) lists are created by users.
Only one user can create a list. Many users can add items to lists after they are created.
So modeling this requires two different relationships
class User < ActiveRecord::Base
has_many :lists
has_many :items_added, :class_name => "Item", :foreign_key => "added_by_user_id"
end
class List < ActiveRecord::Base
belongs_to :user
has_many :items
end
class Item < ActiveRecord::Base
belongs_to :list
belongs_to :added_by_user, :class_name => "User", :foreign_key => "user_id"
end
It makes sense to assume that you all these relationships are required -- that is, a list needs a user_id when it's created to show who created it. Likewise, an item needs to record who added it to the list.
You'd need to add the added_by_user_id column to your items table to make this work and keep track of which user added the item.
So you could do this:
# To create a user and a list is easy!
user = User.create(user_params)
list = user.create_list(list_params)
# but when adding an item, you need to know which user is adding it
item = list.create_item({:added_by_user => current_user, :item_name => 'name', etc})
Now you can find all items added by a user using:
all_user_items = user.items_added
I haven't tested this and it's a bit complicated so I may have 1-2 mistakes in the code, but generally that's one way this could be modeled.

Specifying the default ordering for a has_many association using a join (Rails)?

I have a simple Customer model with a has many relationship with a Purchase model.
class Customer < ActiveRecord::Base
has_many :purchases
end
I am repeatedly finding that I need to order Customer.purchases in my views in the following way:
#customer.purchases.joins(:shop).order("shops.position").order(:position) #yes, two orders chained
In the interest of keeping things DRY, I'd like to put this somewhere centralized so I don't have to repeatedly do it. Ideally, I'd like to make it the default ordering for Customer.purchases. For example:
class Customer < ActiveRecord::Base
has_many :purchases, :order => joins(:shop).order("shops.position").order(:position)
end
Obviously the above doesn't work. How should I do this?
In your customer model you specified joins(:shop) is the value for the key :order. I think here is the problem, So you can use the joins as a key instead of order like below,
class Customer < ActiveRecord::Base
has_many :purchases, :joins => [:shop], :order => "shops.position"
end
I think it may work.
In your purchases model, you can create a class method:
Purchase.rb:
def self.order_by_position
joins(:shop).order("shops.position").order(:position)
end
Then you can say things like:
#customer.purchases.order_by_position
Purchase.order_by_position
You could create a method on Customer that returns ordered purchases:
class Customer < ActiveRecord::Base
has_many :purchases
def ordered_purchases
purchases.joins(:shop).order("shops.position").order(:position)
end
end
and call #customer.ordered_purchases from your views.

De-Normalizing Sum of associated objects in rails

I'm working on a gift registry app using rails 3.0. The app allows multiple guests to contribute towards a gift. When displaying the gifts I need to show the amount remaining, and if the total amount has been given I needs to show the item as purchased.
For performance I want to de-normalize the sum of the total contributions, and the status of the item.
Seems simple enough, but as I tried to figure out how to put this in the model in a way that is completely encapsulated, and works in all circumstances, things got much more complicated then I expected.
I tried a few different approaches including a callback on the association between item and contribution, but ultimately ended up with a callback on the contribution object.
item.rb
class Item < ActiveRecord::Base
# decimal amount
# decimal total_contributed
# boolean purchased
has_many :contributions, :inverse_of => :item
def set_total_contributed
self.total_contributed = 0
contributions.each do |cont|
self.total_contributed += cont.amount
end
purchased = self.total_contributed >= amount
end
end
order.rb
class Order < ActiveRecord::Base
has_many :contributions, :inverse_of => :order, :dependent => :destroy
end
contribution.rb
class Contribution < ActiveRecord::Base
belongs_to :item, :inverse_of => :contributions
belongs_to :order, :inverse_of => :contributions
after_create do |cont|
item.set_total_contributed
item.save
end
after_destroy do |cont|
item.contributions.delete(cont)
item.set_total_contributed
item.save
end
end
This appears to work in the situations I need it to, but it doesn't feel right.
First, the fact that I have to manually update the contributions association in the destroy callback seems odd.
Also, the de-normalized values are only properly updated when objects are persisted.
So the question is, how can I do this better and what's the best practice for this kind of scenario?

Making record available only to certain models in Rails 3

I have a weird design question. I have a model called Article, which has a bunch of attributes. I also have an article search which does something like this:
Article.project_active.pending.search(params)
where search builds a query based on certain params. I'd like to be able to limit results based on a user, that is, to have some articles have only a subset of users which can see them.
For instance, I have an article A that I assign to writers 1,2,3,4. I want them to be able to see A, but if User 5 searches, I don't want that user to see. Also, I'd like to be able to assign some articles to ALL users.
Not sure if that was clear, but I'm looking for the best way to do this. Should I just store a serialized array with a list of user_id's and have -1 in there if it's available to All?
Thanks!
I would create a join table between Users and Articles called view_permissions to indicate that a user has permission to view a specific article.
class ViewPermission
belongs_to :article
belongs_to :user
end
class User
has_many :view_permissions
end
class Article
has_many :view_permissions
end
For example, if you wanted User 1 to be able to view Article 3 you would do the following:
ViewPermission.create(:user_id => 1, :article_id => 3)
You could then scope your articles based on the view permissions and a user:
class Article
scope :viewable_by, lambda{ |user| joins(:view_permissions).where('view_permissions.user_id = ?', user.id) }
end
To search for articles viewable by a specific user, say with id 1, you could do this:
Article.viewable_by(User.find(1)).project_active.pending.search(params)
Finally, if you want to assign an article to all users, you should add an viewable_by_all boolean attribute to articles table that when set to true allows an article to be viewable by all users. Then modify your scope to take that into account:
class Article
scope :viewable_by, lambda{ |user|
joins('LEFT JOIN view_permissions on view_permissions.article_id = articles.id')
.where('articles.viewable_by_all = true OR view_permissions.user_id = ?', user.id)
.group('articles.id')
}
end
If an Article can be assigned to multiple Writers and a Writer can be assigned to multiple Articles, I would create an Assignment model:
class Assignment < AR::Base
belongs_to :writer
belongs_to :article
end
Then you can use has_many :through:
class Article < AR::Base
has_many :assignments
has_many :writers, :through => :assignments
end
class Writer < AR::Base
has_many :assignments
has_many :articles, :through => :assignments
end

Resources