Scope exception rules - ruby-on-rails

I am trying to define an inventory for all my articles but I want to exclude the articles that are sent to me with a parameter.
Here is what the relationship looks like:
Article
has_many :tags, through: :articletags
ArticleTags
belongs_to :article
belongs_to :tags
Tags
has_many :article, through: articletags
Here's a method to define the one without the tags in my models:
def self.by_not_tags(tag)
joins(:tags).where('tags.title != ?', tag)
end
Here's how I call it in my view:
<%= link_to (tag.title), articles_path(:scope => tag.title) %>
Here's my controller:
def custom
if params[:scope].nil?
#articles = Article.all(:order => 'created_at DESC')
else
#articles = Article.by_tags(params[:scope])
#articles2 = Article.by_not_tags(params[:scope])
end
end
The goal is to see all the articles with a tag first, and then to show the other ones without that tag, so I don't have duplicates.
My issue is with the joins, but I am not sure how to find the article without tags. Maybe an except would work, but I am not sure what kind of query would work for it.

Assuming ArticleTag model needs to validate the presence of both article_id and tag_id,
Article.where('article_tag_id is null')
If I do not assume that above validation stated,
Article.where('not exists (select 1 from article_tags where article_id = articles.id)')

Related

Scope in rails to refine search

I am trying to refine my article and giving my user flexibility to decide what they want to view.
Here the models with relationship
Article
has_many :tags, through: :articletags
ArticleTags
belongs_to :article
belongs_to :tags
Tags
has_many :article, through: articletags
Now the idea is the use would go in article and on the side see the tags.title which then give refresh the pages with Article where tags = "world". Now i am trying to do this with scope but i am not to sure how to do it. Here my scope in my model
scope :by_tags, where(title => ?, "world news")
Here how i call it
<%= link_to (tag.title), articles_path(:scope => "test") %>
But obviously it doesn't work how can i fix it?
View
<%= link_to (tag.title), articles_path(:scope => tag.title) %>
Model(Article)
def self.by_tags(tag)
joins(:tags).where('tags.title = ?', tag)
end
Controller
def index
#articles = Article.by_tags(params[:scope])
end

How to combine two scopes with OR

