Routing based on indices in an array in rails 3 using mongoMapper - ruby-on-rails-3

I have two models, Trip and Day, with a one-to-many relationship. For the time being I do not want to make Day an embedded document.
class Day
include MongoMapper::Document
...
key :trip_id, ObjectId
belongs_to :trip
end
class Trip
include MongoMapper::Document
...
key :day_ids, Array
many :days, :in => :day_ids
end
I would like to be able to create routes that look like this:
/trips/:trip_id/days/:index_of_day
Where :index_of_day would be used to find the nth day in a trip #trip.days[:index_of_day], so a person could easily navigate to the first, second, etc. day of a trip.
Currently my route.rb file looks like this:
resources :trips do
resources :days
end
Which generates the default routes /trips/:trip_id/days/:day_id.
One partway solution I had was to put in my route.rb file
match 'trips/:trip_id/day/:id' => 'days#show'
And then in my Days Controller
def show
#day = Trip.find(params[:trip_id]).days(params[:id].to_i)
...
end
This sort of worked except all of the helpers like trip_day_path automatically redirect using the day id, not the day index.

If you want your helpers to use the index instead of the day id, you can define to_param in your Day model. Rails calls to_param on your object to find out what to put in the URL.
class Day
# ...
# many :in is for many-to-many, but you are using it for one-to-many
# either way, many :in doesn't have an inverse yet
def trip
Trip.first('day_ids' => self.id)
end
def to_param
trip.days.find_index(self)
end
end
You'll notice that's kind of hackish, which is a code smell.

Related

ActiveRecord: Using the average of a related model in a query

I have some Rails models that look a bit like this:
class Manufacturer
has_many :widgets
end
class Widget
belongs_to :manufacturer
has_many :listings
def weighted_average
listings.sum('value * stock') / listings.sum('stock')
end
end
class Listing
# Represents that a merchant is selling some widgets
belongs_to :widget
belongs_to :merchant
# Model has a :price, eg. $5.99
# Also has a :stock, eg. 45 units
end
I now want to find all Listing objects where Listing#value is below the weighted_average of the Widget it belongs to.
For example, if a particular widget has an average price of $5, and there are listings for $4, $4.50 and $6, I'd like to return the first two - as they're below the average price and represent a good deal.
The query should produce a list of all cheaply-priced listings for all widgets, and I'd like to be able to do this in a single query.
How can I do this using ActiveRecord queries? In ideal pseudocode, it'd be something like:
Listing.where('listing.price < widget.weighted_average')
# Not possible, because weighted_average isn't a column in the DB, but a method.
Try:
Listing.joins(:widget).where("#{Listing.table_name}.price < ?", 15)
Edit:
Number has to be replaced with your weighted average function result. Depending on database you're using, it can be one sql query that returns exactly what you want. For instance, for PostgreSQL HAVING clause is needed.

Getting previous HABTM values

