Rails: Database records with custom route...? - ruby-on-rails

I have a model, target, that holds a number of records that are timestamped. On the corresponding controller, I list the months of those records by doing the following:
In models/target.rb
def month
self.recorded_on.strftime('%B')
end
In controllers/targets_controller.rb
#records = Target.find :all
In views/targets/index.html.haml
%ul
- #records.group_by(&:month).sort.each do |month, data|
%li= link_to month, ''
That all works great for listing the available months for the records that I have. Next, I want to be able to click on the month and get a report of all the records for that month, at the following path generated with year and the month: /targets/2009/04
How would I do this?

Add some named scopes to your Target model to support finding by year and by month number. Something like:
class Target < ActiveRecord::Base
named_scope :by_month,
lambda { |month| { :conditions => ['MONTH(recorded_on) = ?',
month] }}
named_scope :by_year,
lambda { |year| { :conditions => ['YEAR(recorded_on) = ?', year] }}
.
.
.
end
(Note that the conditions here are using MySQL syntax.)
Assuming you're using RESTful routes, set up a named route like the one below in your config/routes.rb file (make sure it's declared before the default route):
map.targets_by_month '/targets/:year/:month', :controller => 'targets',
:requirements => { :year => /\d{4}/, :month => /\d{1,2}/ },
:conditions => { :method => :get }
—You can use this route in your view like this:
<%= link_to 'Show April 2009 Targets', targets_by_month_path('2009', '04') %>
(Note that the leading zero for the month is optional because of the :requirements regular expression in the named route defined above)
Finally, in your TargetsController, set up the index action to use the named_scopes defined earlier:
def index
#records = Target.by_year(params[:year]).by_month(params[:month])
.
.
.
end

Related

Rails: Next/Previous button for images in database