I have a tag feed and a friend feed.
I want to combine these two and build the ultimate "all" feed.
For friend feed:
class Post < ActiveRecord::Base
scope :friendfeed, lambda{|x| followed_by}
def self.followed_by(user)
where("user_id IN (?) OR user_id = ?", user.watched_ids, user.id)
end
end
For tag feed:
class Post < ActiveRecord::Base
scope :tagfeed, lambda{|x| infatuated_with}
def self.infatuated_with(user)
joins(:attachments).where("attachments.tag_id IN (?)", user.tags).select("DISTINCT pages.*")
end
end
And I would call something like this from the controller (I'm using Kaminari gem for pagination):
#tag_feed = Post.tagfeed(current_user).page(params[:page]).per(21)
#friend_feed = Post.friendfeed(current_user).page(params[:page]).per(21)
Now I want to have a universal feed, but I'm lost. Scopes are meant for narrowing down, but in this case I'm trying to do an OR operation. Doing stuff like
#mother_of_all_feed = #tag_feed + #friend_feed
would be redundant, and I wouldn't be able to control the number of posts appearing on a single page. How can I go about doing this? Thanks!
By the way, for tags I have association set up like this:
class Post < ActiveRecord::Base
has_many :attachments
has_many :tags, :through => :attachments
end
class Tag < ActiveRecord::Base
has_many :attachments
has_many :posts, :through => :attachments
end
class Attachment < ActiveRecord::Base
belongs_to :tag
belongs_to :post
end
There's a rails pull request for this feature (https://github.com/rails/rails/pull/9052), but in the meantime, some one has created a monkey patch that you can include in your initializers that will allow you to or scopes and where clauses in one query and still give you an ActiveRecord::Relation:
https://gist.github.com/j-mcnally/250eaaceef234dd8971b
With that, you'd be able to OR your scopes like this
Post.tagfeed(current_user).or.friendfeed(current_user)
or write a new scope
scope :mother_of_all_feed, lambda{|user| tagfeed(user).or.friendfeed(user)}
Answering my own question. I think I figured out a way.
where("pages.id IN (?) OR pages.id IN (?)",
Page.where(
"user_id IN (?) OR user_id = ?",
user.watched_ids, user.id
),
Page
.joins(:attachments)
.where("attachments.tag_id IN (?)", user.tags)
.select("DISTINCT pages.*")
)
It seems to be working so far, hope this is it!
Here's an example of how I combined two scopes.
scope :reconcilable, -> do
scopes = [
with_matching_insurance_payment_total,
with_zero_insurance_payments_and_zero_amount
]
where('id in (?)', scopes.flatten.map(&:id))
end

rails model association using has_many: through

i have a lessons table and a tags table. i associate both of the them using a has_many :through relationship and my middle table is tags_relationship.rb
class Lesson < ActiveRecord::Base
attr_accessible :title, :desc, :content, :tag_name
belongs_to :user
has_many :tag_relationships
has_many :tags, :through => :tag_relationships
end
class Tag < ActiveRecord::Base
attr_accessible :name
has_many :tag_relationships
has_many :lessons, :through => :tag_relationships
end
in one of my views, im trying to create a a virtual attribute. i have...
<div class="tags">
<%= f.label :tag_name, "Tags" %>
<%= f.text_field :tag_name, data: { autocomplete_source: tags_path} %>
</div>
but my lessons table doesn't have that attribute, tag_name, so it calls my method instead
def tag_name
????????
end
def tag_name=(name)
self.tag = Tag.find_or_initialize_by_name(name) if name.present?
end
however im not sure what to put inside the ????????. im trying to refer the :name attribute inside my tags table.
back then i used a has_many and belongs_to relationship. my lesson belonged to a tag (which was wrong) but i was able to write...
tag.name
and it worked. but since its a has_many :through now, im not sure. i tried using tags.name, Lessons.tags.name, etc but i cant seem to get it to work. how can i refer to the tags table name attribute? thank you
Apologize for my bad english.
When your Lesson was belonged to Tag lesson had only one tag, so your code was right. But now Lesson has many Tags, and it is collection (array in simple words). So, your setter must be more complex:
def tag_names=(names)
names = if names.kind_of? String
names.split(',').map{|name| name.strip!; name.length > 0 ? name : nil}.compact
else
names
end
current_names = self.tags.map(&:name) # names of current tags
not_added = names - current_names # names of new tags
for_remove = current_names - names # names of tags that well be removed
# remove tags
self.tags.delete(self.tags.where(:name => for_remove))
# adding new
not_added.each do |name|
self.tags << Tag.where(:name => name).first || Tag.new(:name => name)
end
end
And getter method should be like this:
def tag_names
self.tags.map(&:name)
end
BTW, finders like find_by_name are deprecated. You must use where.

What is a good way to deal with categories in a rails application?

I am trying to get my head around how to deal with my Products <-> Categories relation.
I am trying to build a small shop in rails and I want to make a navigation out of the category tree.
The navigation will look something like this:
- Men
|--Shirts
|--Pants
- Woman
|--Shirts
|--Dresses
-Accessoires
You get the idea...
Now, the problem is that these appear to be all different scopes on the same model, Product, with different find conditions on the associated Category.
My models so far:
class Product < ActiveRecord::Base
# validations...
has_many :categorizations
has_many :categories, :through => :categorizations
# more stuff ...
end
class Category < ActiveRecord::Base
acts_as_nested_set
has_many :categorizations
has_many :products, :through => :categorizations
end
class Categorization < ActiveRecord::Base
belongs_to :product
belongs_to :category
end
Also, I want to have multiple categories on my products and maybe make it possible to create new categories "on-the-fly" when adding a product. So the whole category management should be as easy as possible. If someone can point me in the right direction or link me to a tutorial, best practice or anything would be really awesome!
UPDATE
Ok, so now I can creating categories on the fly using virtual attributes, the question is how do I search for articles of a specific category?
What I tried:
#products = Product.scoped(:include => :categorizations, :conditions => {:category_names => params[:category]})
or
#products = Product.where("categorization = ?", params[:category])
but both didnt work. basically i want all products of one category...
You can allow users to create new categories at the same time as creating new products by using accepts_nested_attributes_for in your model. Have a look through the documentation for that to get you started.
So I ended up creating a many-to-many relation through categorizations. This railscast explains perfectly how to do this and create new categories (or tags) on-the-fly.
After I loop through the categories to make them links in my product overview:
# app/views/products/index.html.erb
<ul class="categories">
<% for category in #categories %>
<li><%= link_to category.name, :action => "index" , :category => category.id %></li>
<% end %>
</ul>
and then in the controller I build the products from the category if there is any:
# products_controller.rb
def index
if params[:category]
#products = Category.find(params[:category]).products
else
#products = Product.scoped
end
#products = #products.where("title like ?", "%" + params[:title] + "%") if params[:title]
#products = #products.order('title').page(params[:page]).per( params[:per_page] ? params[:per_page] : 25)
#categories = Category.all
end
for sure there is a more elegant way to do it but this wors for now.. any improvement appreciated.

How to access nested params

I would like to get some nested params. I have an Order that has many Items and these Items each have a Type. i would like to get the type_id parameter from the controllers create method.
#order = Order.new(params[:order])
#order.items.each do |f|
f.item_type_id = Item_type.find_by_name(f.item_type_id).id
end
The reason is that i want the user to be able to create new item_types in the view. When they do that i use an AJAX call add them to the db. When they post the form i get names of the item_type in the item_type_id parameter and i want to find the correct item_type and set the id to that
To access the nested fields from params do the following:
params[:order][:items_attributes].values.each do |item|
item[:type_id]
end if params[:order] and params[:order][:items_attributes]
Above solution will work ONLY if you have declared the correct associations and accepts_nested_attributes_for.
class Order < ActiveRecord::Base
has_many :items
accepts_nested_attributes_for :items, :allow_destroy => true
end
class Item < ActiveRecord::Base
belongs_to :order
end

Resources