In my app, I have several clients, and they have several elements (via has_many_through association) depending on a certain BusinessType to which Client belongs to so that instead of manually adding all the elements to the Client, I can just select the BusinessType and everything gets added automatically (business_type in Client is attr_readonly). BusinessType HABTM elements.
Here's the catch, after creation with the default BusinessType, the clients can update their elements and remove or add as they please (mostly add), so what I'm trying to do is the following:
Suppose one business_type has elements [1,2,3] and is assigned to one client, then, the following elements are added manually to the client = [4,5,6] so it ends up having [1,2,3,4,5,6], ok everything's fine here.
But after this, the business_type gets updated and has element 2 removed, so it ends up being [1,3]. Here's the deal, I want the client to be updated by removing the 2, but not the [4,5,6] that do not correspond to the business_type in question so that it ends up [1,3,4,5,6], I'm using an after_update callback to update the clients' elements but the _was method doesn't work for HABTM relationships (to get the old business_type's elements.
I've tried using a before_update callback to first to client.elements = client.elements - business_type.elements to store momentarily in the DB [1,2,3,4,5,6] - [1,2,3] = [4,5,6], and in the after_update do client.elements = client.elements + business_type.elements to get [4,5,6] + [1,3] = [1,3,4,5,6]but this has already the new value of [1,3]. How can I get the old business_type.elements value in the before_update or after_update?
Thanks in advance for your help!
I had a similar problem in an app, and the only solution I could come up with was to store the values before doing update_attributes in the controller.
Example code:
Models
class Product < ActiveRecord::Base
has_and_belongs_to_many :categories, :join_table => "categories_products"
def remember_prev_values(values)
#prev_values = values
end
def before_update_do_something
puts #prev_values - self.category_ids # Any categories removed?
puts self.category_ids - #prev_values # Any categories added?
end
end
class Category < ActiveRecord::Base
has_and_belongs_to_many :products, :join_table => "categories_products"
end
In the update method in the products controller I do the following:
class ProductsController < ApplicationController
...
def update
#product.remember_prev_values(#product.category_ids)
if #product.update_attributes(params[:product])
flash[:notice] = "Product was successfully updated."
redirect_to(product_path(#product))
else
render :action => "edit"
end
end
...
end
It is not ideal, but it is then possible to "catch" the habtm inserts/removes before they are executed.
I do think it is possible to do in a callback, but you might need to "hack" into ActiveRecord.
I did not spend much time on trying to dig into ActiveRecord internals, as this is a simple implementation that works.
You should use after_initialize callback to store previous values.
after_initialize do #previous_elements = elements.map{|x| x} end
Note that here we make a copy of assosiations by map function call.

Nested Routing for Single Table Inheritance model rails 3.1

I created a Single table inheritance model in my model file and am having difficulty with the routing. When I use :as in my resource, it renames my named path.
Model file:
class Account < ActiveRecord::Base
belongs_to :user
end
class AdvertiserAccount < Account
end
class PublisherAccount < Account
end
Routes.rb
resources :advertiser_accounts, :as => "accounts" do
resources :campaigns
end
I used :as in my routes because it is a single table inheritance and I want to pass the account_id and not the advertiser_account_id. My link is http://127.0.0.1:3000/advertiser_accounts/1/campaigns
/advertiser_accounts/:account_id/campaigns/:id(.:format)
However, using :as renames my named path from advertiser_account_campaigns to account_campaigns. My route looks like
account_campaigns GET /advertiser_accounts/:account_id/campaigns(.:format) campaigns#index
So when I create a new item using form_for, I would get "undefined method `advertiser_account_campaigns_path'"
Edited: current hacked solution
A hack around way that I am using is to duplicate the code in the routes file. Anyone have suggestions?
resources :advertiser_accounts, :as => "accounts" do
resources :campaigns
end
resources :advertiser_accounts do
resources :campaigns
end
If you run "rake routes" with your setup you'll see this:
account_campaigns GET /advertiser_accounts/:account_id/campaigns(.:format) campaigns#index
POST /advertiser_accounts/:account_id/campaigns(.:format) campaigns#create
new_account_campaign GET /advertiser_accounts/:account_id/campaigns/new(.:format) campaigns#new
edit_account_campaign GET /advertiser_accounts/:account_id/campaigns/:id/edit(.:format) campaigns#edit
account_campaign GET /advertiser_accounts/:account_id/campaigns/:id(.:format) campaigns#show
PUT /advertiser_accounts/:account_id/campaigns/:id(.:format) campaigns#update
DELETE /advertiser_accounts/:account_id/campaigns/:id(.:format) campaigns#destroy
accounts GET /advertiser_accounts(.:format) advertiser_accounts#index
POST /advertiser_accounts(.:format) advertiser_accounts#create
new_account GET /advertiser_accounts/new(.:format) advertiser_accounts#new
edit_account GET /advertiser_accounts/:id/edit(.:format) advertiser_accounts#edit
account GET /advertiser_accounts/:id(.:format) advertiser_accounts#show
PUT /advertiser_accounts/:id(.:format) advertiser_accounts#update
DELETE /advertiser_accounts/:id(.:format) advertiser_accounts#destroy
So you should use "account_campaingns_path" in this setup, the ":as" actually changes the calls in the code not the paths in the url. If you want to change the paths you should use ":path =>" rather than ":as =>".
The Rails guide on routing also shows some examples with ":as" and ":path" and the resulting paths and helpers, you'll need to search a bit because think they only use in in examples explaining other cases.
Edit: rereading your question, I think you may also want to look at member routes, I'm not sure if that's what you want to mean with it being a single inheritance and not wanting to pass the advertiser_account's ':account_id'?

BEGINNER: Correct seeds.rb in rails 3

I've just created two models and one "join table". Person, Adress (create_adresses_personss)
class Person < ActiveRecord::Base
has_and_belongs_to_many :streets
end
class Street < ActiveRecord::Base
has_and_belongs_to_many :persons
end
Now I want to add some data to these models in the db/seeds.rb file. The tutorial I follow just adds the objects:
person = Person.create :name => 'Dexter'
street.create[{:streetname => 'street1'},
{:streetname => 'street2'},
{:streetname => 'julianave'},
{:streetname => 'street3'}]
Question 1: Why is persons' data added differently than streets'? Is it just the tutorial that wants to show that there are many ways of adding data in the seeds.rb?
Question 2: The tutorial doesn't make the connections/joins in the seeds.rb. It does that in the rails console;
>>p1 = Person.find(1)
>>s1 = Street.find(1)
>>p1.streets << s1
Can't theese connections be made in the seeds.rb file?
Question 3: Would it be better to do this join with a "rich many_to_many-assocciation"?
Thanks for your time and patience with a beginner ;)
1) The first method is creating one object. The second method is creating multiple objects. However, for the second method you would need to do Street.create, not street.create.
2) Yes, you can do that in the seed file the same way.
3) The "Rich many-to-many" you're talking about is an association with a Join Model, I guess you're talking about. This is opposed to just a join table, which is what has_and_belongs_to_many does. To use a join model, you'll want to look up has_many :through. It's generally considered better to always use a proper join model, however I still use HABTM when I just need a quick, simple association. has_many :through allows for more options and more flexibility, but it is a little more complicated to setup (not that much, though). It's your decision.
One way that I like to create seed data for many-to-many associations is setting up one of the models, the adding a tap block that sets up the other models through the association.
Person.create!(:name => "Fubar").tap do |person|
3.times do |n|
person.streets.create!(:streetname => "street #{n}")
end
# OR
person.streets.create!([
{:streetname => "street 1"},
{:streetname => "street 2"},
... and so on
])
end
All tap is doing is executing the block with the object as it's only parameter. I find it convenient for seeds.
One other tip I would toss out there would be to have your model attribute names spaced on the words with underscores.
:street_name instead of :streetname
The difference is more profound when you start wanting to use some of the ActiveSupport helers that take model attributes and turn them into text strings for use in the UI.
e
:streetname.to_s.titleize # "Streetname"
:street_name.to_s.titleize # "Street Name"
And one last nitpick, you might want your join table to be addresses_people not addresses_persons since the rais inflector is going to pluralize person as people. The same would go for your controller on the Person model, PeopleController instead of PersonsController. Though maybe it will work with persons as well.
:person.to_s.pluralize # "people"
:people.to_s.singularize # "person"
:persons.to_s.singularize # "person"

