Nested routing for models with double associations - ruby-on-rails

In a toy Rails application, I'm modelling a situation where you have a number of pots, each containing an amount of something, and you can make transactions between the pots. A transaction can come from any pot, and go to any pot.
Here are the relevant parts of the models:
class Pot < ActiveRecord::Base
has_many :to_transactions, :foreign_key => "to_id", :class_name => "Transaction"
has_many :from_transactions, :foreign_key => "from_id", :class_name => "Transaction"
end
class Transaction < ActiveRecord::Base
belongs_to :to_pot, :class_name => "Pot", :foreign_key => "to_id"
belongs_to :from_pot, :class_name => "Pot", :foreign_key => "from_id"
end
This allows me to do the following at the console:
>> p = Pot.find(123)
>> p.from_transactions
=> # returns array of transactions from pot 123
>> t = p.to_transactions.new
=> # t is a new transaction with to_id set to 123
and so on.
I'm having a problem setting up the routing. For example, I would like:
/pots/123/from_transactions to give a list of all transactions from pot 123,
/pots/123/to_transactions/new to give the new transaction form, with the to_id set to 123
Is this possible? Any help gratefully received etc etc.

I would say a clean way of managing is that all the from_transactions related request go to from_transactions_controller and to_transactions related go to to_transactions_controller. But the underlying model could be same for both:
In routing file you could specify your routes as follows:
'pots/:id/to_transactions/new', :controller => 'to_transactions', :action => 'new'
'pots/:id/from_transactions/', :controller => 'from_transactions', :action => 'index'
Does that help?

My routes.rb now includes the following:
map.resources :transactions, :path_prefix => '/pots/:from_id', :as => :from_transactions
map.resources :transactions, :path_prefix => '/pots/:to_id', :as => :to_transactions
This means, for example, that a request to /pots/123/from_transactions/new is sent to the transactions controller, and params[:from_id] is set to 123.

Related

Rails 4 ActiveRecord::Relation ArgumentError just doesn't make sense

This is a rather simple Rails 4 situation. Model Intranet has_many activities. Activities exists with sufficient records for several intranets. Current_intranet.activities.size returns 69 records. However, whenever I try to access any of the records, I receive "output error: <ArgumentError: wrong number of arguments (1 for 0)" even though I'm not passing any arguments. All of the following fail with that error.
Current_intranet.activities.first
Activity.where(intranet_id: 1).first
Activity.where(:intranet_id => 1).first
Activity.where{intranet_id.eq 1}.first
All of the above with [0] instead of first
There is no problem with any other models
I'd appreciate any suggestions. Thanks in advance.
There are no defined scopes. The code is:
class Activity < ActiveRecord::Base
acts_as_capitalized
# activities created during intranet creation.
DEFAULT_ACTIVITIES = { 'Document Preparation' => 'general income', 'No Charge' => 'general income'}
# static activity names that can not be deleted.
STATIC_ACTIVITY_NAMES = [ 'Document Preparation','No Charge']
before_destroy :check_if_used
before_destroy :cannot_delete_static_activities
belongs_to :created_by_user, :class_name => "User", :foreign_key => "created_by"
belongs_to :updated_by_user, :class_name => "User", :foreign_key => "updated_by"
belongs_to :intranet
belongs_to :chart_of_account
has_many :activity_rates, :dependent => :destroy
has_many :event_type
has_many :billings do
def find_by_name(name)
(find :all, :conditions => ["activity.name = ?",name])
end
end
has_many :document_templates, :foreign_key => "billing_activity_id"
validates_presence_of :intranet_id, :name, :chart_of_account_id
validates_uniqueness_of :name, :scope => :intranet_id

extra has_many requirements