I have images where you can click next and previous, but I'm having trouble because when I'm viewing the last image in my database (that is associated with user_id), it show's routing error with :id => nil.
This is happening because there's no more data after the image for that user. So how can I set it will rotate their images even if they get to their last image in database (if next), and vice versa, if previous.
This is in my model
def self.s_prev(img)
first(:conditions => ["created_at < ?", img.created_at], :order => "created_at desc")
end
def self.s_next(img)
first(:conditions => ["created_at > ?", img.created_at], :order => "created_at asc")
end
These is my links
<%= link_to "Previous", user_image_path(#image.user_id, #user.images.s_prev(#image)) if user_image_path(#image.user_id, #user.styles.s_prev(#image)) %>
<%= link_to "Next", user_image_path(#image.user_id, #user.images.s_next(#image)) if user_image_path(#image.user_id, #user.images.s_next(#image)) %>
Apparently the if statements don't help me, for some reason
Lets say there's these database id for images table:
id user_id
1 14
2 14
3 14
4 15
So say if I'm on this page: localhost:3000/users/14/images/2
The page will show, and the links will show, but when I click on next, I'll get an error because there is no user_id => 15 and id => 4, where the next button is trying to get localhost:3000/users/14/images/4 for the page in ...users/14/images/3
So how do I avoid this issue and only display next/previous links associated with user?
def self.s_prev(img)
ordered = scoped.order("created_at desc")
ordered.first(:conditions => ["created_at < ?", img.created_at]) || ordered.first
end
def self.s_next(img)
ordered = scoped.order("created_at asc")
ordered.first(:conditions => ["created_at > ?", img.created_at]) || ordered.first
end

Rails: Sorting and Filting Data based on boolean attribute

I have a basic model of "Projects", which currently only has the attributes name:string, active:boolean. On the index view, I want to be able to have three links: Active Projects, Inactive Projects, and All Projects. These links will display the appropriate projects based on the status of the :active boolean value. Initially I set up the view by giving the links params like:
link_to "Active Projects", {:action => 'index', :active => true}
Then in the controller:
if params[:active] == "true"
#projects = Project.find(:all, :conditions => {:active => true})
elsif params[:active] == "false"
#projects = Project.find(:all, :conditions => {:active => false})
else
#projects = Project.all
This seems a little cumbersome, especially since in future I want to have multiple filters, like due date, and client. What is a good way / gem to implement advanced sorting / filtering actions, without filling up the controller with a lot of code?
You could structure your params as a hash, and pass it to conditions:
# example:
params = {
:filters => {
:active => true,
:name => 'Boby',
# etc...
}
}
filters = params[:filters]
#projects = Project.where(filters)
I would recommend using where statements.. Also here is your code refactored :
#projects = Project.all
#projects = #projects.where(active: params[:active]) if params[:active].present?
Then you can keep stacking on items if additional params exist like so :
#projects = #projects.where(awesome_sauce: params[:awesome_sauce]) if params[:awesome_sauce].present?

What is the ActiveScaffold syntax for a 'has_many' relation link in list view if placed in the helper?

A 'product' has many 'parallel_products':
class Product < ActiveRecord::Base
has_many :parallel_products, :class_name => "Product", :foreign_key => "master_product_id"
end
In the controller I add the 'parallel_products' column to the list view:
class ProductsController < ApplicationController
active_scaffold :product do |config|
config.list.columns = [ :parallel_products ]
end
end
This gives me a ActiveScaffold generated link in the list view to view, create and edit parallel products of the selected product.
So far so good.
Now, I want to specify this 'parallel_products' link in the helper instead. No changes to the link itself, it should be exactly as the ActiveScaffold generated link. The reason is that I need to add a condition, so that the link is only shown under certain circumstances.
The ActiveScaffold generated link looks like this in the log:
Started GET "/products?assoc_id=6&association=parallel_products&eid=products_6_parallel_products&parent_scaffold=products&adapter=_list_inline_adapter" for 172.16.99.11 at 2012-03-05 09:37:45 +0100
Processing by ProductsController#index as JS
Parameters: {"assoc_id"=>"6", "association"=>"parallel_products", "eid"=>"products_6_parallel_products", "parent_scaffold"=>"products", "adapter"=>"_list_inline_adapter"}
My best proposal for the ActiveScaffold has_many relation link in the helper is:
link_to("link text", :controller => "products", :assoc_id => record.id, :association => "parallel_products", :eid => "products_#{record.id}_parallel_products", :parent_scaffold => "products", :adapter => "_list_inline_adapter")
This gives me in the log:
Started GET "/products?adapter=_list_inline_adapter&assoc_id=6&association=parallel_products&eid=products_6_parallel_products&parent_scaffold=products" for 172.16.99.11 at 2012-03-05 09:39:38 +0100
Processing by ProductsController#index as HTML
Parameters: {"adapter"=>"_list_inline_adapter", "assoc_id"=>"6", "association"=>"parallel_products", "eid"=>"products_6_parallel_products", "parent_scaffold"=>"products"}
My link does not work, but it seems to be very close. Only difference is that the generated link state 'ProductsController#index as JS' and my syntax state 'ProductsController#index as HTML'.
What is the correct ActiveScaffold syntax for making a 'has_many' relation list view link in the helper?
Thanks to Sergio Cambra for helping to solve this.
This is the syntax for a 'has_many' association link if placed in the helper:
link_to("link text", {:controller => "products", :association => "parallel_products",
:parent_scaffold => "products", :product_id => record.id}, :class => 'index as_action',
:id => "as_products-index-parallel_products-#{record.id}-link",
:remote => true, :data => {:position => :after, :action => 'index'})
To answer the question in full, this is how it can be implemented to exactly replace the autogenerated AS association link:
def parallel_products_column(record)
if product.parallel_products.blank?
link_text = "-"
css_class = "index as_action empty"
else
link_text = product.parallel_products[0...3].map{ |p| p.name }.join(", ")
if product.parallel_products.length > 3
link_text += ", … (#{product.parallel_products.length})"
end
css_class = "index as_action"
end
link_to(link_text.html_safe, {:controller => "products", :association => "parallel_products",
:parent_scaffold => "products", :product_id => record.id}, :class => css_class,
:id => "as_products-index-parallel_products-#{record.id}-link",
:remote => true, :data => {:position => :after, :action => 'index'})
end
You will need a small 'hack' for the css 'empty' class, example:
.active-scaffold a.empty {
display: block;
text-align: center;
}
Note:
This is for Rails/AS 3.1 or newer.

Rails Beginner - How to define 3 buttons enabling a user to make a choice?

I am aware this is a very basic question, we are very new to rails and have been unable to find a specific answer to this question.
Background: We have just 1 database containing product information (called Product), one column (type) contains information regarding the product type and is either a value of 1 or 2.
Aim: create 3 buttons on a page which correspond to different user choices
e.g. Button 1 - show items of type 1; Button 2 - show items of type 2; Button 3 - show all items.
Ideally the information regarding the button pressed should be visible to a number of pages within a class (we have an index page, as well as 3 others in the controller)
Would somebody be able to provide an outline of the code required to do this please?
I am guessing it is some combination involving the ..._controller.rb and..._helper.rb?
Thanks a lot for your patience
What I would do is the following.
First, create a scope or named_scope in your Project model for finding projects by type. You'll then be able to use this scope to query your projects depending on type.
# Rails 3
class Project
scope :by_type, lambda{ |type| where(type: type.to_i) unless type.nil? }
end
#Rails 2
class Project
named_scope :by_type, lambda do |type|
{ :conditions => { :type => type.to_i } } unless type.nil?
end
end
Next, create a before filter in your controller to load the projects of that type. The before filter should be applied to all pages where you want the buttons to be present:
class ProjectsController
before_filter :load_projects, :only => [:index, :action1, :action2]
protected
def load_projects
#projects = Project.by_type(params[:type])
end
end
Finally, create a partial for the buttons that you can include in the views that have the option of displaying different project types:
# _project_options.html.erb
<%= link_to "Button 1", :controller => params[:controller], :action => params[:action], :type => '1' %>
<%= link_to "Button 2", :controller => params[:controller], :action => params[:action], :type => '2' %>
<%= link_to "Button 3", :controller => params[:controller], :action => params[:action], :type => '' %>
You can then include this partial in each of your related views. And you'll be able to display the projects by doing something like this (if you have an _projects.html.erb partial defined):
render #projects
You can load all the Products and then hide them selectively with some javascript. Just add a class to your markup for each type of product, like this:
<%= link_to #product.name, product_path(#product), :class => #product.type %>

Searching in Ruby on Rails - How do I search on each word entered and not the exact string?

I have built a blog application w/ ruby on rails and I am trying to implement a search feature. The blog application allows for users to tag posts. The tags are created in their own table and belong_to :post. When a tag is created, so is a record in the tag table where the name of the tag is tag_name and associated by post_id. Tags are strings.
I am trying to allow a user to search for any word tag_name in any order. Here is what I mean. Lets say a particular post has a tag that is 'ruby code controller'. In my current search feature, that tag will be found if the user searches for 'ruby', 'ruby code', or 'ruby code controller'. It will not be found if the user types in 'ruby controller'.
Essentially what I am saying is that I would like each word entered in the search to be searched for, not necessarily the 'string' that is entered into the search.
I have been experimenting with providing multiple textfields to allow the user to type in multiple words, and also have been playing around with the code below, but can't seem to accomplish the above. I am new to ruby and rails so sorry if this is an obvious question and prior to installing a gem or plugin I thought I would check to see if there was a simple fix. Here is my code:
View: /views/tags/index.html.erb
<% form_tag tags_path, :method => 'get' do %>
<p>
<%= text_field_tag :search, params[:search], :class => "textfield-search" %>
<%= submit_tag "Search", :name => nil, :class => "search-button" %>
</p>
<% end %>
TagsController
def index
#tags = Tag.search(params[:search]).paginate :page => params[:page], :per_page => 5
#tagsearch = Tag.search(params[:search])
#tag_counts = Tag.count(:group => :tag_name,
:order => 'count_all DESC', :limit => 100)
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #tags }
end
end
Tag Model
class Tag < ActiveRecord::Base
belongs_to :post
validates_length_of :tag_name, :maximum=>42
validates_presence_of :tag_name
def self.search(search)
if search
find(:all, :order => "created_at DESC", :conditions => ['tag_name LIKE ?', "%#{search}%"])
else
find(:all, :order => "created_at DESC")
end
end
end
If I read your problem correctly, you want to return a row if the tag names for the row matches one of the words passed in the query string.
You can rewrite your search method as follows:
def self.search(search)
all :conditions => (search ? { :tag_name => search.split} : [])
end
If you need partial matching then do the following:
def self.search(str)
return [] if str.blank?
cond_text = str.split.map{|w| "tag_name LIKE ? "}.join(" OR ")
cond_values = str.split.map{|w| "%#{w}%"}
all(:conditions => (str ? [cond_text, *cond_values] : []))
end
Edit 1
If you want pass multiple search strings then:
def self.search(*args)
return [] if args.blank?
cond_text, cond_values = [], []
args.each do |str|
next if str.blank?
cond_text << "( %s )" % str.split.map{|w| "tag_name LIKE ? "}.join(" OR ")
cond_values.concat(str.split.map{|w| "%#{w}%"})
end
all :conditions => [cond_text.join(" AND "), *cond_values]
end
Now you can make calls such as:
Tag.search("Ruby On Rails")
Tag.search("Ruby On Rails", "Houston")
Tag.search("Ruby On Rails", "Houston", "TX")
Tag.search("Ruby On Rails", "Houston", "TX", "Blah")
Tag.search("Ruby On Rails", "Houston", "TX", "Blah", ....) # n parameters
Caveat:
The wild card LIKE searches are not very efficient(as they don't use the index). You should consider using Sphinx (via ThinkingSphinx) OR Solr(via SunSpot) if you have lot of data.
You can try to set up ferret, or if you are really bend on just using rails, try this:
# Break the search string into words
words = params[:search].blank? ? [] : params[:search].split(' ')
conditions = [[]] # Why this way? You'll know soon
words.each do |word|
conditions[0] << ["tag_name LIKE ?"]
conditions << "%#{word}%"
end
conditions[0] = conditions.first.join(" OR ") # Converts condition string to include " OR " easily ;-)
# Proceed to find using `:conditions => conditions` in your find
hope this helps =)
Sounds like you need a full text search. The best search integration right now is with Sphinx and the Thinking_Sphinx plugin. I have used it on several projects and it's super easy to setup.
You do need to install sphinx on your host so if you are using a shared host that could present some issues.
You could also use full text search in a MyISAM MySQL database, but performance on that is pretty poor.
Once you have your sphinx installed you just put what you want to index in your model and call model.search. The results will be a list of model objects. It supports will_paginate as well.
I'd suggest looking at Searchlogic if you don't want to use a separate fulltext search engine (Ferret, Sphinx, etc). It makes simple searches extremely easy, although you may not want to use it in a public facing area without lots of testing.
Also check out the Railscast on it: http://railscasts.com/episodes/176-searchlogic
1.You can do some coding in your controller post as such:-
<pre>
def show
#post = Post.find(params[:id])
#tag_counts = Tag.count(:group => :name, :order => 'updated_at DESC', :limit => 10)
respond_to do |format|
format.html # show.html.erb
format.json { render json: #post }
end
end
</pre>
2.Now make some changes in your view file:-
<pre>
<b>Tags:</b>
<%= join_tags(#post) %>
<%unless #tag_counts.nil?%>
<% #tag_counts.each do |tag_name, tag_count| %>
<tr><td><%= link_to(tag_name, posts_path(:name => tag_name)) %></td>
<td>(<%=tag_count%>)</td>
</tr><% end %>
<%end%>
</pre>
3. And one important thing is that there should be many to many relationship between tags and post.

Resources