Ruby on Rails validation bypass and confusion - ruby-on-rails

I am facing a strange thing on Ruby on Rails and I don't know what is the correct way to deal with this situation.
I have two models, Book and Page.
Book(name:string)
Page(page_number: integer, book_id: ID)
class Book < ActiveRecord::Base
has_many :pages
accepts_nested_attributes_for :pages
end
class Page < ActiveRecord::Base
belongs_to :book
validates_uniqueness_of :page_number, scope: :book_id
end
I have created a view from where I can update a book. I accept nested attributes for pages and there is a section where I can update book pages as well as add new pages (by a Javascript function that lets the user to add a new page row by clicking + button). As you can see I have a validation that requires the page number to be unique for a certain book to prevent duplication. I have also defined an unique index on the database level (using Postgres)
On my update action in Book's Controller I have:
def update
#book = Book.find(params[:id])
if #book.update_attributes(params[:book])
flash[:notice] = 'Book successfully modified!'
end
end
The problem with my approach is that sometimes the validation about the page_number that I have defined on Pages model is bypassed and I get an error directly from PG ("PG::UniqueViolation: ERROR: duplicate key value violates unique constraint").
This scenario happens on 2 cases:
Trying to create two or more pages with the same number directly on one form submission
Updating existing page_number (ex from page_number: 4 to nr: 5) and creating one new page with number 4 on one form submission.
It seems that there is some problem of concurrency and order of processing the updates/creates.
For the point 2, we should somehow tell Rails to look over all records and see if there we are trying to do any duplication by combining updates with creates. Point 2 is a valid option and there should be no validation error thrown but because Rails is doing creation first it laments that a page with page_number 4 already exists (without taking into considerance that page_number 4 is being updated to 5)
I would be thankful I you could advice me how to handle this situation and be able to predict all use cases so that I do not hit the database in case of a validation error.
If this is impossible, is there a way that I can catch the error from Postgres, format and display it to the user?
I appreciate any advice!

Related

issue displaying associated record in show action rails 5

