How to Pass Data from a Controller to a Model with Ruby on Rails

How do you pass data from a controller to a model with Ruby on Rails?

The concept you're wrestling with is MVC architecture, which is about separating responsibilities. The models should handle interaction with the DB (or other backend) without needing any knowledge of the context they're being used in (whether it be a an HTTP request or otherwise), views should not need to know about the backend, and controllers handle interactions between the two.

So in the case of your Rails app, the views and controllers have access to the request object, while your models do not. If you want to pass information from the current request to your model, it's up to your controller to do so. I would define your add_community as follows:

class User < ActiveRecord::Base

def add_community(city, state)
self.community = city.to_s + state.to_s # to_s just in case you got nils
end

end

And then in your controller:

class UsersController < ApplicationController

def create # I'm assuming it's create you're dealing with
...
@user.add_community(request.location.city, request.location.state)
...
end
end

I prefer not to pass the request object directly, because that really maintains the separation of the model from the current request. The User model doesn't need to know about request objects or how they work. All it knows is it's getting a city and a state.

Hope that helps.

Rails 4 pass data from controller to model attr_accessor

The @heros variable is a collection of Hero instances. You can loop through them and set it if you want.

class HerosController < ApplicationController
def index
@heros = Hero.all.order(:name).each do |hero|
hero.current_league = current_league
end
end
end

While it works, I don't find this answer to be all that elegant. Granted, I don't know the full extend of the thing you are making, but based on the code here I would create a composite object. Something like this:

class HeroInLeague

attr_reader :league, :hero

def initialize(league, hero)
@league = league
@hero = hero
end

def some_method
# ...
end

end

Then you can create these objects inside your controller:

class HerosController < ApplicationController
def index
@heros_in_league = Hero.all.order(:name).map { |hero|
HeroInLeague.new(current_league, hero)
}
end
end

Now you've created a place for methods to go that are related the combination of heros and leagues. Why is this important? Well, with the previous approach you'd probably end up with methods on Hero that don't make any sense when there is no current league. (like the some_method method). That makes the Hero class a bit of a mess. Now you've created a place to put some_method and all its friends.

You can use delegators to make the interface of HeroInLeague a bit more friendly, so you don't have to do hero_in_league.hero.foo, but can call hero_in_league.foo directly.

How Do I Pass Additional Parameters in Rails from Controller to Model


I have an association of cart and transaction in which cart has_many transactions and transaction belongs_to cart

Since that's the case and you already have a cart object, in your controller just instantiate the transaction from the cart:

transaction = cart.transactions.build app_token: params[:token]
transaction.save

cart_id will then be available to all the instance methods in the model, so there is no need to extend app_token= with additional logic unrelated to the app_token. Instead, take advantage of ActiveRecord callbacks. For example, you could use a before_save callback to implement your logic:

class Transaction < ActiveRecord::Base
belongs_to :cart
before_save :do_something_with_cart

def do_something_with_cart
# I want to add a few more lines of code here so that I can manipulate cart_id
end
end

If for some reason a callback does not fit your use casae, call the custom method directly in the controller:

transaction = cart.transactions.build app_token: params[:token]
transaction.do_something_with_cart

Rails - How to pass data between Rails controller and JavaScript Stimulus controller to dynamically change view

This sounds like plain old pagination.

If there could be thousands of movies you'd be generation markup/html for thousands of movies. With pagination you'd get get 3 records at a time. Could even use a turbo-frame to replace the next 3.

But if you really want to do what your thinking, you'd have hide all cards except for the first 3, then use stimulus to get the next or last three cards.

A test example

Controller

class TestController < ApplicationController
def index
@movies = Array.new(100) { |e| e = e + 1 }
end

Template using slim

.bg-white
div[data-controller="movies"]
- cnt = 0
button.btn[data-action="click->movies#last_set"] Last
button.btn[data-action="click->movies#next_set"] Next
- @movies.each do |m|
- if cnt < 3
div[data-movies-target="movie" class='block'] = "This is movie #{m}"
- cnt += 1
- else
div[data-movies-target="movie" class='hidden'] = "This is movie #{m}"

Stimulus Controller

import { Controller } from "@hotwired/stimulus"

// Connects to data-controller="movies"
export default class extends Controller {
static targets = ['movie']

connect() {
this.size = this.movieTargets.length
this.idx = 0
// this.idx points to the first current cards (3 for example)
}

next_set() {
let curr = this.idx
this.idx = curr + 3 // set new idx
if (this.idx > this.size) {this.idx = this.size - 3}
for (var i = curr; i <= curr + 2; i++) {
this.movieTargets[i].classList.add("hidden")
}
for (var i = this.idx; i <= this.idx + 2; i++) {
this.movieTargets[i].classList.remove("hidden")
}
// console.log(`idx = ${this.idx}`)
}

last_set() {
let curr = this.idx
this.idx = curr - 3 // set new idx
if (this.idx < 0 ) {this.idx = 0}
for (var i = curr; i <= curr + 2; i++) {
this.movieTargets[i].classList.add("hidden")
}
for (var i = this.idx; i <= this.idx + 2; i++) {
this.movieTargets[i].classList.remove("hidden")
}
// console.log(`idx = ${this.idx}`)
}

}

Just a rough example.

EDIT Not very good at javascript and cleaned up the stimulus controller, added variable number of elements to display and work with pages instead of elements.

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
static targets = ['perPage','movie','status','nextBtn','lastBtn']

connect() {
this.size = this.movieTargets.length
this.page = 0
this.perPage = Number(this.perPageTarget.innerHTML)
this.pages = Math.floor(this.size/this.perPage)
if ((this.size % this.perPage) > 0) {this.pages += 1}
this.setStatus()
// this.page points to the current set of cards (3 for example)
}


next_set() {
if (this.page == (this.pages - 1)) {return}
this.hide()
this.page += 1
this.show()
this.setStatus()
}

last_set() {
if (this.page == 0) {return}
this.hide()
this.page -= 1
this.show()
this.setStatus()
}

hide(){
let curr = this.page * this.perPage
let last = curr + this.perPage - 1
while(curr <= last){
if (curr >= 0 && curr < this.size) {
this.movieTargets[curr].classList.add("hidden")
}
curr++
}
}

show(){
let curr = this.page * this.perPage
let last = curr + this.perPage - 1
while(curr <= last){
if (curr >= 0 && curr < this.size) {
this.movieTargets[curr].classList.remove("hidden")
}
curr++
}
}

setStatus(){
this.statusTarget.innerHTML = `Page ${this.page + 1} of ${this.pages}`
if (this.page == 0) {
this.lastBtnTarget.classList.add('hidden')
}else{
this.lastBtnTarget.classList.remove('hidden')
}
if (this.page == this.pages) {
this.nextBtnTarget.classList.add('hidden')
}else{
this.nextBtnTarget.classList.remove('hidden')
}
}
}


Related Topics



Leave a reply



Submit