Rails 4.0 multiple models and one controller - ruby-on-rails

I'm in the process of learning Ruby on Rails, and now I have created the mobile version of my application.
I created the relation between models ans controller is one-one. Now I want to make changes to manage three models from one controller. I have read and watch videos a lot about how to do this but, it doesn't work when I try to do it in my application.
Models:
class Subject < ActiveRecord::Base
has_many :pages
class Page < ActiveRecord::Base
belongs_to :subject
has_many :sections
class Section < ActiveRecord::Base
belongs_to :page
Controller:
class SubjectsController < ApplicationController
has_mobile_fu
layout "admin"
before_action :confirm_logged_in
def index
#subjects = Subject.newest_first
#pages = #subjects.pages.sorted
end
This is the error:
NoMethodError (undefined method pages' for # <ActiveRecord::Relation::ActiveRecord_Relation_Subject:0x007fbbf3c9b218>):
app/controllers/subjects_controller.rb:10:inindex'
The application works well if I keep each model managed by its controller. The problem started now that I want to control multiple models from one controller.

Can definitely use multiple models in a single controller. The issue here is you're calling a method that doesnt exist for the active record relation.
An active record relation is typically a collection of returned objects from a query using active record. So the newest_first is returning multiple, not just one. If you want to get all pages for the subjects and sort them, you can do this:
#subjects = Subject.newest_first
#pages = #subjects.map(&:pages).flatten.sort { |a, b| a.title <=> b.title }
Can switch the attribute on which you wish to sort by. The map function goes through each one, and returns the object of which i passed in the symbol. It's a shortcut for:
#subjects.map { |subject| subject.pages }
The flatten then takes that array of active record relations and flattens it into a single array. I then just use the array sort.
Edit Here's a way you can do it using the database:
#subjects = Subject.newest_first
#pages = Page.where.not(:subject_id => nil).order(:title)

MVC
Something else you'll benefit from is to look at the MVC Programming Pattern:
Rails is famous for its strict coherence to the Model-View-Controller pattern, as it works like this:
You send a request to your app
Rails "routes" your request to a specific controller / action
The controller will then collate data from your Models
The controller will then render a view to display this data
The relationship between models and controllers is exclusive; meaning you don't have to call certain models from a controller, etc.
So the basic answer is no, you don't need to call a single model from a controller. However, you do need to ensure you have the correct model associations set up, as per the explanation below:
Associations
The caveat here, is that since Ruby is object-orientated (and Rails, by virtue of being built on Ruby, also being so), it's generally considered best practice to build your application around objects
"Objects" are basically elaborate variables (constructed from your Model classes), but the pattern behind making OOP work properly is super important - everything from Rails' routes to your controller actions are designed to be object-ORIENTATED
Each time you initiate an instance of a Model, Rails is actually building an object for you to use. This object allows you to call / use a series of attributes / methods for the object, allowing you to create the experience you require with Rails
--
The bottom line -
I would highly recommend examining the ActiveRecord Associations in your models (which will determine whether you need to call a single model or not):
#app/controllers/subjects_controller.rb
Class SubjectsController < ApplicationController
def index
#subjects = Subject.newest_first #-> good use of OOP
#posts = # this is where your error occurs (`.posts` is only an attribute of each `Subject` object instance, which is fixed using the accepted answer)
end
end
Hopefully this gives you some more ideas about how to construct Rails applications

Related

Rails API: One Request, Multiple Controller Actions

I have multiple models that in practice are created and deleted together.
Basically I have an Article model and an Authorship model. Authorships link the many to many relation between Users and Articles. When an Article is created, the corresponding Authorships are also created. Right now, this is being achieved by POSTing multiple times.
However, say only part of my request works. For instance, I'm on bad wifi and only the create article request makes it through. Then my data is in a malformed half created, half not state.
To solve this, I want to send all the data at once, then have Rails split up the data into the corresponding controllers. I've thought of a couple ways to do this. The first way is having controllers handle each request in turn, sort of chaining them together. This would require the controllers to call the next one in the chain. However, this seems sorta rigid because if I decide to compose the controllers in a different way, I'll have to actually modify the controller code itself.
The second way splits up the data first, then calls the controller actions with each bit of data. This way seems more clean to me, but it requires some logic either in the routing or in a layer independent of the controllers. I'm not really clear where this logic should go (another controller? Router? Middleware?)
Has anybody had experience with either method? Is there an even better way?
Thanks,
Nicholas
Typically you want to do stuff like this -- creating associated records on object creation -- all in the same transaction. I would definitely not consider breaking up the creation of an Authorship and Article if creating an Authorship is automatic on Article creation. You want a single request that takes in all needed parameters to create an Article and its associated Authorship, then you create both in the same transaction. One way would be to do something like this in the controller:
class Authorship
belongs_to :user
belongs_to :article
end
class Article
has_many :authorships
has_many :users, through: :authorships
end
class ArticlesController
def create
#article = Article.new({title: params[:title], stuff: [:stuff]...})
#article.authorships.build(article: #article, user_id: params[:user_id])
if #article.save
then do stuff...
end
end
end
This way when you hit #article.save, the processing of both the Article and the Authorship are part of the same transaction. So if something fails anywhere, then the whole thing fails, and you don't end up with stray/disparate/inconsistent data.
If you want to assign multiple authorships on the endpoint (i.e. you take in multiple user id params) then the last bit could become something like:
class ArticlesController
def create
#article = Article.new({title: params[:title], stuff: [:stuff]...})
params[:user_ids].each do |id|
#article.authorships.build(article: #article, user_id: id)
end
if #article.save
then do stuff...
end
end
end
You can also offload this kind of associated object creation into the model via a virtual attribute and a before_save or before_create callback, which would also be transactional. But the above idiom seems more typical.
I would handle this in the model with one request. If you have a has_many relationship between Article and Author, you may be able to use accept_nested_attributes_for on your Article model. Then you can pass Authorship attributes along with your Article attributes in one request.
I have not seen your code, but you can do something like this:
model/article.rb
class Article < ActiveRecord::Base
has_many :authors, through: :authorship # you may also need a class_name: param
accepts_nested_attributes_for: :authors
end
You can then pass Author attributes to the Article model and Rails will create/update the Authors as required.
Here is a good blog post on accepts_nested_attributes_for. You can read about it in the official Rails documentation.
I would recommend taking advantage of nested attributes and the association methods Rails gives you to handle of this with one web request inside one controller action.

Rails how to use 2 of the same slugs

I am using slugs in my project to give my params an other name but I have two params called: "how-does-it-work".
(.../investor/how-does-it-work)
(.../customer/how-does-it-work)
I would like to use the slugs as how they are currently set.
Is there a way to do that?
Create two distinct routes/controllers, and simply query the corresponding ActiveRecord model in the show action. Assuming there is a slug field on your models:
Rails.application.routes.draw do
resources :customers
resources :investors
end
class CustomersController < ApplicationController
def show
#customer = Customer.find_by(slug: params[:id])
end
end
class InvestorsController < ApplicationController
def show
#investor= Investor.find_by(slug: params[:id])
end
end
This is probably the most conventional way to solve this problem in Rails. If you are using the friendly_id gem, the same approach more or less applies, except for maybe the query itself.
Hope this helps.
So, is /investor/ and /customer/ both parts of the slug?
If that's the case, you can split the string, and do a search based on the "how-does-it-work" in the grouping of "investor" or "customer".
If investor and customer are both parts of the routes, you shouldn't have a difficult time there, because they're pointing to two different controller methods. You should be able to write a search based on each of those methods that correspond to the data. If the data is the same, all your doing is pointing the controller to the correct model data with the correct params.
If you're using friendlyId, it usually has built in candidate matching. Also, if you're meaning to match multiple pages to the same slug (which I've done in the past), you can display a results page if you'd like too, by rendering based on the quantity of results.

How to reduce number of calls to a helper method

In my view, I need a User object to display a few different properties. There is an instance variable #comments that's being sent from the controller. I loop through the comments and get the User information through a helper method in order to reduce db calls.
Here is the helper method:
def user(id)
if #user.blank? == false && id == #user.id
return #user
else
return #user = User.find(id)
end
end
And in the view, I display the details as follows:
<h4> <%=user(comment.user_id).name%> </h4>
<p><%=user(comment.user_id).bio%></p>
<p><%=user(comment.user_id).long_bio%></p>
<p><%=user(comment.user_id).email%></p>
<hr>
<p><%=user(comment.admin_id).bio%></p>
<p><%=user(comment.admin_id).long_bio%></p>
<p><%=user(comment.admin_id).email%></p>
I was told that assigning a variable in the view is bad practice and hence I am calling the helper method multiple times instead of assigning the returned User object.
Is there a better way to do this?
I think you are overcomplicating things here.
Let's say you have a user model
class User < ActiveRecord::Base
has_many :comments
end
an admin model
class Admin < User
end
a comment model
class Comment < ActiveRecord::Base
belongs_to :user
end
Now you only need a type column in your users table and you can do things like this:
Admin.all (All users with type "Admin")
User.all (Really all users including type "Admin" and all other types)
and for every comment you can just use
comment.user.bio
and it doesn't matter if it's an admin or not.
See http://www.therailworld.com/posts/18-Single-Table-Inheritance-with-Rails for example
Additional info: To reduce db calls in general(N+1 queries) watch http://railscasts.com/episodes/372-bullet
It's perfectly fine to pass models to your view and build the data on the view off of the data contained in the model. Keep in mind that I'm not entirely certain how you want your page to work, but one option you may have is to use a partial view and pass it the user object. This allows you to still only have the one model in your partial view without setting additional variables.
Also, without knowing what kind of database you're using or if your models have any associations, and assuming that you're doing some input validation, you may not need this helper method and may be able to lean on your ORM to get the user object.
For Example:
<%= comment.user.age %>
This isn't any more efficient than what you've currently got, but it certainly makes the code look cleaner.
Another alternative: set a user variable in the view. You're not performing logic in your view at this point, you're simply storing some data to the heap for later use.

Converting multiple existing classes to inherit from newly created class in rails

At the moment in my rails app I have a few classes that are different products.
e.g. one example is Circuits.
What I want to do is create a new class named Service and have all the individual product models inherit from it.
Previously my circuit.rb model was
class Circuit < ActiveRecord::Base
but now it is
class Circuit < Service
and I have created a new `Services1 class, simply:
class Service < ActiveRecord::Base
end
In my circuit_controller.rb I have a few functions, the most straightforward being list
def list
conditions = []
conditions = ["organisation_id = ?", params[:id]] if params[:id]
#circuits = Circuit.paginate(:all, :page => params[:page], :conditions => conditions, :per_page => 40)
end
but changing the circuit model to inherit from services results in my circuit list view being empty which I didn't expect.
In my services table I have included a type field for storing which type of product it is but at the moment the table is empty.
Is multi table inheritance the best way to go? The app is quite large so I don't want to have to refactor a lot of code to implement this change.
Single Table inheritance would definitely be a no-go so I am wondering if some kind of association would be better.
update
Just tried following this blog post:
http://rhnh.net/2010/08/15/class-table-inheritance-and-eager-loading
so I have added
belongs_to :service
to my individual product models and then in the services model
SUBCLASSES = [:circuit, :domain]
SUBCLASSES.each do |class_name|
has_one class_name
end
end
then in the service_controller.rb
def list
#services = Service.all(:include => Service::SUBCLASSES)
end
finally, in the list view I try to inspect and debug the #services variable but it's empty because the query is running on an empty services table, should it not be also running on the circuits and domains tables aswell?
My take : I guess even if you are going to use multi table inheritance you will need to (ideally) write a task to put in all the common variables into your Service table.
Multi table inheritance doesn't serve its purpose if the common variables are not in the parent table.
Plus it also depends on how many uncommon attributes are there in your existing models. If it is none I would rather use STI.If the structure of the tables are same i would rather use STI.
But i am assuming its is not the same for all the models.

Relation Between 2 Controllers

How can I define a relationship between two controllers. I have one controller called rides and another called registrant. Is there anyway I can access the registrant database from within the rides controller? I was thinking
#registrant = Registrant.find(:first)
from within rides, but that didn't work.
Any suggestions?
Thanks
You can access your registrant model from your rides controller just like accessing it from any other controller. What do you mean by Registrant.find(:first) not working?
Now, if there's a relationship (or association as it's normally called) between your rides model and registrant model (like a has_many association), you can use nested resources to nest one controller in another.
Check out the Action Controller Overview and Rails Routing from the Outside In guides and think about picking up a good book on Rails like Agile Web Development with Rails.
If you have defined models: ride and registrant (or more general user) then you can setup a before_filter on the rides controller:
before_filter :get_user
def get_user
#user = User.find(:first, :conditions => %Q(userid = "#{params[:user_id]}"))
end
This would fetch the the user with user_id passed in as a parameter before the controller generates the view.
Yes, that should work. To get the terminology right, you are accessing the Registrant model from the RidesController. They should both be in the same database, but in separate tables.
Please post the error message you are getting.

Resources