For some reason I'm having an issue displaying an associated record on my account show page.
I have the following models:
account.rb has the association of: belongs_to :plan
plan.rb has the association of: has_many :accounts
# accounts_controller.rb
def show
#account = Account.find(params[:id])
#account.plan = Plan.find_by(params[:plan_id])
end
When I try to display the Plan name on the account page, it only displays the first plan name (even through i have multiple plans and the account has the plan_id properly set.
ie Account 1 has a plan_id of 2 but displays the info for plan 1 where as Account 2 has the plan_id of 1 and that shows the proper plan...
Not sure where I'm veering off course here any assistance would be appreciated.
ActiveRecord/FinderMethods#find_by find and returns the first record matching the conditions, but for that you need to pass the attribute which you're looking for and the value assigned, in your case you're passing just the value.
If you're planning on getting the plan for that specific account then Plan.find(id) would be enough.

Dynamically generate values and create multiple records based on user input with Ruby on Rails

In my Ruby on Rails application I am trying to dynamically create n number of database records based on a number that the user enters on a form. To keep things simple,I will use the analogy of a book and its chapters, to illustrate what I am trying to do. Below is my model, attributes, and form to create a book.
Models:
class Book < ActiveRecord::Base
has_many :chapters
end
class Chapter < ActiveRecord::Base
belongs_to :book
end
Book Attributes:
id, book_title, number_of_chapters , created_at, updated_at
Chapter Attributes:
id, chapter_title, contents, book_id, created_at, updated_at
Form to create new Book:
<%= form_for(#book) do |f| %>
<%= f.text_field :book_title %>
<%= f.text_field :number_of_chapters %>
<%= f.submit %>
<% end %>
When a user creates a book, they can enter in the book's title and the number of chapters the book has. If the user enters in 5 chapters, we then need to be able to dynamically insert 5 records into the Chapter table each with a unique generated chapter title name.
The generated chapter titles could be as simple as:
chapter1
chapter2
chapter3
chapter4
chapter5
The closest solution I have been able to find in my research is to create a nested model form. But a nested model form is not the ideal solution, for a couple of reasons. I cannot predict the number of records that will need to be inserted into the second model, it could be a few or as high as 10,000 records. Plus manually data entering field values for that many records is not practical.
I have a few bits and pieces of what I think needs to be done, but I need help tying it all together.
My first thought is to use an after_save callback to trigger a method that will create the chapters. I would add the following to the Book model.
after_save Chapter.generate_chapter_titles
But I am questioning if after_save is the best way to do this, from a performance standpoint, since we could potentially be creating several thousand records at a time.
On the Chapter model I was thinking of using the following method to create the chapter_titles values based on the number_of_chapters.
def generate_chapter_titles
1.upto(book.number_of_chapters) do |chapter|
print "chapter%d" % chapter
end
But now I am stuck, I haven't quite figured how to take the output from generate_chapter_titles and create the individual Chapter db records. While making sure that the newly generated chapters are associated with the book it was created from.
Any help on getting this functionality working is greatly appreciated. I have tried to research this before posting here, but answer still eludes me. I am a programming and Ruby noob, so I am willing to admit that some of the information on the Ruby and Ruby on Rails API documentation goes over my head. I would greatly appreciate any help on breaking down and understanding the added functionally that I need to make this work.
Update - Final Solution
Thanks to #margo, all I had to do was update the create method in the books_controller.rb with the code below. Now the chapters are generated automatically based on the value entered in for number_of_chapters.
class BooksController < ApplicationController
[...]
def create
#book = Book.new(book_params)
if #book.save
ActiveRecord::Base.transaction do
#book.number_of_chapters.times do |n|
Chapter.create!(book_id: #book.id, title: "Chapter #{n+1}")
end
end
redirect_to #book, notice: "Book created successfully."
else
render 'new'
end
end
[...]
end
First of all, you will want to wrap the creation of the book's chapters in a transaction. Transactions will ensure that all the chapters get saved or rolls back any commits if there is an error before it finishes.
You can create all the chapters in the create method in the books_controller. Below is an example, you'll have to fill in the pieces.
In your books_controller
def create
#book = Book.new(book_params)
if #book.save
generate_chapters(#book)
end
end
private
def generate_chapters(book)
ActiveRecord::Base.transaction do
book.number_of_chapters.times do |n|
Chapter.create!(book_id: book.id, title: "Chapter #{n+1})"
end
end
end
I would suggest that you start with this approach and get it working. If you're going to be creating thousands of records at once, then you might want to consider moving this to a background job, but that's a separate question.

implementing a many-to-may association in Rails 4 app

I'm working on a Video Tutorial application in Rails 4 (https://github.com/acandael/courseapp)
I'm thinking how best to implement the feature where a user can see which chapters he completed and wich videos he viewed. Completed chapters then get a 'Completed' tag beside them and watched videos get a checkmark icon beside them.
I think a way to implement this, for instance for chapters is to create a many-to-many association between the User model and Chapter model.
Two questions concerning this scenario. Does my join table needs an extra field besides the field for the user_id and chapter_id foreign keys, for instance a field 'is_complete' of type boolean ?
Second question, how can I check in my view whether a user has completed a chapter? Could I check this with
#user.chapter.is_complete?
Thanks for your advice,
Anthony
A many to many relationship is the best way to handle this situation.
class Chapter
has_and_belongs_to_many :users
end
class User
has_and_belongs_to_many chapters,
:as => :completed_chapters # Not sure about this
def has_completed?(chapter)
completed_chapters.include?(chapter)
end
end
# Create a basic relationship here
user = User.new
chapter = Chapter.new
user.chapters << chapter
user.has_completed?(chapter)
# => true

How to let user modifying content of pages with rails ? Query for text?

I'm developing a website with Ruby on Rails.
I want to find the better way to let users (not developers) to edit text on some pages (like the index...). (like a CMS ?)
Actually they had to get the page through FTP, to edit the text and to put the new file on the server (through FTP).
It's a very very bad practice and I wanted to know if someone has an idea to solve this problem ?
Many thanks
It would be the same as the basic Rails CRUD operations. Just make a model/controller representing page content, and an edit view for the controller. Then on the pages you want text to be editable, instead of having the content directly on the page just use a view partial.
Of course, you would probably also want to implement some type of authentication to make sure not just everyone can edit pages.
Well, one thing you could do is add a model to your database called "Content" or "Copy" which represents some text on a page. Then you could use polymorphic association to link the content/copy to your actual model. For instance, if you had a page with a list of products on it, you'd likely have a Product model in your database. You could do something like this:
class Content < ActiveRecord::Base
belongs_to :contentable, :polymorphic => true # excuse my lame naming here
# this model would need two fields to make it polymorphic:
# contentable_id <-- Integer representing the record that owns it
# contentable_type <-- String representing the kind of model (Class) that owns it
# An example would look like:
# contentable_id: 4 <--- Product ID 4 in your products table
# contentable_type: Product <--- Tells the rails app which model owns this record
# You'd also want a text field in this model where you store the page text that your
# users enter.
end
class Product < ActiveRecord::Base
has_many :contents, :as => :contentable # again forgive my naming
end
In this scenario, when the product page renders, you could call #product.contents to retrieve all the text users have entered for this product. If you don't want to use two separate models like this, you could put a text field directly on the Product model itself and have users enter text there.

Cucumber and webrat - How to handle dynamic URLs in the paths.rb?

I am using Cucumber for BDD development in my Ruby on Rails project and I'm running into some confusion on how the path.rb handles paths used in rails applications.
Given I have:
class Parent < ActiveRecord::Base
has_many :children
end
class Child < ActiveRecord::Base
belongs_to :parent
end
and I have the following Cucumber feature:
Scenario: A test feature
Given I am on the parent page
When I follow "Link to Children"
Then I should be on the children list page
with the path defined as:
def path_to(page_name)
case page_name
when /the children list page/
'/parents/:id/children'
end
The problem I come across is the following error when running the feature:
Spec::Expectations::ExpectationNotMetError: expected: "/parents/:id/children",
got: "/parents/1726/children" (using ==)
I don't really care what the :id is. What should I do instead? Is this even possible with the default web steps? Am I thinking about the problem in the wrong way?
The way I do it, which may not be the best way is as follows:
when /the children list page for "(.+)"/
p = Parent.find_by_name($1)
parent_children_path(p)
In our app, we always wanted a new record in the database whenever a user clicked the "new" button. Thus, our controller's new action automatically calls create and then redirects to the edit action.
We faced a similar problem in testing, when we didn't care so much about what the ID was -- just that it got to the edit page for the app.
Here's what I came up with.
(NOTE: The step definition is written using capybara, but it shouldn't be too different from webrat)
Then /^(?:|I )should now be editing the (.*)$/ do |model|
id = find_by_id("#{model}_id").value
Then "I should be on the edit #{model} page for \"#{id}\""
end
The basic premise is that when you're on a Rails edit page, there will be a form for the model you're editing. That form always contains a hidden field with the ID of the specific record you're editing.
The step finds the hidden field, extracts the ID from it, and then looks for a web_step to resolve the path for that model.
Just make sure you have a path that matches for the model you're looking up.
when /the edit person page for "([^\"]*)"/
edit_person_path($1)

Resources