I am a .NET guy and I try to understand the concept behind Rails and its Active Record stuff.
As I can see in all examples they always assume that your view is a 1:1 copy of your model. In reality frequently that´s not true.
Like a view that holds a customer and contact person(s) that are not related to that customer. User should be able to edit both (customer and contact person(s) in one view e.g.)
In every example I see that they bind the view directly to ONE activerecord object. All the stuff like the model, validation and so on bind to one object that is mapped directly to the database.
Could a Rails guy explain what´s an elegant way to work with Active Record in real life applications in complex model situations? In the first moment I was thinking about DTOs but I could not imagine that this is the way to go with Rails.
Agree with John....
You asked: "Like a view that holds a customer and contact person(s) that are not related to that customer. User should be able to edit both (customer and contact person(s) in one view e.g.)"
Ok, so this is a Customer model that has some employees, right? If not, replace "Employee" with "Person"
/app/model/customer.rb
Class Customer < ActiveRecord::Base
has_many :employees
accepts_nested_attributes_for :employees
end
/app/model/employee.rb
Class Employee < ActiveRecord::Base
belongs_to :customer
end
Then in your view of the customer
/app/views/customers/show.html.erb
<%= form_for(#customer) do |f| %>
<%= f.text_field :name %>
.... yada yada ....
<%= f.fields_for(:employees) do |ef| } %>
<%= ef.text_field :first_name%>
<%= ef.text_field :phone %>
<% end %>
<%= f.submit %>
<% end %>
The above has 1 form that lets you save the customer and it's employees. Called Nested Form, and I think does the job of needing "view models".
If you just keep it grouped how real-life is grouped, it goes pretty simple.
Because Rails uses an MVC architecture, it's perfectly possible—and often usual—to have a controller that co-ordinates multiple models and provides them to the view for rendering.
I think that thinking about Data Transfer Objects is a blind alley because they're just dumb data holders and ActiveRecord models in Rails have more smarts. They have associated models (I know DTOs can have structural relationships with other DTOs), custom finder methods and validation logic for a start.
Related
I am trying to build a simple donation app in rails. In this application, patrons would give amounts of money to clients. Both the patron and the client share large amounts of the same functionality. They are both linked to a user and have a username. However, the client is also supposed to have a content_type and a content_list property. At first glance, my guess is that I want to have both my patron and client inherit from the account class. However, the client has additional functionality, which seems to preclude any STI-based implementation (though I will be the first to admit that my understanding of STI is shaky at best). As it stands, it seems to simply make more sense to write out two separate resources, but I would like to keep my code as DRY as humanly possible. Is there a simple way for me to create the behaviors I want through inheritance, or should I simply go with overlapping resources?
Here's an idea, as both of your user-types share the same basic functionalities.
As you said, make one make one unified User or Account model. Include the database fields customer (as boolean) and patron (also as boolean).
In the signup process, the user can then select if they're a patron or customer, as they would regardless.
Then inside your view, you can then call, if you use Devise for instance (which I personally think is great)
<% if current_user.patron == true %>
<!-- all relevant UI functionality for patrons -->
<% end %>
or if the User is a customer
<% if current_user.customer == true %>
<!-- all relevant UI functionality for customers -->
<% end %>
Or if you want to loop through a list of patrons or customers:
<% #user.where(:customer == true).each do |users| %>
<% #user.first_name %> <% #user.last_name %>
<% end %>
These are just basic examples, but it would do just fine for what you're trying to achieve.
P.S You could also create one migration called "account_type" as a
string and then with the help of radio_buttons in the signup process
store the account_type as a string value.
<%= f.radio_button :account_type, "customer" %>
<%= f.radio_button :account_type, "patron" %>
I actually think that would be better. Then you would split the views up
like this:
<% if current_user.account_type => "customer" %>
Show the list that only customer should have or see.
<% end %>
<% if current_user.account_type => "patron" %>
Show the list that only patron should have or see.
<% end %>
<% #user.where(:account_type => "customer").each do |users| %>
<% #user.first_name %> <% #user.last_name %>
<% end %>
Regarding my question: Will a patron ever be a client? Will a client ever be a patron?
If the answer is "yes", you should consider creating a separate entity/model for the resource they're relating to.
With model DonationCampaign (attributes client_id, among others):
has_one :client, :class_name => 'User'
has_many :patrons, :class_name => 'DonationCampaignPatron'
DonationCampaignPatron (attributes patron_id, among others):
belongs_to :patron, :class_name => 'User'
This allows you to keep the shared functionality of User and then extend functionality to specific campaigns, without having to make other models messy, and keeps things DRY.
If a DonationCampaign could then have multiple users (as administrators, per se), to extend, a DonationCampaignRole model would be required, donation_campaign_id, user_id, role
Say you're using CanCanCan,
if can? :manage, #campaign
if can? :contribute, #campaign
:contribute would have to be added to ability.rb and could simply be #campaign.client != User
Also, STI example (the good kind, at least):
class Animal < ActiveRecord::Base
def says
raise "Implement on subclass"
end
end
class Cat < Animal
# always has a type of 'Cat'
def says
"meow"
end
end
class Dog < Animal
# always has a type of 'Dog'
def says
"woof"
end
end
And there's only one table: animals
Edit: Based on your response: From what I can learn by briefly using Patreon they have a single User model (for authentication), and then they likely have a creator_page_id in the User column. They then have a separate model CreatorPage which has all the "Client" (in your terms) info associated with it.
Personally, I would stick with a single User model for authentication and then implement the aforementioned DonationCampaign/DonationCampaignPatreon business logic. It's the most extensible with the least amount of effort (both long and shot-term).
If, for whatever reason, a Client is restricted to contributing to other Clients once they are a Client, I would forego using STI on the User model.
I have two models Run and Patient. Run belongs_to Patient and Patient has_many runs.
On the Run model I'm using accepts_nested_attributes in which to enter a patient's information into a run via a regular Rails form using fields_for.
Right now I have a basic form (stripped down version) that looks like this:
<%= form_for(#run) do |f| %>
<%= f.fields_for :patient do |p| %>
<%= p.text_field :patient_name, placeholder: 'John Doe', class: 'form-control' %>
<% end %>
<% end %>
I'm able to create a patient inside of the form and reject the associated/nested object created inside of the model. But what i'm looking to do is:
1.) Have a search/select box where I can type a patient name in and it will autocomplete
2.) If it finds the patient, it selects the patient and uses it for the run.
3.) If it does not find the patient or it's not the right patient, I'd like to be able to use first_or_create somehow to create the new patient record tied to the run.
I have searched for some examples on this and have come up short except an old Railscast espisode which wasn't very helpful and uses jQuery autocomplete which I'd like to avoid (use selectize or select2 instead).
I will continue working on examples that I find online, but any suggestions on how to do this is what I'm looking for. I feel that I can instantiate the patients from a collection within the controller and use those in a selectize/select2 box to choose the patient and the associated nested field of patient_id. So selecting the patient would not be a problem, but I'm unsure as to how to tackle the creation of a patient that does not match.
This answered my question:
https://gorails.com/episodes/select-or-create-with-selectize-js
I was able to get the behavior down that I needed using this.
I'm currently pretty new to Ruby on Rails, but right now I'm trying to set up a simple platform to create events, manage those events, and purchase tickets to those events. I have two different user types:
Manager:
has_many: events
(with params):
email
password
organization_name
Fan:
has_many: tickets
(with params):
email
password
name
cell_phone
I have two different partials containing sign-up forms for both Managers and Fans. My thought process right now is to have a param called #is_manager in the session that allows my sign-up form to dynamically hide/reveal the partials and to handle logic in the controller.
The sign-in form for both models will be identical, as they can both login using their emails and passwords. My current thought for this is to either include an additional checkbox which filters attempted logins to either the Fan or Manager database, or to require emails to be unique across both databases.
I've looked at a large number of other stack overflow questions, and have looked into Devise (which I was cautioned against while I still don't have a strong handling of Ruby on Rails), as well as some JQuery solutions to dynamically changing this session param, but I haven't found a solution which I feel applies well to me.
Thoughts?
(My current sign-up form code is something like below:)
<h1>Signup</h1>
<h3>Are you a manager?</h3>
<%= link_to_function "Yes", "magic_javascript_function" %>
<%= link_to_function "No", "magic_javascript_function" %>
<%= form_for :session, url: signup_path, method: :post do |f| %>
<%= render 'manager_signup', :f => f if #is_manager %>
<%= render 'user_signup', :f => f unless #is_manager %>
<%= f.submit 'Submit', class: "btn btn-primary" %>
<% end %>
If your new to RoR I highly advise going to The Rails Tutorial by Michael Hartl's railstutorial.org.
I recomend this tutorial because he shows how to create a user model with login sessions etc. without using devise or cancan and this gives excelent insight into what those programs are used for and how they work so you can use them later.
In regards to your specific case I would argue this: What if a member who has only acted as a manger wants to go to another managers event. Would he then need a separate fan account. Alternatively what if a member who has only been a fan wants to organize a local event for his garage band.
See where I am going with this. It might be best for a login/signup page to be agnostic towards these roles.
Where you could use these roles however is in you relationships
here you could do something like this
User.rb
has_many: events
has_many: tickets
Event.rb
belongs_to: manager, class_name: "User"
has_many: tickets
Ticket.rb
belongs_to: event
belongs_to: fan, class_name: "User"
Check the belongs_to api if it this gives you trouble you may need to explicitly set the foreign key.
Is it somehow possible to filter database records site wide? My models are league, match, team, player and detail. Now I would like when I enter the site and select a league that everything on the site is only displaying data for this specific league.
My relations are:
league has_many matches
match belongs_to team1
match belongs_to team2
team has_many players
player has_many details
Would that be possible?
You could do it like this:
Principle
If the league option is going to be the main decider of the content on your app, you might want to design around the league model
To do this, you'd make your root the league controller, and then show data based on the league's associated content
I.E instead of Team.joins(:league).where(league: "x"), you would do:
#Leagues
league = League.find(x)
league.matches
This would mean you could load the league directly from the database, making it much simpler for the user
Nested Resources
The alternative to this is to use the nested resources method that Michael Szyndel recommended in his comment. This would make it so you'd have to visit the URLs as follows:
domain.com/leagues/1/matches/2/teams/1
The benefit of this would be to DRY up your code, but it would add lots of extra levels to your system, which you may not want. I'll give an overview of what I'd look at below:
Code
#config/routes.rb
root to: "leagues#index"
#app/controllers/leagues_controller.rb
def index
league = params[:league] ? params[:league] : "0"
#league = League.find(league)
end
#app/views/league/index.html.erb
<%= form_tag('/') do %>
<%= select_tag 'league', options_for_select(League.all.collect {|u| [u.name, u.id] }) %>
<% end %>
<% #league.matches.each do |match| %>
<%= "#{match.team_1.name} vs #{match.team_2.name} : #{match.created_at}" %>
<% end %>
This would need to be re-factored & extended to include match details, make the "league" a session variable etc, but as the question might be closed, I thought I'd give you some ideas
The bottom line is that it comes down to how you want the app to function
Based on following models
class Company < ActiveRecord::Base
has_and_belongs_to_many :origins
end
class Origin < ActiveRecord::Base
has_and_belongs_to_many :companies
end
I want to have in my companies/_form a collection of checkboxes representing all origins.
Don't know if the Company.new(params[:company]) in companies_controller#create can create the association between company and the selected origins?
I'm running rails 3.0.0, what is the best way to achieve that?
thanks for your insights
habtm isn't a popular choice these days, it's better to use has_many :through instead, with a proper join model in between. This will give you the method Company#origin_ids= which you can pass an array of origin ids to from your form, to set all the associated origins for #company. eg
<% current_origin_ids = #company.origin_ids %>
<% form_for #company do |f| %>
<label>Name:<%= f.text_field :name %></label>
<% Origin.all.each do |origin| %>
<label><%= origin.name %>
<%= check_box_tag "company[origin_ids][]", origin.id, current_origin_ids.include?(origin.id) %>
</label>
<% end %>
<% end %>
As an aside, using a proper join model, with corresponding controller, allows you to easily add/remove origins with AJAX, using create/delete calls to the join model's controller.
I have to agreed with #carpeliam a has_many :through should not be the default choice. A HABTM works fine and involves less code. It also does not restrict the use of ajax and does expose a origin_ids setter to which you can pass an array of ids. Therefore the screencast, whilst from 2007, still works with Rails 3. The other option if using simple_form is this:
= form.association :origins, :as => :check_boxes
Personally I'm not of the belief that has-many-through is always better, it really depends on your situation. Has-many-through is better if there is ANY possibility of your join model having attributes itself. It's more flexible to change. It removes the magic of some Rails conventions. If however you don't need has-many-through, then there's an old RailsCast for HABTM checkboxes that might come in handy.