Creating a news feed in Rails 3

I want to create an activity feed from recent article and comments in my rails app. They are two different types of activerecord (their table structures are different).
Ideally I would be able to create a mixed array of articles and comments and then show them in reverse chronological order.
So, I can figure out how to get an array of both articles and comments and then merge them together and sort by created_at, but I'm pretty sure that won't work as soon as I start using pagination as well.
Is there any way to create a scope like thing that will create a mixed array?
One of the other problems for me, is that it could be all articles and it could be all comments or some combination in between. So I can't just say I'll take the 15 last articles and the 15 last comments.
Any ideas on how to solve this?
When I've done this before I've managed it by having a denormalised UserActivity model or similar with a belongs_to polymorphic association to an ActivitySource - which can be any of the types of content that you want to display (posts, comments, up votes, likes, whatever...).
Then when any of the entities to be displayed are created, you have an Observer that fires and creates a row in the UserActivity table with a link to the record.
Then to display the list, you just query on UserActivity ordering by created_at descending, and then navigate through the polymorphic activity_source association to get the content data. You'll then need some smarts in your view templates to render comments and posts and whatever else differently though.
E.g. something like...
user_activity.rb:
class UserActivity < ActiveRecord::Base
belongs_to :activity_source, :polymorphic => true
# awesomeness continues here...
end
comment.rb (post/whatever)
class Comment < ActiveRecord::Base
# comment awesomeness here...
end
activity_source_observer.rb
class ActivitySourceObserver < ActiveRecord::Observer
observe :comment, :post
def after_create(activity_source)
UserActivity.create!(
:user => activity_source.user,
:activity_source_id => activity_source.id,
:activity_source_type => activity_source.class.to_s,
:created_at => activity_source.created_at,
:updated_at => activity_source.updated_at)
end
def before_destroy(activity_source)
UserActivity.destroy_all(:activity_source_id => activity_source.id)
end
end
Take a look at this railscast.
Then you can paginate 15 articles and in app/views/articles/index you can do something like this:
- #articles.each do |article|
%tr
%td= article.body
%tr
%td= nested_comments article.comment.descendants.arrange(:order => :created_at, :limit => 15)
This assumes the following relations:
#app/models/article.rb
has_one :comment # dummy root comment
#app/models/comment.rb
belongs_to :article
has_ancestry
And you add comments to an article as follows:
root_comment = #article.build_comment
root_comment.save
new_comment = root_comment.children.new
# add reply to new_comment
new_reply = new_comment.children.new
And so forth.