Assignment: title:string
Permission: user_id:integer, subject_class:string, subject_id:integer, action:string
Assignment has_many :permissions, :foreign_key => :subject_id
But how do I make sure that it knows about the subject_class == self.class.to_s requirement?
Update: Using :conditions => {:subject_class => 'Assignment'}, how can I ensure when I do permissions.new the :subject_class is also set.
This is something you want to use polymorphic associations for (http://wiki.rubyonrails.org/howtos/db-relationships/polymorphic)
In your Assignment model, you would do the following:
has_many :permissions, :as => :subject
And in your Permission model, you would do this:
belongs_to :subject, :polymorphic => true
However, you'll need to rename the subject_class field to subject_type in order for this to work implicitly. When all of this is done, you'll have everything you asked for for free from Rails. You won't need to pass :subject_type when you create a new Permission. You can create a Permission like this and :subject_type will be filled automatically for you:
assignment = Assignment.create(:title => "My Assignment")
permission = Permission.create(:subject => assignment, :action => "read", :user => current_user)
- or -
assignment.permission.create(:user => current_user, :action => "read")

Rails + Thinking-Sphinx polymorphic association

class User < ActiveRecord::Base
has_many :followings, :as => :followable, :dependent => :destroy, :class_name => 'Follow'
has_many :follows, :as => :follower, :dependent => :destroy
define_index do
has follows.followable(:id), :as => :followable_id
has followings.follower(:id), :as => :follower_id
has follows.followable(:type), :as => :followable_type
has followings.follower(:type), :as => :follower_type
end
end
question: I can not search by type (always empty array). A bug? I would like to get all users where followers are of type 'AAA'.
User.search '', :with => { :follower_type => 'AAA' }
question: Why do I have to inverse my association to get the right result (index definition):
follows.followable(:id), :as => :followable_id
instead of
followings.followable(:id), :as => :followable_id
I would like to get a list of followers for a user with id=1
User.search :with => {:followable_id => 1} # List of followers for a user with id=1
Thx!
With regards to the first question - string filters don't work in Sphinx. This should change in the future (with Sphinx 1.10-beta, once Thinking Sphinx supports the new features), but not sure when that'll happen (I'd love to say soon, but can't promise anything).
There is a workaround available, though... but keep in mind you're handling an array of strings, so that's an additional level of complexity.
As for the second question, struggling to get my head around what the database is looking like (confusing names, but I'm lacking focus right now), so I'll just leave it at this for the moment.

Rails has_many :through and Setting Property on Join model

Similar to this question, how do I set a property on the join model just before save in this context?
class Post < ActiveRecord::Base
has_many :post_assets
has_many :assets, :through => :post_assets
has_many :featured_images, :through => :post_assets, :class_name => "Asset", :source => :asset, :conditions => ['post_assets.context = ?', "featured"]
end
class PostAssets < ActiveRecord::Base
belongs_to :post
belongs_to :asset
# context is so we know the scope or role
# the join plays
validates_presences_of :context
end
class Asset < ActiveRecord::Base
has_many :post_assets
has_many :posts, :through => :post_assets
end
I just want to be able to do this:
#post = Post.create!(:title => "A Post")
#post.featured_images << Asset.create!(:title => "An Asset")
# ...
#post = Post.first
#featured = #post.featured_images.first
#=> #<Asset id: 1, title: "An Asset">
#featured.current_post_asset #=> #<PostAsset id: 1, context: "featured">
How would that work? I've been banging my head over it all day :).
What currently happens is when I do this:
#post.featured_images << Asset.create!(:title => "An Asset")
Then the join model PostAsset that gets created never gets a chance to set context. How do I set that context property? It looks like this:
PostAsset.first #=> #<PostAsset id: 1, context: nil>
Update:
I have created a test gem to try to isolate the problem. Is there an easier way to do this?!
This ActsAsJoinable::Core class makes it so you can have many to many relationships with a context between them in the join model. And it adds helper methods. The basic tests show basically what I'm trying to do. Any better ideas on how to do this properly?
Look at the has_many options in the ActiveRecord::Associations::ClassMethods API located here: http://rails.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#M001316
This is the most interesting quote:
:conditions
Specify the conditions that the associated object must meet in order to be included as a WHERE SQL fragment, such as authorized = 1. Record creations from the association are scoped if a hash is used. has_many :posts, :conditions => {:published => true} will create published posts with #blog.posts.create or #blog.posts.build.
So I believe your conditions must be specified as a hash, like so:
class Post < ActiveRecord::Base
has_many :post_assets
has_many :featured_post_assets, :conditions => { :context => 'featured' }
has_many :assets, :through => :post_assets
has_many :featured_images, :through => :featured_post_assets,
:class_name => "Asset", :source => :asset,
end
And you should also do the following:
#post.featured_images.build(:title => "An asset")
instead of:
#post.featured_images << Asset.create!(:title => "An Asset")
This should call the scoped asset build, as suggested in the quote above to add the context field to asset. It will also save both the join model object (post_asset) and the asset object to the database at the same time in one atomic transaction.

How to create "two-side" many-to-many relationships in Rails?

Suppose we have a photography site. Any author can subscribe to receive updates from any other author. Obviously if author A is subscribed to author B that doesn't mean that B is subscribed to A. So we build models
class Author < ActiveRecord::Base
has_many :subscriptions
has_many :subscribed_by_author, :through => :subscriptions, :source => :subscribed_to
end
class Subscription < ActiveRecord::Base
belongs_to :author
belongs_to :subscribed_to, :class_name => "Author", :foreign_key => "subscribed_to"
end
This way we can use
some_author.subscribed_by_author -- the list of the authors to whom some_author is subscribed.
For any subscription we can know both ends (who is subscribed to whom)
But the question is how to get the list of people subscribed to some author using only rails (not using plain SQL) i.e get the answer to :"Who is subscribed to some_author?"
Question: is there any ability in Rails to get the relationship working both sides i.e. not only writing some_author.subscribed_BY_author but having some_author_subscribed_TO_author? If there is one, then what is it?
P.S. Obvious solution is to
Change the database design, adding a column named "direction"
Create 2 records each time a subscription is created
Add to the author model
has_many :subscribed_BY_author, :through => :subscriptions, :source => :subscribed_to, :conditions => "direction = 'by'"
has_many :subscribed_TO_author, :through => :subscriptions, :source => :subscribed_to, :conditions => "direction = 'to'"
But i wonder if there is a solution without changing the database design.
I'd use plain HABTM for something simple like this, but you're going to need a join table no matter what.
create_table :subscriptions do |t|
t.column :author_id, :integer
t.column :subscriber_id, :integer
end
Point Author to it:
class Author < ActiveRecord::Base
has_and_belongs_to_many :subscribers
:class_name => "Author",
:join_table => "subscriptions",
:association_foreign_key => "subscriber_id"
def subscriptions # "subscribers" is already included above
self.subscribers.find(:all, :subscriber_id=>author.id) # hopefully not too
end # much SQL
end
If you're really committed to your method names:
def subscribed_to_author
subscribers
end
def subscribed_by_author(author)
self.subscribers.find(:all, :subscriber_id=>author.id)
end
Create some connections (I'd make SubscriptionsController to be RESTy)
SubscriptionsController < ApplicationController
def create
#author = Author.find(params[:author_id] # author to be subscribed to
#user = current_user # user clicking the "subscribe" button
#author.subscribers << #user # assuming authors should only
#author.save # be able to subscribe themselves
end
end
Display names, or whatever
#author.subscribers.each do |s|
s.name
end
# ...or...and...also...
<%= render :partial => #author.subscribers -%>
<%= render :partial => #author.subscriptions -%>
# Author model
has_many :subscriptions_to, :class_name => "Subscription", :foreign_key => "subscribed_to"
has_many :subscribed_to_author, :through => :subscriptions_to, :source => :author
As far as I know - it works! :)

Resources